diff --git a/mtec/integrationpluginmtec.cpp b/mtec/integrationpluginmtec.cpp index 52f9712..4a2e3f5 100644 --- a/mtec/integrationpluginmtec.cpp +++ b/mtec/integrationpluginmtec.cpp @@ -29,6 +29,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include +#include #include "integrationpluginmtec.h" #include "plugininfo.h" @@ -55,30 +56,46 @@ void IntegrationPluginMTec::discoverThings(ThingDiscoveryInfo *info) qCDebug(dcMTec()) << "Found" << networkDeviceInfo; QString title; - if (networkDeviceInfo.hostName().isEmpty()) { - title = networkDeviceInfo.address().toString(); - } else { - title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")"; - } - QString description; - if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { - description = networkDeviceInfo.macAddress(); - } else { - description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")"; + MacAddressInfo macInfo; + + switch (networkDeviceInfo.monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + macInfo = networkDeviceInfo.macAddressInfos().constFirst(); + description = networkDeviceInfo.address().toString(); + if (!macInfo.vendorName().isEmpty()) + description += " - " + networkDeviceInfo.macAddressInfos().constFirst().vendorName(); + + if (networkDeviceInfo.hostName().isEmpty()) { + title = macInfo.macAddress().toString(); + } else { + title = networkDeviceInfo.hostName() + " (" + macInfo.macAddress().toString() + ")"; + } + + break; + case NetworkDeviceInfo::MonitorModeHostName: + title = networkDeviceInfo.hostName(); + description = networkDeviceInfo.address().toString(); + break; + case NetworkDeviceInfo::MonitorModeIp: + title = "Network device " + networkDeviceInfo.address().toString(); + description = "Interface: " + networkDeviceInfo.networkInterface().name(); + break; } ThingDescriptor descriptor(mtecThingClassId, title, description); + ParamList params; - params << Param(mtecThingIpAddressParamTypeId, networkDeviceInfo.address().toString()); - params << Param(mtecThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + params << Param(mtecThingMacAddressParamTypeId, networkDeviceInfo.thingParamValueMacAddress()); + params << Param(mtecThingAddressParamTypeId, networkDeviceInfo.thingParamValueAddress()); + params << Param(mtecThingHostNameParamTypeId, networkDeviceInfo.thingParamValueHostName()); descriptor.setParams(params); // Check if we already have set up this device - Things existingThings = myThings().filterByParam(mtecThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); - if (existingThings.count() == 1) { - qCDebug(dcMTec()) << "This heat pump already exists in the system!" << networkDeviceInfo; - descriptor.setThingId(existingThings.first()->id()); + Thing *existingThing = myThings().findByParams(params); + if (existingThing) { + qCDebug(dcMTec()) << "This heat pump already exists in the system:" << networkDeviceInfo; + descriptor.setThingId(existingThing->id()); } info->addThingDescriptor(descriptor); @@ -93,151 +110,174 @@ void IntegrationPluginMTec::setupThing(ThingSetupInfo *info) Thing *thing = info->thing(); qCDebug(dcMTec()) << "Setup" << thing; - if (thing->thingClassId() == mtecThingClassId) { - QHostAddress hostAddress = QHostAddress(thing->paramValue(mtecThingIpAddressParamTypeId).toString()); - if (hostAddress.isNull()) { - info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("No IP address given")); - return; - } - - qCDebug(dcMTec()) << "Using ip address" << hostAddress.toString(); - - MTec *mtec = new MTec(hostAddress, this); - connect(mtec, &MTec::connectedChanged, thing, [=](bool connected){ - qCDebug(dcMTec()) << thing << "Connected changed to" << connected; - thing->setStateValue(mtecConnectedStateTypeId, connected); - }); - - connect(mtec, &MTec::roomTemperatureChanged, thing, [=](double roomTemperature){ - qCDebug(dcMTec()) << thing << "Room temperature" << roomTemperature << "°C"; - thing->setStateValue(mtecTemperatureStateTypeId, roomTemperature); - }); - - connect(mtec, &MTec::targetRoomTemperatureChanged, thing, [=](double targetRoomTemperature){ - qCDebug(dcMTec()) << thing << "Target room temperature" << targetRoomTemperature << "°C"; - thing->setStateValue(mtecTargetTemperatureStateTypeId, targetRoomTemperature); - }); - - connect(mtec, &MTec::waterTankTopTemperatureChanged, thing, [=](double waterTankTopTemperature){ - qCDebug(dcMTec()) << thing << "Water tank top temperature" << waterTankTopTemperature << "°C"; - thing->setStateValue(mtecWaterTankTopTemperatureStateTypeId, waterTankTopTemperature); - }); - - connect(mtec, &MTec::bufferTankTemperatureChanged, thing, [=](double bufferTankTemperature){ - qCDebug(dcMTec()) << thing << "Buffer tank temperature" << bufferTankTemperature << "°C"; - thing->setStateValue(mtecBufferTankTemperatureStateTypeId, bufferTankTemperature); - }); - - connect(mtec, &MTec::totalAccumulatedHeatingEnergyChanged, thing, [=](double totalAccumulatedHeatingEnergy){ - qCDebug(dcMTec()) << thing << "Total accumulated heating energy" << totalAccumulatedHeatingEnergy << "kWh"; - thing->setStateValue(mtecTotalAccumulatedHeatingEnergyStateTypeId, totalAccumulatedHeatingEnergy); - }); - - connect(mtec, &MTec::totalAccumulatedElectricalEnergyChanged, thing, [=](double totalAccumulatedElectricalEnergy){ - qCDebug(dcMTec()) << thing << "Total accumulated electrical energy" << totalAccumulatedElectricalEnergy << "kWh"; - thing->setStateValue(mtecTotalAccumulatedElectricalEnergyStateTypeId, totalAccumulatedElectricalEnergy); - }); - - connect(mtec, &MTec::heatPumpStateChanged, thing, [=](MTec::HeatpumpState heatPumpState){ - qCDebug(dcMTec()) << thing << "Heat pump state" << heatPumpState; - switch (heatPumpState) { - case MTec::HeatpumpStateStandby: - thing->setStateValue(mtecHeatPumpStateStateTypeId, "Standby"); - thing->setStateValue(mtecHeatingOnStateTypeId, false); - thing->setStateValue(mtecCoolingOnStateTypeId, false); - break; - case MTec::HeatpumpStatePreRun: - thing->setStateValue(mtecHeatPumpStateStateTypeId, "Pre run"); - thing->setStateValue(mtecHeatingOnStateTypeId, false); - thing->setStateValue(mtecCoolingOnStateTypeId, false); - break; - case MTec::HeatpumpStateAutomaticHeat: - thing->setStateValue(mtecHeatPumpStateStateTypeId, "Automatic heat"); - thing->setStateValue(mtecHeatingOnStateTypeId, true); - thing->setStateValue(mtecCoolingOnStateTypeId, false); - break; - case MTec::HeatpumpStateDefrost: - thing->setStateValue(mtecHeatPumpStateStateTypeId, "Defrost"); - thing->setStateValue(mtecHeatingOnStateTypeId, false); - thing->setStateValue(mtecCoolingOnStateTypeId, false); - break; - case MTec::HeatpumpStateAutomaticCool: - thing->setStateValue(mtecHeatPumpStateStateTypeId, "Automatic cool"); - thing->setStateValue(mtecHeatingOnStateTypeId, false); - thing->setStateValue(mtecCoolingOnStateTypeId, true); - break; - case MTec::HeatpumpStatePostRun: - thing->setStateValue(mtecHeatingOnStateTypeId, false); - thing->setStateValue(mtecHeatPumpStateStateTypeId, "Post run"); - thing->setStateValue(mtecCoolingOnStateTypeId, false); - break; - case MTec::HeatpumpStateSaftyShutdown: - thing->setStateValue(mtecHeatingOnStateTypeId, false); - thing->setStateValue(mtecHeatPumpStateStateTypeId, "Safty shutdown"); - thing->setStateValue(mtecCoolingOnStateTypeId, false); - break; - case MTec::HeatpumpStateError: - thing->setStateValue(mtecHeatingOnStateTypeId, false); - thing->setStateValue(mtecHeatPumpStateStateTypeId, "Error"); - thing->setStateValue(mtecCoolingOnStateTypeId, false); - break; - } - }); - - connect(mtec, &MTec::heatMeterPowerConsumptionChanged, thing, [=](double heatMeterPowerConsumption){ - qCDebug(dcMTec()) << thing << "Heat meter power consumption" << heatMeterPowerConsumption << "W"; - thing->setStateValue(mtecHeatMeterPowerConsumptionStateTypeId, heatMeterPowerConsumption); - }); - - connect(mtec, &MTec::energyMeterPowerConsumptionChanged, thing, [=](double energyMeterPowerConsumption){ - qCDebug(dcMTec()) << thing << "Energy meter power consumption" << energyMeterPowerConsumption << "W"; - thing->setStateValue(mtecEnergyMeterPowerConsumptionStateTypeId, energyMeterPowerConsumption); - }); - - connect(mtec, &MTec::actualExcessEnergySmartHomeChanged, thing, [=](double actualExcessEnergySmartHome){ - qCDebug(dcMTec()) << thing << "Smart home energy" << actualExcessEnergySmartHome << "W"; - thing->setStateValue(mtecSmartHomeEnergyStateTypeId, actualExcessEnergySmartHome); - }); - - connect(mtec, &MTec::actualExcessEnergySmartHomeElectricityMeterChanged, thing, [=](double actualExcessEnergySmartHomeElectricityMeter){ - qCDebug(dcMTec()) << thing << "Smart home energy electrical meter" << actualExcessEnergySmartHomeElectricityMeter << "W"; - thing->setStateValue(mtecSmartHomeEnergyElectricityMeterStateTypeId, actualExcessEnergySmartHomeElectricityMeter); - }); - - connect(mtec, &MTec::actualOutdoorTemperatureChanged, thing, [=](double actualOutdoorTemperature){ - qCDebug(dcMTec()) << thing << "Outdoor temperature" << actualOutdoorTemperature << "°C"; - thing->setStateValue(mtecOutdoorTemperatureStateTypeId, actualOutdoorTemperature); - }); - - m_mtecConnections.insert(thing, mtec); - - // TODO: start timer and give 15 seconds until connected, since the controler is down for ~10 seconds after a disconnect - - if (!mtec->connectDevice()) { - qCWarning(dcMTec()) << "Initial connect returned false. Lets wait 15 seconds until the connection can be established."; - } - - info->finish(Thing::ThingErrorNoError); + NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing); + if (!monitor) { + qCWarning(dcMTec()) << "Unable to register monitor with the given params" << thing->params(); + info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Unable to set up the connection with this configuration, please reconfigure the connection.")); + return; } + + qCInfo(dcMTec()) << "Set up MTec modbus connection with" << monitor; + m_monitors.insert(thing, monitor); + connect(info, &ThingSetupInfo::aborted, monitor, [this, thing](){ + if (m_monitors.contains(thing)) { + qCDebug(dcMTec()) << "Unregistering monitor because setup has been aborted."; + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + } + }); + + + QHostAddress address = monitor->networkDeviceInfo().address(); + if (address.isNull()) { + qCWarning(dcMTec()) << "Cannot set up thing. The host address is not known yet. Maybe it will be available in the next run..."; + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying later again.")); + return; + } + + MTec *mtec = new MTec(address, this); + connect(monitor, &NetworkDeviceMonitor::reachableChanged, this, [=](bool reachable ){ + qCDebug(dcMTec()) << "Monitor reachable changed to" << reachable << "for" << thing; + if (reachable && !thing->stateValue("connected").toBool()) { + mtec->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address()); + mtec->connectDevice(); + } else if (!reachable) { + // Note: We disable autoreconnect explicitly and we will + // connect the device once the monitor says it is reachable again + mtec->disconnectDevice(); + } + }); + + connect(mtec, &MTec::connectedChanged, thing, [=](bool connected){ + qCDebug(dcMTec()) << thing << "Connected changed to" << connected; + thing->setStateValue(mtecConnectedStateTypeId, connected); + }); + + connect(mtec, &MTec::roomTemperatureChanged, thing, [=](double roomTemperature){ + qCDebug(dcMTec()) << thing << "Room temperature" << roomTemperature << "°C"; + thing->setStateValue(mtecTemperatureStateTypeId, roomTemperature); + }); + + connect(mtec, &MTec::targetRoomTemperatureChanged, thing, [=](double targetRoomTemperature){ + qCDebug(dcMTec()) << thing << "Target room temperature" << targetRoomTemperature << "°C"; + thing->setStateValue(mtecTargetTemperatureStateTypeId, targetRoomTemperature); + }); + + connect(mtec, &MTec::waterTankTopTemperatureChanged, thing, [=](double waterTankTopTemperature){ + qCDebug(dcMTec()) << thing << "Water tank top temperature" << waterTankTopTemperature << "°C"; + thing->setStateValue(mtecWaterTankTopTemperatureStateTypeId, waterTankTopTemperature); + }); + + connect(mtec, &MTec::bufferTankTemperatureChanged, thing, [=](double bufferTankTemperature){ + qCDebug(dcMTec()) << thing << "Buffer tank temperature" << bufferTankTemperature << "°C"; + thing->setStateValue(mtecBufferTankTemperatureStateTypeId, bufferTankTemperature); + }); + + connect(mtec, &MTec::totalAccumulatedHeatingEnergyChanged, thing, [=](double totalAccumulatedHeatingEnergy){ + qCDebug(dcMTec()) << thing << "Total accumulated heating energy" << totalAccumulatedHeatingEnergy << "kWh"; + thing->setStateValue(mtecTotalAccumulatedHeatingEnergyStateTypeId, totalAccumulatedHeatingEnergy); + }); + + connect(mtec, &MTec::totalAccumulatedElectricalEnergyChanged, thing, [=](double totalAccumulatedElectricalEnergy){ + qCDebug(dcMTec()) << thing << "Total accumulated electrical energy" << totalAccumulatedElectricalEnergy << "kWh"; + thing->setStateValue(mtecTotalAccumulatedElectricalEnergyStateTypeId, totalAccumulatedElectricalEnergy); + }); + + connect(mtec, &MTec::heatPumpStateChanged, thing, [=](MTec::HeatpumpState heatPumpState){ + qCDebug(dcMTec()) << thing << "Heat pump state" << heatPumpState; + switch (heatPumpState) { + case MTec::HeatpumpStateStandby: + thing->setStateValue(mtecHeatPumpStateStateTypeId, "Standby"); + thing->setStateValue(mtecHeatingOnStateTypeId, false); + thing->setStateValue(mtecCoolingOnStateTypeId, false); + break; + case MTec::HeatpumpStatePreRun: + thing->setStateValue(mtecHeatPumpStateStateTypeId, "Pre run"); + thing->setStateValue(mtecHeatingOnStateTypeId, false); + thing->setStateValue(mtecCoolingOnStateTypeId, false); + break; + case MTec::HeatpumpStateAutomaticHeat: + thing->setStateValue(mtecHeatPumpStateStateTypeId, "Automatic heat"); + thing->setStateValue(mtecHeatingOnStateTypeId, true); + thing->setStateValue(mtecCoolingOnStateTypeId, false); + break; + case MTec::HeatpumpStateDefrost: + thing->setStateValue(mtecHeatPumpStateStateTypeId, "Defrost"); + thing->setStateValue(mtecHeatingOnStateTypeId, false); + thing->setStateValue(mtecCoolingOnStateTypeId, false); + break; + case MTec::HeatpumpStateAutomaticCool: + thing->setStateValue(mtecHeatPumpStateStateTypeId, "Automatic cool"); + thing->setStateValue(mtecHeatingOnStateTypeId, false); + thing->setStateValue(mtecCoolingOnStateTypeId, true); + break; + case MTec::HeatpumpStatePostRun: + thing->setStateValue(mtecHeatingOnStateTypeId, false); + thing->setStateValue(mtecHeatPumpStateStateTypeId, "Post run"); + thing->setStateValue(mtecCoolingOnStateTypeId, false); + break; + case MTec::HeatpumpStateSaftyShutdown: + thing->setStateValue(mtecHeatingOnStateTypeId, false); + thing->setStateValue(mtecHeatPumpStateStateTypeId, "Safty shutdown"); + thing->setStateValue(mtecCoolingOnStateTypeId, false); + break; + case MTec::HeatpumpStateError: + thing->setStateValue(mtecHeatingOnStateTypeId, false); + thing->setStateValue(mtecHeatPumpStateStateTypeId, "Error"); + thing->setStateValue(mtecCoolingOnStateTypeId, false); + break; + } + }); + + connect(mtec, &MTec::heatMeterPowerConsumptionChanged, thing, [=](double heatMeterPowerConsumption){ + qCDebug(dcMTec()) << thing << "Heat meter power consumption" << heatMeterPowerConsumption << "W"; + thing->setStateValue(mtecHeatMeterPowerConsumptionStateTypeId, heatMeterPowerConsumption); + }); + + connect(mtec, &MTec::energyMeterPowerConsumptionChanged, thing, [=](double energyMeterPowerConsumption){ + qCDebug(dcMTec()) << thing << "Energy meter power consumption" << energyMeterPowerConsumption << "W"; + thing->setStateValue(mtecEnergyMeterPowerConsumptionStateTypeId, energyMeterPowerConsumption); + }); + + connect(mtec, &MTec::actualExcessEnergySmartHomeChanged, thing, [=](double actualExcessEnergySmartHome){ + qCDebug(dcMTec()) << thing << "Smart home energy" << actualExcessEnergySmartHome << "W"; + thing->setStateValue(mtecSmartHomeEnergyStateTypeId, actualExcessEnergySmartHome); + }); + + connect(mtec, &MTec::actualExcessEnergySmartHomeElectricityMeterChanged, thing, [=](double actualExcessEnergySmartHomeElectricityMeter){ + qCDebug(dcMTec()) << thing << "Smart home energy electrical meter" << actualExcessEnergySmartHomeElectricityMeter << "W"; + thing->setStateValue(mtecSmartHomeEnergyElectricityMeterStateTypeId, actualExcessEnergySmartHomeElectricityMeter); + }); + + connect(mtec, &MTec::actualOutdoorTemperatureChanged, thing, [=](double actualOutdoorTemperature){ + qCDebug(dcMTec()) << thing << "Outdoor temperature" << actualOutdoorTemperature << "°C"; + thing->setStateValue(mtecOutdoorTemperatureStateTypeId, actualOutdoorTemperature); + }); + + m_mtecConnections.insert(thing, mtec); + + // TODO: start timer and give 15 seconds until connected, since the controler is down for ~10 seconds after a disconnect + + if (!mtec->connectDevice()) { + qCWarning(dcMTec()) << "Initial connect returned false. Lets wait 15 seconds until the connection can be established."; + } + + info->finish(Thing::ThingErrorNoError); } void IntegrationPluginMTec::postSetupThing(Thing *thing) { - if (thing->thingClassId() == mtecThingClassId) { - MTec *mtec = m_mtecConnections.value(thing); - if (mtec) { - update(thing); - } + if (m_mtecConnections.contains(thing)) + update(thing); - if (!m_pluginTimer) { - qCDebug(dcMTec()) << "Starting plugin timer..."; - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); - connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { - foreach (Thing *thing, myThings().filterByThingClassId(mtecThingClassId)) { - update(thing); - } - }); - } + if (!m_pluginTimer) { + qCDebug(dcMTec()) << "Starting plugin timer..."; + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { + foreach (Thing *thing, myThings().filterByThingClassId(mtecThingClassId)) { + update(thing); + } + }); } } @@ -251,6 +291,9 @@ void IntegrationPluginMTec::thingRemoved(Thing *thing) } } + if (m_monitors.contains(thing)) + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + if (myThings().isEmpty()) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); m_pluginTimer = nullptr; diff --git a/mtec/integrationpluginmtec.h b/mtec/integrationpluginmtec.h index 7a476ad..0c75cae 100644 --- a/mtec/integrationpluginmtec.h +++ b/mtec/integrationpluginmtec.h @@ -37,6 +37,8 @@ #include +class NetworkDeviceMonitor; + class IntegrationPluginMTec: public IntegrationPlugin { Q_OBJECT @@ -57,6 +59,7 @@ public: private: PluginTimer *m_pluginTimer = nullptr; QHash m_mtecConnections; + QHash m_monitors; private slots: void update(Thing *thing); diff --git a/mtec/integrationpluginmtec.json b/mtec/integrationpluginmtec.json index 0885ba8..466eedc 100644 --- a/mtec/integrationpluginmtec.json +++ b/mtec/integrationpluginmtec.json @@ -13,15 +13,23 @@ "displayName": "MTec", "id": "451e38d8-50d5-4ae9-8d9f-21af9347128d", "createMethods": ["discovery", "user"], - "interfaces": ["thermostat", "connectable"], + "interfaces": ["thermostat", "connectable", "networkdevice"], "paramTypes": [ { "id": "f1c43b1e-cffe-4d30-bda0-c23ed648dd71", - "name": "ipAddress", + "name": "address", "displayName": "IP address", "type": "QString", "inputType": "IPv4Address", - "defaultValue": "127.0.0.1" + "defaultValue": "" + }, + { + "id": "544348c3-485d-4fba-bb9b-a64031241ac8", + "name": "hostName", + "displayName": "Host name", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "" }, { "id": "906f6099-d0e1-4297-a2b3-f8ec4482c578", diff --git a/mtec/mtec.cpp b/mtec/mtec.cpp index ed94152..d6f7003 100644 --- a/mtec/mtec.cpp +++ b/mtec/mtec.cpp @@ -51,6 +51,11 @@ MTec::~MTec() m_modbusMaster->disconnectDevice(); } +ModbusTcpMaster *MTec::modbusTcpMaster() const +{ + return m_modbusMaster; +} + QHostAddress MTec::hostAddress() const { return m_hostAddress; diff --git a/mtec/mtec.h b/mtec/mtec.h index 20ce064..e77fa9f 100644 --- a/mtec/mtec.h +++ b/mtec/mtec.h @@ -55,7 +55,10 @@ public: explicit MTec(const QHostAddress &address, QObject *parent = nullptr); ~MTec(); + ModbusTcpMaster *modbusTcpMaster() const; + QHostAddress hostAddress() const; + bool connected() const; QModbusReply *setTargetRoomTemperature(double targetRoomTemperature);