Merge PR #7: New Plugin: iDM

This commit is contained in:
Jenkins nymea 2021-06-30 12:44:06 +02:00
commit 96e079a05e
23 changed files with 1489 additions and 261 deletions

16
debian/control vendored
View File

@ -29,6 +29,22 @@ Description: nymea.io plugin for Drexel & Weiss heat pumps
This package will install the nymea.io plugin for Drexel & Weiss heat pumps
Package: nymea-plugin-idm
Architecture: any
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
nymea-plugins-modbus-translations,
Description: nymea.io plugin for iDM heat pumps
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package will install the nymea.io plugin for iDM heat pumps
Package: nymea-plugin-modbuscommander
Architecture: any
Section: libs

1
debian/nymea-plugin-idm.install.in vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginidm.so

18
idm/README.md Normal file
View File

@ -0,0 +1,18 @@
# iDM
Connect nymea to iDM heat pumps.
## Supported Things
* Navigator 2.0 based heat pump models
## Requirements
* The package 'nymea-plugin-idm' must be installed
* Navigator 2.0 settings
* "Modbus TCP" must be selected in the "Building Management System" menu?
* Both devices must be in the same local area network.
## More
https://www.idm-energie.at/en/

198
idm/idm.cpp Normal file
View File

@ -0,0 +1,198 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "idm.h"
#include "extern-plugininfo.h"
#include "../modbus/modbushelpers.h"
#include <QTimer>
Idm::Idm(const QHostAddress &address, QObject *parent) :
QObject(parent),
m_hostAddress(address)
{
qCDebug(dcIdm()) << "iDM: Creating iDM connection" << m_hostAddress.toString();
m_modbusMaster = new ModbusTCPMaster(address, 502, this);
if (m_modbusMaster) {
qCDebug(dcIdm()) << "iDM: Created ModbusTCPMaster";
connect(m_modbusMaster, &ModbusTCPMaster::receivedHoldingRegister, this, &Idm::onReceivedHoldingRegister);
connect(m_modbusMaster, &ModbusTCPMaster::readRequestError, this, &Idm::onModbusError);
connect(m_modbusMaster, &ModbusTCPMaster::writeRequestError, this, &Idm::onModbusError);
connect(m_modbusMaster, &ModbusTCPMaster::writeRequestExecuted, this, &Idm::writeRequestExecuted);
}
}
Idm::~Idm()
{
qCDebug(dcIdm()) << "iDM: Deleting iDM connection" << m_hostAddress.toString();
}
bool Idm::connectDevice()
{
qCDebug(dcIdm()) << "iDM: Connecting device";
return m_modbusMaster->connectDevice();
}
QHostAddress Idm::getIdmAddress() const
{
return m_hostAddress;
}
void Idm::getStatus()
{
//this request starts an update cycle
m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::OutsideTemperature, 2);
}
QUuid Idm::setTargetTemperature(double targetTemperature)
{
QVector<uint16_t> value = ModbusHelpers::convertFloatToRegister(targetTemperature);
return m_modbusMaster->writeHoldingRegisters(Idm::modbusUnitID, Idm::RegisterList::RoomTemperatureTargetHeatingEcoHKA, value);
}
void Idm::onReceivedHoldingRegister(int slaveAddress, int modbusRegister, const QVector<quint16> &value)
{
Q_UNUSED(slaveAddress);
/* qCDebug(dcIdm()) << "receivedHoldingRegister " << modbusRegister << "(length: " << value.length() << ")"; */
switch (modbusRegister) {
case Idm::OutsideTemperature:
/* qCDebug(dcIdm()) << "received outside temperature"; */
if (value.length() == 2) {
m_idmInfo.outsideTemperature = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::OutsideTemperature - modbusRegister]);
}
QTimer::singleShot(200, this, [this] {
m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::CurrentFaultNumber, 1);
});
break;
case Idm::CurrentFaultNumber:
/* qCDebug(dcIdm()) << "current fault number"; */
if (value.length() == 1) {
if (value[0] > 0) {
m_idmInfo.error = true;
} else {
m_idmInfo.error = false;
}
}
QTimer::singleShot(200, this, [this] {
m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::HeatStorageTemperature, 2);
});
break;
case Idm::HeatStorageTemperature:
/* qCDebug(dcIdm()) << "received storage temperature"; */
if (value.length() == 2) {
m_idmInfo.waterTemperature = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::HeatStorageTemperature - modbusRegister]);
}
QTimer::singleShot(200, this, [this] {
m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::TargetHotWaterTemperature, 1);
});
break;
case Idm::TargetHotWaterTemperature:
/* qCDebug(dcIdm()) << "received target hot water temperature"; */
if (value.length() == 1) {
/* The hot water target temperature is stored as UCHAR (manual p. 13) */
m_idmInfo.targetWaterTemperature = (double)value[RegisterList::TargetHotWaterTemperature - modbusRegister];
}
QTimer::singleShot(200, this, [this] {
m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::HeatPumpOperatingMode, 1);
});
break;
case Idm::HeatPumpOperatingMode:
/* qCDebug(dcIdm()) << "received heat pump operating mode"; */
if (value.length() == 1) {
m_idmInfo.mode = heatPumpOperationModeToString((Idm::IdmHeatPumpMode)value[RegisterList::HeatPumpOperatingMode-modbusRegister]);
}
QTimer::singleShot(200, this, [this] {
m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::RoomTemperatureHKA, 2);
});
break;
case Idm::RoomTemperatureHKA:
/* qCDebug(dcIdm()) << "received room temperature hka"; */
if (value.length() == 2) {
m_idmInfo.roomTemperature = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::RoomTemperatureHKA - modbusRegister]);
}
QTimer::singleShot(200, this, [this] {
m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::RoomTemperatureTargetHeatingEcoHKA, 2);
});
break;
case Idm::RoomTemperatureTargetHeatingEcoHKA:
/* qCDebug(dcIdm()) << "received room temprature hka eco"; */
if (value.length() == 2) {
m_idmInfo.targetRoomTemperature = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::RoomTemperatureTargetHeatingEcoHKA - modbusRegister]);
}
QTimer::singleShot(200, this, [this] {
m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::CurrentPowerConsumptionHeatPump, 2);
});
break;
case Idm::CurrentPowerConsumptionHeatPump:
/* qCDebug(dcIdm()) << "received power consumption heat pump"; */
if (value.length() == 2) {
m_idmInfo.powerConsumptionHeatPump = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::CurrentPowerConsumptionHeatPump - modbusRegister]);
}
/* Everything read without an error
* -> set connected to true */
m_idmInfo.connected = true;
emit statusUpdated(m_idmInfo);
break;
}
}
void Idm::onModbusError()
{
qCDebug(dcIdm()) << "iDM: Received modbus error" << m_modbusMaster->errorString();
m_idmInfo.connected = false;
emit statusUpdated(m_idmInfo);
}
QString Idm::heatPumpOperationModeToString(IdmHeatPumpMode mode)
{
QString result{};
/* Operation modes according to table of manual p. 14 */
switch (mode) {
case IdmHeatPumpModeOff:
result = "Off";
break;
case IdmHeatPumpModeHeating:
result = "Heating";
break;
case IdmHeatPumpModeCooling:
result = "Cooling";
break;
case IdmHeatPumpModeHotWater:
result = "Hot water";
break;
case IdmHeatPumpModeDefrost:
result = "Defrost";
break;
}
return result;
}

183
idm/idm.h Normal file
View File

@ -0,0 +1,183 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef IDM_H
#define IDM_H
#include <QObject>
#include "../modbus/modbustcpmaster.h"
#include "idminfo.h"
/*
* Functionality:
* The current version allows read access to selected
* modbus registers:
* + Room temperature (HK A)
* + Water temperature
* + Outside air temperature
* + Target room temperature eco mode (HK A)
* + Target water temperature
* + Current power consumption
* + Operation mode
* + Error
*
* At present there is no write access for target
* room temperature and target water temperature. These
* result in an "invalid data access" error from the
* device.
*/
/* Note: It would be desirable to read the modbus registers
* of the Idm heat pump in groups to minimize the number
* of read requests. However, a maximum of 6 registers
* can be read simultaneously. With the given set of
* addresses it is not possible to reasonably group the
* registers, therefore they are read individually.
*/
class Idm : public QObject
{
Q_OBJECT
public:
explicit Idm(const QHostAddress &address, QObject *parent = nullptr);
~Idm();
bool connectDevice();
QHostAddress getIdmAddress() const;
QUuid setTargetTemperature(double targetTemperature);
void getStatus();
private:
/** Modbus Unit ID of Idm device */
static const quint16 modbusUnitID = 1;
enum IscModus {
KeineAbwarme = 0,
Heizung = 1,
Warmwasser = 4,
Warmequelle = 8,
};
/** System operation modes according to manual p. 13 */
enum IdmSysMode {
IdmSysModeStandby = 0,
IdmSysModeAutomatic = 1,
IdmSysModeAway = 2,
IdmSysModeOnlyHotwater = 4,
IdmSysModeOnlyRoomHeating = 5
};
/** Heat pump operation modes according to manual p. 14 */
enum IdmHeatPumpMode {
IdmHeatPumpModeOff = 0,
IdmHeatPumpModeHeating = 1,
IdmHeatPumpModeCooling = 2,
IdmHeatPumpModeHotWater = 4,
IdmHeatPumpModeDefrost = 8
};
/** The following modbus addresses are according to the manual
* Modbus TCP Navigatorregelung 2.0 pages 13-31.
* Comments at the end of each line give their original name
* in the German manual. */
enum RegisterList {
OutsideTemperature = 1000, // Außentemperatur (B31)
MeanOutsideTemperature = 1002, // Gemittelte Außentemperature
CurrentFaultNumber = 1004, // Aktuelle Störungsnummer
OperationModeSystem = 1005, // Betriebsart System
SmartGridStatus = 1006, // Smart Grid Status
HeatStorageTemperature = 1008, // Wärmespeichertemperatur (B38)
ColdStorageTemperature = 1010, // Kältespeichertemperatur (B40)
DrinkingWaterHeaterTempBottom = 1012, // Trinkwassererwärmertmp. unten (B41)
DrinkingWaterHeaterTempTop = 1014, // Trinkwassererwärmertmp. oben (B48)
HotWaterTapTemperature = 1030, // Warmwasserzapftemperatur (B42)
TargetHotWaterTemperature = 1032, // Warmwasser-Solltemperatur
HeatPumpOperatingMode = 1090, // Betriebsart Wärmepumpe
SummationFaultHeatPump = 1099, // Summenstörung Wärepumpe
RoomTemperatureHKA = 1364, // Heizkreis A Raumtemperature (B61)
HumiditySensor = 1392, // Feuchtesensor
RoomTemperatureTargetHeatingHKA = 1401, // Raumsolltemperatur Heizen Normal HK A
RoomTemperatureTargetHeatingEcoHKA = 1415, // Raumsolltemperatur Heizen Eco HK A
ExternalOutsideTemperature = 1690, // Externe Außentemperatur
ExternalHumidity = 1692, // Externe Feuchte
ExternalRequestTemperatureHeating = 1694, // Externe Anforderungstemperatur Heizen
ExternalRequestTemperatureCooling = 1695, // Externe Anforderungstemperatur Kühlen
HeatingRequirement = 1710, // Anforderung Heizen
CoolingRequirement = 1711, // Anforderung Kühlen
HotWaterChargingRequirement = 1712, // Anforderung Warmwasserladung
HeatQuantityHeating = 1750, // Wärmemenge Heizen
HeatQuantityCooling = 1752, // Wärmemenge Kühlen
HeatQuantityHotWater = 1754, // Wärmemenge Warmwasser
HeatQuantityDefrosting = 1756, // Wärmemenge Abtauung
HeatQuantityPassiveCooling = 1758, // Wärmemenge Passive Kühlung,
HeatQuantityPhotovolatics = 1760, // Wärmemenge Solar
HeatQuantityHeatingElemetn = 1762, // Wärmemenge Elektroheizeinsatz,
CurrentPower = 1790, // Momentanleistung
CurrentPowerSolar = 1792, // MomentanleistungSolar
SolarCollectorTemperature = 1850, // SolarKollektortemperatur (B73)
SolarCollectorReturnTemperature = 1852, // SolarKollektorruecklauftemperatur (B75)
SolarChargeTemperature = 1854, // SolarLadetemperatur (B74)
SolarOperatingMode = 1856, // Betriebsart Solar
ISCMode = 1875, // ISCModus
AcknowledgeFaultMessages = 1999, // Störmeldungen quittieren
TargetRoomTemperatureZ1R1 = 2004, // Zonenmodul 1 Raumsolltemperatur Raum 1
CurrentPhotovoltaicsSurplus = 74, // Aktueller PV-Überschuss
CurrentPhotovoltaicsProduction = 78, // Aktueller PV Produktion
CurrentPowerConsumptionHeatPump = 4122, // Aktuelle Leistungsaufnahme Wärmepumpe
};
/* Note: This class only requires one IP address and one
* TCP Modbus connection. Multiple devices are managed
* within the IntegrationPluginIdm class. */
QHostAddress m_hostAddress;
/** Pointer to ModbusTCPMaster object, responsible for low-level communicaiton */
ModbusTCPMaster *m_modbusMaster = nullptr;
/** This structure is allocated within onRequestStatus and filled
* by the receivedStatusGroupx functions */
IdmInfo m_idmInfo;
/** Converts a heat pump operation mode code to a string (according to manual p. 14) */
QString heatPumpOperationModeToString(IdmHeatPumpMode mode);
signals:
void statusUpdated(const IdmInfo &info);
void targetRoomTemperatureChanged();
void writeRequestExecuted(const QUuid &requestId, bool success);
private slots:
void onModbusError();
void onReceivedHoldingRegister(int slaveAddress, int modbusRegister, const QVector<quint16> &value);
};
#endif // IDM_H

BIN
idm/idm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

19
idm/idm.pro Normal file
View File

@ -0,0 +1,19 @@
include(../plugins.pri)
QT += \
network \
serialbus \
SOURCES += \
idm.cpp \
integrationpluginidm.cpp \
../modbus/modbustcpmaster.cpp \
../modbus/modbushelpers.cpp \
HEADERS += \
idm.h \
idminfo.h \
integrationpluginidm.h \
../modbus/modbustcpmaster.h \
../modbus/modbushelpers.h \

79
idm/idminfo.h Normal file
View File

@ -0,0 +1,79 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef IDMINFO_H
#define IDMINFO_H
#include <QMetaType>
#include <QString>
/** This struct holds the status information that is read from the IDM device
* and passed to the nymea framework within this plugin.
*/
struct IdmInfo {
/** Set to true, if register values can be read,
* false in case of communication problems */
bool connected;
bool power;
/** RegisterList::OutsideTemperature */
double roomTemperature;
/** RegisterList::ExternalOutsideTemperature */
double outsideTemperature;
/** RegisterList::HeatStorageTemperature */
double waterTemperature;
/** RegisterList::TargetRoomTemperatureZ1R1 (zone 1, room 1) */
double targetRoomTemperature;
/** RegisterList::TargetHotWaterTemperature */
double targetWaterTemperature;
/** RegisterList::HumiditySensor */
double humidity;
/** RegisterList::CurrentPowerConsumptionHeatPump */
double powerConsumptionHeatPump;
/** RegisterList::OperationModeSystem */
QString mode;
/** True if there is an error code set
* (RegisterList::CurrentFaultNumber != 0) */
bool error;
};
Q_DECLARE_METATYPE(IdmInfo);
#endif

View File

@ -0,0 +1,214 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginidm.h"
#include "plugininfo.h"
IntegrationPluginIdm::IntegrationPluginIdm()
{
}
void IntegrationPluginIdm::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
qCDebug(dcIdm()) << "setupThing called" << thing->name();
if (thing->thingClassId() == navigator2ThingClassId) {
QHostAddress hostAddress = QHostAddress(thing->paramValue(navigator2ThingIpAddressParamTypeId).toString());
if (hostAddress.isNull()) {
qCWarning(dcIdm()) << "Setup failed, IP address not valid";
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("No IP address given"));
return;
}
if (m_idmConnections.contains(thing)) {
qCDebug(dcIdm()) << "Cleaning up after reconfiguration";
m_idmConnections.take(thing)->deleteLater();
}
qCDebug(dcIdm()) << "User entered address: " << hostAddress.toString();
/* Check, if address is already in use for another device */
Q_FOREACH (Idm *idm, m_idmConnections) {
if (hostAddress.isEqual(idm->getIdmAddress())) {
qCWarning(dcIdm()) << "Address already in use";
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("IP address already in use"));
return;
}
}
qCDebug(dcIdm()) << "Creating Idm object";
/* Create new Idm object and store it in hash table */
Idm *idm = new Idm(hostAddress, this);
if (idm->connectDevice()) {
qCWarning(dcIdm()) << "Could not connect to thing";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
connect(idm, &Idm::statusUpdated, info, [info, thing, idm, this] (const IdmInfo &idmInfo) {
if (idmInfo.connected) {
m_idmConnections.insert(thing, idm);
connect(idm, &Idm::statusUpdated, this, &IntegrationPluginIdm::onStatusUpdated);
connect(idm, &Idm::writeRequestExecuted, this, &IntegrationPluginIdm::onWriteRequestExecuted);
info->finish(Thing::ThingErrorNoError);
}
});
connect(idm, &Idm::destroyed, this, [thing, this] {m_idmConnections.remove(thing);});
connect(info, &ThingSetupInfo::aborted, idm, &Idm::deleteLater);
} else {
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
}
}
void IntegrationPluginIdm::postSetupThing(Thing *thing)
{
qCDebug(dcIdm()) << "postSetupThing called" << thing->name();
if (!m_refreshTimer) {
qCDebug(dcIdm()) << "Starting refresh timer";
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginIdm::onRefreshTimer);
}
if (thing->thingClassId() == navigator2ThingClassId) {
Idm *idm = m_idmConnections.value(thing);
if (!idm) {
qCWarning(dcIdm()) << "Could not find any iDM connection for" << thing->name();
return;
}
thing->setStateValue(navigator2ConnectedStateTypeId, true);
update(thing);
}
}
void IntegrationPluginIdm::thingRemoved(Thing *thing)
{
qCDebug(dcIdm()) << "thingRemoved called" << thing->name();
if (thing->thingClassId() == navigator2ThingClassId) {
if (m_idmConnections.contains(thing)) {
m_idmConnections.take(thing)->deleteLater();
}
}
if (myThings().isEmpty()) {
qCDebug(dcIdm()) << "Stopping refresh timer";
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
m_refreshTimer = nullptr;
}
}
void IntegrationPluginIdm::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
Action action = info->action();
if (thing->thingClassId() == navigator2ThingClassId) {
Idm *idm = m_idmConnections.value(thing);
if (!idm) {
return info->finish(Thing::ThingErrorHardwareFailure);
}
if (action.actionTypeId() == navigator2TargetTemperatureActionTypeId) {
double targetTemperature = thing->stateValue(navigator2TargetTemperatureStateTypeId).toDouble();
QUuid requestId = idm->setTargetTemperature(targetTemperature);
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {m_asyncActions.remove(requestId);});
} else {
Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8());
}
} else {
Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
}
}
void IntegrationPluginIdm::update(Thing *thing)
{
if (thing->thingClassId() == navigator2ThingClassId) {
qCDebug(dcIdm()) << "Updating thing" << thing->name();
Idm *idm = m_idmConnections.value(thing);
if (!idm) {
return;
}
idm->getStatus();
}
}
void IntegrationPluginIdm::onStatusUpdated(const IdmInfo &info)
{
qCDebug(dcIdm()) << "Received status from heat pump";
Idm *idm = qobject_cast<Idm *>(sender());
Thing *thing = m_idmConnections.key(idm);
if (!thing)
return;
/* Received a structure holding the status info of the
* heat pump. Update the thing states with the individual fields. */
thing->setStateValue(navigator2ConnectedStateTypeId, info.connected);
thing->setStateValue(navigator2PowerStateTypeId, info.power);
thing->setStateValue(navigator2TemperatureStateTypeId, info.roomTemperature);
thing->setStateValue(navigator2OutsideAirTemperatureStateTypeId, info.outsideTemperature);
thing->setStateValue(navigator2WaterTemperatureStateTypeId, info.waterTemperature);
thing->setStateValue(navigator2TargetTemperatureStateTypeId, info.targetRoomTemperature);
thing->setStateValue(navigator2TargetWaterTemperatureStateTypeId, info.targetWaterTemperature);
thing->setStateValue(navigator2CurrentPowerConsumptionHeatPumpStateTypeId, info.powerConsumptionHeatPump);
thing->setStateValue(navigator2ModeStateTypeId, info.mode);
thing->setStateValue(navigator2ErrorStateTypeId, info.error);
}
void IntegrationPluginIdm::onWriteRequestExecuted(const QUuid &requestId, bool success)
{
if (m_asyncActions.contains(requestId)) {
ThingActionInfo *info = m_asyncActions.value(requestId);
if (success) {
info->finish(Thing::ThingErrorNoError);
} else {
info->finish(Thing::ThingErrorHardwareNotAvailable);
}
}
}
void IntegrationPluginIdm::onRefreshTimer()
{
qCDebug(dcIdm()) << "onRefreshTimer called";
foreach (Thing *thing, myThings().filterByThingClassId(navigator2ThingClassId)) {
update(thing);
}
}

View File

@ -0,0 +1,71 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef INTEGRATIONPLUGINIDM_H
#define INTEGRATIONPLUGINIDM_H
#include "integrations/integrationplugin.h"
#include "plugintimer.h"
#include "idm.h"
#include <QUuid>
class IntegrationPluginIdm: public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginidm.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginIdm();
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, Idm *> m_idmConnections;
QHash<QUuid, ThingActionInfo *> m_asyncActions;
void update(Thing *thing);
void onRefreshTimer();
private slots:
void onStatusUpdated(const IdmInfo &info);
void onWriteRequestExecuted(const QUuid &requestId, bool success);
};
#endif // INTEGRATIONPLUGINIDM_H

View File

@ -0,0 +1,135 @@
{
"name": "Idm",
"displayName": "iDM",
"id": "3968d86d-d51a-4ad1-a185-91faa017e38f",
"vendors": [
{
"name": "Idm",
"displayName": "iDM",
"id": "6f54e4b0-1057-4004-87a9-97fdf4581625",
"thingClasses": [
{
"name": "navigator2",
"displayName": "Navigator 2.0",
"id": "1c95ac91-4eca-4cbf-b0f4-d60d35d069ed",
"createMethods": ["user"],
"interfaces": ["thermostat", "connectable"],
"paramTypes": [
{
"id": "05714e5c-d66a-4095-bbff-a0eb96fb035b",
"name":"ipAddress",
"displayName": "IP address",
"type": "QString"
}
],
"stateTypes":[
{
"id": "cfd71e64-b666-45ef-8db0-8213acd82c5f",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "33c27167-8e24-4cc5-943c-d17cd03e0f68",
"name": "power",
"displayName": "Power",
"displayNameEvent": "Power changed",
"type": "bool",
"defaultValue": false
},
{
"id": "f0f596bf-7e45-43ea-b3d4-767b82dd422a",
"name": "temperature",
"displayName": "Room temperature",
"displayNameEvent": "Room temperature changed",
"type": "double",
"unit": "DegreeCelsius",
"defaultValue": 0
},
{
"id": "fcf8e97f-a672-407f-94ae-30df15b310f4",
"name": "waterTemperature",
"displayName": "Water temperature",
"displayNameEvent": "Water temperature changed",
"type": "double",
"unit": "DegreeCelsius",
"defaultValue": 0
},
{
"id": "9f3462c2-7c42-4eeb-afc4-092e1e41a25d",
"name": "outsideAirTemperature",
"displayName": "Outside air temperature",
"displayNameEvent": "Outside air temperature changed",
"type": "double",
"unit": "DegreeCelsius",
"defaultValue": 0
},
{
"id": "efae7493-68c3-4cb9-853c-81011bdf09ca",
"name": "targetTemperature",
"displayName": "Target room temperature",
"displayNameAction": "Set target room temperature",
"displayNameEvent": "Target room temperature changed",
"type": "double",
"unit": "DegreeCelsius",
"minValue": "18",
"maxValue": "28",
"defaultValue": 22.00,
"writable": true
},
{
"id": "746244d6-dd37-4af8-b2ae-a7d8463e51e2",
"name": "targetWaterTemperature",
"displayName": "Target water temperature",
"displayNameEvent": "Target water temperature changed",
"type": "double",
"unit": "DegreeCelsius",
"defaultValue": 0.00
},
{
"id": "b98fb325-100d-4eae-bf8d-97e8f7e1eb00",
"name": "currentPowerConsumptionHeatPump",
"displayName": "Current power consumption",
"displayNameEvent": "Current power consumption heat pump changed",
"displayNameAction": "Change current power consumption het pump",
"type": "double",
"unit": "KiloWatt",
"defaultValue": 0.00
},
{
"id": "e539366b-44da-4119-b11b-497bcdb1f522",
"name": "mode",
"displayName": "Mode",
"displayNameEvent": "Mode changed",
"type": "QString",
"defaultValue": "Off",
"possibleValues": [
"Off",
"Heating",
"Cooling",
"Hot water",
"Defrost"
]
},
{
"id": "49fd83ee-ddf3-4477-9ee4-e01c53283b43",
"name": "error",
"displayName": "Error",
"displayNameEvent": "Error changed",
"type": "bool",
"defaultValue": false
}
]
}
]
}
]
}

13
idm/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "iDM",
"tagline": "Control iDM network enabled heat pumps.",
"icon": "idm.png",
"stability": "consumer",
"offline": true,
"technologies": [
"network"
],
"categories": [
"heating"
]
}

View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>Idm</name>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="61"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="64"/>
<source>Connected</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: connected, ID: {cfd71e64-b666-45ef-8db0-8213acd82c5f})
----------
The name of the StateType ({cfd71e64-b666-45ef-8db0-8213acd82c5f}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="67"/>
<source>Connected changed</source>
<extracomment>The name of the EventType ({cfd71e64-b666-45ef-8db0-8213acd82c5f}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="70"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="73"/>
<source>Current power consumption</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: currentPowerConsumptionHeatPump, ID: {b98fb325-100d-4eae-bf8d-97e8f7e1eb00})
----------
The name of the StateType ({b98fb325-100d-4eae-bf8d-97e8f7e1eb00}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="76"/>
<source>Current power consumption heat pump changed</source>
<extracomment>The name of the EventType ({b98fb325-100d-4eae-bf8d-97e8f7e1eb00}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="79"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="82"/>
<source>Error</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: error, ID: {49fd83ee-ddf3-4477-9ee4-e01c53283b43})
----------
The name of the StateType ({49fd83ee-ddf3-4477-9ee4-e01c53283b43}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="85"/>
<source>Error changed</source>
<extracomment>The name of the EventType ({49fd83ee-ddf3-4477-9ee4-e01c53283b43}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="88"/>
<source>IP address</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, Type: thing, ID: {05714e5c-d66a-4095-bbff-a0eb96fb035b})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="91"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="94"/>
<source>Mode</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: mode, ID: {e539366b-44da-4119-b11b-497bcdb1f522})
----------
The name of the StateType ({e539366b-44da-4119-b11b-497bcdb1f522}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="97"/>
<source>Mode changed</source>
<extracomment>The name of the EventType ({e539366b-44da-4119-b11b-497bcdb1f522}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="100"/>
<source>Navigator 2.0</source>
<extracomment>The name of the ThingClass ({1c95ac91-4eca-4cbf-b0f4-d60d35d069ed})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="103"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="106"/>
<source>Outside air temperature</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: outsideAirTemperature, ID: {9f3462c2-7c42-4eeb-afc4-092e1e41a25d})
----------
The name of the StateType ({9f3462c2-7c42-4eeb-afc4-092e1e41a25d}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="109"/>
<source>Outside air temperature changed</source>
<extracomment>The name of the EventType ({9f3462c2-7c42-4eeb-afc4-092e1e41a25d}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="112"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="115"/>
<source>Power</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: power, ID: {33c27167-8e24-4cc5-943c-d17cd03e0f68})
----------
The name of the StateType ({33c27167-8e24-4cc5-943c-d17cd03e0f68}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="118"/>
<source>Power changed</source>
<extracomment>The name of the EventType ({33c27167-8e24-4cc5-943c-d17cd03e0f68}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="121"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="124"/>
<source>Room temperature</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: temperature, ID: {f0f596bf-7e45-43ea-b3d4-767b82dd422a})
----------
The name of the StateType ({f0f596bf-7e45-43ea-b3d4-767b82dd422a}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="127"/>
<source>Room temperature changed</source>
<extracomment>The name of the EventType ({f0f596bf-7e45-43ea-b3d4-767b82dd422a}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="130"/>
<source>Set target room temperature</source>
<extracomment>The name of the ActionType ({efae7493-68c3-4cb9-853c-81011bdf09ca}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="133"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="136"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="139"/>
<source>Target room temperature</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, ActionType: targetTemperature, ID: {efae7493-68c3-4cb9-853c-81011bdf09ca})
----------
The name of the ParamType (ThingClass: navigator2, EventType: targetTemperature, ID: {efae7493-68c3-4cb9-853c-81011bdf09ca})
----------
The name of the StateType ({efae7493-68c3-4cb9-853c-81011bdf09ca}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="142"/>
<source>Target room temperature changed</source>
<extracomment>The name of the EventType ({efae7493-68c3-4cb9-853c-81011bdf09ca}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="145"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="148"/>
<source>Target water temperature</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: targetWaterTemperature, ID: {746244d6-dd37-4af8-b2ae-a7d8463e51e2})
----------
The name of the StateType ({746244d6-dd37-4af8-b2ae-a7d8463e51e2}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="151"/>
<source>Target water temperature changed</source>
<extracomment>The name of the EventType ({746244d6-dd37-4af8-b2ae-a7d8463e51e2}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="154"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="157"/>
<source>Water temperature</source>
<extracomment>The name of the ParamType (ThingClass: navigator2, EventType: waterTemperature, ID: {fcf8e97f-a672-407f-94ae-30df15b310f4})
----------
The name of the StateType ({fcf8e97f-a672-407f-94ae-30df15b310f4}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="160"/>
<source>Water temperature changed</source>
<extracomment>The name of the EventType ({fcf8e97f-a672-407f-94ae-30df15b310f4}) of ThingClass navigator2</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="163"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/idm/plugininfo.h" line="166"/>
<source>iDM</source>
<extracomment>The name of the vendor ({6f54e4b0-1057-4004-87a9-97fdf4581625})
----------
The name of the plugin Idm ({3968d86d-d51a-4ad1-a185-91faa017e38f})</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>IntegrationPluginIdm</name>
<message>
<location filename="../integrationpluginidm.cpp" line="50"/>
<source>No IP address given</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginidm.cpp" line="65"/>
<source>IP address already in use</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

61
modbus/modbushelpers.cpp Normal file
View File

@ -0,0 +1,61 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "modbushelpers.h"
float ModbusHelpers::convertRegisterToFloat(const quint16 *reg) {
float result = 0.0;
if (reg != nullptr) {
/* low-order byte is sent first, so swap order */
quint32 tmp = 0;
tmp |= ((quint32)(reg[1]) << 16) & 0xFFFF0000;
tmp |= reg[0];
/* copy value over to float variable without any conversion */
/* needs to be done with char * to avoid pedantic compiler errors */
memcpy((char *)&result, (char *)&tmp, sizeof(result));
}
return result;
}
QVector<quint16> ModbusHelpers::convertFloatToRegister(float value)
{
quint32 tmp = 0;
memcpy((char *)&tmp, (char *)&value, sizeof(value));
QVector<quint16> reg;
reg.append((quint16)(tmp));
reg.append((quint16)((tmp & 0xFFFF0000) >> 16));
return reg;
}

44
modbus/modbushelpers.h Normal file
View File

@ -0,0 +1,44 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef MODBUSHELPERS_H
#define MODBUSHELPERS_H
#include <QtGlobal>
#include <QVector>
class ModbusHelpers {
public:
static float convertRegisterToFloat(const quint16 *reg);
static QVector<quint16> convertFloatToRegister(float value);
};
#endif

View File

@ -54,7 +54,6 @@ ModbusRTUMaster::ModbusRTUMaster(QString serialPort, uint baudrate, QSerialPort:
connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusRTUMaster::onReconnectTimer);
}
ModbusRTUMaster::~ModbusRTUMaster()
{
if (!m_modbusRtuSerialMaster) {

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -29,6 +29,7 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "modbustcpmaster.h"
#include <loggingcategories.h>
NYMEA_LOGGING_CATEGORY(dcModbusTCP, "ModbusTCP")
@ -62,7 +63,7 @@ ModbusTCPMaster::~ModbusTCPMaster()
}
bool ModbusTCPMaster::connectDevice() {
// TCP connction to target device
// TCP connection to target device
qCDebug(dcModbusTCP()) << "Setting up TCP connecion";
if (!m_modbusTcpClient)
@ -86,6 +87,16 @@ void ModbusTCPMaster::setTimeout(int timeout)
m_modbusTcpClient->setTimeout(timeout);
}
QString ModbusTCPMaster::errorString() const
{
return m_modbusTcpClient->errorString();
}
QModbusDevice::Error ModbusTCPMaster::error() const
{
return m_modbusTcpClient->error();
}
uint ModbusTCPMaster::port()
{
return m_modbusTcpClient->connectionParameter(QModbusDevice::NetworkPortParameter).toUInt();
@ -130,20 +141,20 @@ QUuid ModbusTCPMaster::readCoil(uint slaveAddress, uint registerAddress, uint si
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
if (reply->error() == QModbusDevice::NoError) {
writeRequestExecuted(requestId, true);
emit readRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedCoil(reply->serverAddress(), modbusAddress, unit.values());
} else {
writeRequestExecuted(requestId, false);
emit readRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
}
});
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
emit writeRequestError(requestId, reply->errorString());
emit readRequestError(requestId, reply->errorString());
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(200, reply, &QModbusReply::deleteLater);
@ -217,13 +228,13 @@ QUuid ModbusTCPMaster::readDiscreteInput(uint slaveAddress, uint registerAddress
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
if (reply->error() == QModbusDevice::NoError) {
writeRequestExecuted(requestId, true);
emit readRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.values());
} else {
writeRequestExecuted(requestId, false);
emit readRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
}
});
@ -231,7 +242,7 @@ QUuid ModbusTCPMaster::readDiscreteInput(uint slaveAddress, uint registerAddress
qCWarning(dcModbusTCP()) << "Modbus replay error:" << error;
QModbusReply *reply = qobject_cast<QModbusReply *>(sender());
emit writeRequestError(requestId, reply->errorString());
emit readRequestError(requestId, reply->errorString());
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
@ -261,20 +272,20 @@ QUuid ModbusTCPMaster::readInputRegister(uint slaveAddress, uint registerAddress
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
reply->deleteLater();
if (reply->error() == QModbusDevice::NoError) {
writeRequestExecuted(requestId, true);
emit readRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.values());
} else {
writeRequestExecuted(requestId, false);
emit readRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
}
});
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
emit writeRequestError(requestId, reply->errorString());
emit readRequestError(requestId, reply->errorString());
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
@ -304,21 +315,22 @@ QUuid ModbusTCPMaster::readHoldingRegister(uint slaveAddress, uint registerAddre
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
if (reply->error() == QModbusDevice::NoError) {
writeRequestExecuted(requestId, true);
emit writeRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.values());
} else {
writeRequestExecuted(requestId, false);
emit writeRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
emit readRequestError(requestId, reply->errorString());
}
reply->deleteLater();
});
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
qCWarning(dcModbusTCP()) << "Modbus replay error:" << error;
emit writeRequestError(requestId, reply->errorString());
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
emit readRequestError(requestId, reply->errorString());
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
@ -339,7 +351,7 @@ QUuid ModbusTCPMaster::writeCoil(uint slaveAddress, uint registerAddress, bool v
}
QUuid ModbusTCPMaster::writeCoils(uint slaveAddress, uint registerAddress, const QVector<quint16> &values)
{
{
if (!m_modbusTcpClient) {
return "";
}
@ -354,14 +366,14 @@ QUuid ModbusTCPMaster::writeCoils(uint slaveAddress, uint registerAddress, const
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] () {
if (reply->error() == QModbusDevice::NoError) {
writeRequestExecuted(requestId, true);
emit writeRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedCoil(reply->serverAddress(), modbusAddress, unit.values());
} else {
writeRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
emit writeRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Write response error:" << reply->error();
}
reply->deleteLater();
});

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -37,6 +37,8 @@
#include <QTimer>
#include <QUuid>
Q_DECLARE_LOGGING_CATEGORY(dcModbusTcp)
class ModbusTCPMaster : public QObject
{
Q_OBJECT
@ -49,6 +51,9 @@ public:
void setNumberOfRetries(int number);
void setTimeout(int timeout);
QString errorString() const;
QModbusDevice::Error error() const;
QUuid readCoil(uint slaveAddress, uint registerAddress, uint size = 1);
QUuid readDiscreteInput(uint slaveAddress, uint registerAddress, uint size = 1);
QUuid readInputRegister(uint slaveAddress, uint registerAddress, uint size = 1);
@ -81,6 +86,9 @@ signals:
void writeRequestExecuted(const QUuid &requestId, bool success);
void writeRequestError(const QUuid &requestId, const QString &error);
void readRequestExecuted(const QUuid &requestId, bool success);
void readRequestError(const QUuid &requestId, const QString &error);
void receivedCoil(uint slaveAddress, uint modbusRegister, const QVector<quint16> &values);
void receivedDiscreteInput(uint slaveAddress, uint modbusRegister, const QVector<quint16> &values);
void receivedHoldingRegister(uint slaveAddress, uint modbusRegister, const QVector<quint16> &values);

View File

@ -31,6 +31,10 @@
#include "integrationpluginmodbuscommander.h"
#include "plugininfo.h"
#include "hardwaremanager.h"
#include "hardware/modbus/modbusrtumaster.h"
#include "hardware/modbus/modbusrtuhardwareresource.h"
#include <QSerialPort>
IntegrationPluginModbusCommander::IntegrationPluginModbusCommander()
@ -39,8 +43,6 @@ IntegrationPluginModbusCommander::IntegrationPluginModbusCommander()
void IntegrationPluginModbusCommander::init()
{
connect(this, &IntegrationPluginModbusCommander::configValueChanged, this, &IntegrationPluginModbusCommander::onPluginConfigurationChanged);
m_slaveAddressParamTypeId.insert(coilThingClassId, coilThingSlaveAddressParamTypeId);
m_slaveAddressParamTypeId.insert(inputRegisterThingClassId, inputRegisterThingSlaveAddressParamTypeId);
m_slaveAddressParamTypeId.insert(discreteInputThingClassId, discreteInputThingSlaveAddressParamTypeId);
@ -62,46 +64,49 @@ void IntegrationPluginModbusCommander::init()
m_valueStateTypeId.insert(inputRegisterThingClassId, inputRegisterValueStateTypeId);
m_valueStateTypeId.insert(discreteInputThingClassId, discreteInputValueStateTypeId);
m_valueStateTypeId.insert(holdingRegisterThingClassId, holdingRegisterValueStateTypeId);
// Plugin configuration
connect(this, &IntegrationPluginModbusCommander::configValueChanged, this, &IntegrationPluginModbusCommander::onPluginConfigurationChanged);
// Modbus RTU hardware resource
connect(hardwareManager()->modbusRtuResource(), &ModbusRtuHardwareResource::modbusRtuMasterRemoved, this, [=](const QUuid &modbusUuid){
qCDebug(dcModbusCommander()) << "Modbus RTU master has been removed" << modbusUuid.toString();
// Check if there is any device using this resource
foreach (Thing *thing, m_modbusRtuMasters.keys()) {
if (m_modbusRtuMasters.value(thing)->modbusUuid() == modbusUuid) {
qCWarning(dcModbusCommander()) << "Hardware resource removed for" << thing << ". The thing will not be functional any more until a new resource has been configured for it.";
m_modbusRtuMasters.remove(thing);
thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
// Set all child things disconnected
foreach (Thing *childThing, myThings()) {
if (childThing->parentId() == thing->id()) {
thing->setStateValue(m_connectedStateTypeId[childThing->thingClassId()], false);
}
}
}
}
});
}
void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
{
ThingClassId thingClassId = info->thingClassId();
if (thingClassId == modbusRTUClientThingClassId) {
Q_FOREACH(QSerialPortInfo port, QSerialPortInfo::availablePorts()) {
qCDebug(dcModbusCommander()) << "Found serial port:" << port.systemLocation() << "manufacturer" << port.manufacturer() << "description" << port.description() << "serial number" << port.serialNumber();
if (port.isBusy()) {
qCDebug(dcModbusCommander()) << "Serial port ist busy, skipping.";
continue;
foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) {
qCDebug(dcModbusCommander()) << "Found RTU master resource" << modbusMaster;
if (modbusMaster->connected()) {
ParamList parameters;
ThingDescriptor thingDescriptor(thingClassId, "Modbus RTU master", modbusMaster->serialPort());
parameters.append(Param(modbusRTUClientThingModbusMasterUuidParamTypeId, modbusMaster->modbusUuid()));
thingDescriptor.setParams(parameters);
info->addThingDescriptor(thingDescriptor);
} else {
qCWarning(dcModbusCommander()) << "Found configured resource" << modbusMaster << "but it is not connected. Skipping.";
}
QString manufacturer = port.manufacturer();
if (manufacturer.isEmpty()) {
manufacturer = "unknown";
}
QString description = port.description()+" Manufacturer: "+port.manufacturer();
ThingDescriptor thingDescriptor(thingClassId, "Modbus RTU interface", description);
ParamList parameters;
QString serialPort = port.systemLocation();
QString serialnumber = port.serialNumber();
if (serialnumber.isEmpty()) {
serialnumber = port.manufacturer()+QString::number(port.productIdentifier(), 16);
}
qCDebug(dcModbusCommander()) << " - Serial number" << serialnumber;
Q_FOREACH (Thing *exisingThing, myThings().filterByParam(modbusRTUClientThingClassId)) {
thingDescriptor.setThingId(exisingThing->id());
// Rediscovery is broken because of a missing unique device id
// This is a workaround and doesnt work if multiple uart converters are attached.
// ThingDiscoveryInfo may be extended to distinquish between discovery and rediscovery
break;
}
parameters.append(Param(modbusRTUClientThingSerialPortParamTypeId, serialPort));
parameters.append(Param(modbusRTUClientThingSerialnumberParamTypeId, serialnumber));
thingDescriptor.setParams(parameters);
info->addThingDescriptor(thingDescriptor);
}
//FIXME missing info if it is a rediscovery
info->finish(Thing::ThingErrorNoError);
return;
} else if (thingClassId == discreteInputThingClassId) {
@ -112,7 +117,7 @@ void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptor(descriptor);
}
if (clientThing->thingClassId() == modbusRTUClientThingClassId) {
ThingDescriptor descriptor(thingClassId, "Discrete input", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString());
ThingDescriptor descriptor(thingClassId, "Discrete input", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toString());
descriptor.setParentId(clientThing->id());
info->addThingDescriptor(descriptor);
}
@ -128,7 +133,7 @@ void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptor(descriptor);
}
if (clientThing->thingClassId() == modbusRTUClientThingClassId) {
ThingDescriptor descriptor(thingClassId, "Coil", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString());
ThingDescriptor descriptor(thingClassId, "Coil", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toString());
descriptor.setParentId(clientThing->id());
info->addThingDescriptor(descriptor);
}
@ -143,7 +148,7 @@ void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptor(descriptor);
}
if (clientThing->thingClassId() == modbusRTUClientThingClassId) {
ThingDescriptor descriptor(thingClassId, "Holding register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString());
ThingDescriptor descriptor(thingClassId, "Holding register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toString());
descriptor.setParentId(clientThing->id());
info->addThingDescriptor(descriptor);
}
@ -159,7 +164,7 @@ void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptor(descriptor);
}
if (clientThing->thingClassId() == modbusRTUClientThingClassId) {
ThingDescriptor descriptor(thingClassId, "Input register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString());
ThingDescriptor descriptor(thingClassId, "Input register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toString());
descriptor.setParentId(clientThing->id());
info->addThingDescriptor(descriptor);
}
@ -215,74 +220,51 @@ void IntegrationPluginModbusCommander::setupThing(ThingSetupInfo *info)
}
});
connect(thing, &Thing::settingChanged, thing, [thing, modbusTCPMaster] (const ParamTypeId &paramTypeId, const QVariant &value) {
if (paramTypeId == modbusTCPClientSettingsNumberOfRetriesParamTypeId) {
qCDebug(dcModbusCommander()) << "Set number of retries" << thing->name() << value.toUInt();
modbusTCPMaster->setNumberOfRetries(value.toUInt());
} else if (paramTypeId == modbusTCPClientSettingsTimeoutParamTypeId) {
qCDebug(dcModbusCommander()) << "Set timeout " << thing->name() << value.toUInt();
modbusTCPMaster->setTimeout(value.toUInt());
}
});
if (paramTypeId == modbusTCPClientSettingsNumberOfRetriesParamTypeId) {
qCDebug(dcModbusCommander()) << "Set number of retries" << thing->name() << value.toUInt();
modbusTCPMaster->setNumberOfRetries(value.toUInt());
} else if (paramTypeId == modbusTCPClientSettingsTimeoutParamTypeId) {
qCDebug(dcModbusCommander()) << "Set timeout " << thing->name() << value.toUInt();
modbusTCPMaster->setTimeout(value.toUInt());
}
});
modbusTCPMaster->connectDevice();
} else if (thing->thingClassId() == modbusRTUClientThingClassId) {
QUuid modbusUuid = thing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toUuid();
QString serialPort = thing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString();
uint baudrate = thing->paramValue(modbusRTUClientThingBaudRateParamTypeId).toUInt();
uint stopBits = thing->paramValue(modbusRTUClientThingStopBitsParamTypeId).toUInt();
uint dataBits = thing->paramValue(modbusRTUClientThingDataBitsParamTypeId).toUInt();
uint numberOfRetries = thing->setting(modbusRTUClientSettingsNumberOfRetriesParamTypeId).toUInt();
uint timeout = thing->setting(modbusRTUClientSettingsTimeoutParamTypeId).toUInt();
QSerialPort::Parity parity = QSerialPort::Parity::NoParity;
QString parityString = thing->paramValue(modbusRTUClientThingParityParamTypeId).toString();
if (parityString.contains("No")) {
parity = QSerialPort::Parity::NoParity;
} else if (parityString.contains("Even")) {
parity = QSerialPort::Parity::EvenParity;
} else if (parityString.contains("Odd")) {
parity = QSerialPort::Parity::OddParity;
}
qCDebug(dcModbusCommander()) << "Setting up RTU client" << thing->name();
qCDebug(dcModbusCommander()) << " baud:" << baudrate;
qCDebug(dcModbusCommander()) << " stop bits:" << stopBits;
qCDebug(dcModbusCommander()) << " data bits:" << dataBits;
qCDebug(dcModbusCommander()) << " parity:" << parityString;
qCDebug(dcModbusCommander()) << " number of retries:" << numberOfRetries;
qCDebug(dcModbusCommander()) << " timeout:" << timeout;
if (m_modbusRTUMasters.contains(thing)) {
// In case of a rediscovery
m_modbusRTUMasters.take(thing)->deleteLater();
if (!hardwareManager()->modbusRtuResource()->available()) {
qCWarning(dcModbusCommander()) << "Cannot set up thing" << thing << ". The modbus RTU hardware resource is not available.";
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The modbus RTU hardware resource is not available"));
return;
}
ModbusRTUMaster *modbusRTUMaster = new ModbusRTUMaster(serialPort, baudrate, parity, dataBits, stopBits, this);
modbusRTUMaster->setTimeout(timeout);
modbusRTUMaster->setNumberOfRetries(numberOfRetries);
connect(modbusRTUMaster, &ModbusRTUMaster::connectionStateChanged, this, &IntegrationPluginModbusCommander::onConnectionStateChanged);
connect(modbusRTUMaster, &ModbusRTUMaster::requestExecuted, this, &IntegrationPluginModbusCommander::onRequestExecuted);
connect(modbusRTUMaster, &ModbusRTUMaster::requestError, this, &IntegrationPluginModbusCommander::onRequestError);
connect(modbusRTUMaster, &ModbusRTUMaster::receivedCoil, this, &IntegrationPluginModbusCommander::onReceivedCoil);
connect(modbusRTUMaster, &ModbusRTUMaster::receivedDiscreteInput, this, &IntegrationPluginModbusCommander::onReceivedDiscreteInput);
connect(modbusRTUMaster, &ModbusRTUMaster::receivedHoldingRegister, this, &IntegrationPluginModbusCommander::onReceivedHoldingRegister);
connect(modbusRTUMaster, &ModbusRTUMaster::receivedInputRegister, this, &IntegrationPluginModbusCommander::onReceivedInputRegister);
connect(modbusRTUMaster, &ModbusRTUMaster::connectionStateChanged, info, [info, modbusRTUMaster, this] (bool connected) {
if (connected) {
info->finish(Thing::ThingErrorNoError);
m_modbusRTUMasters.insert(info->thing(), modbusRTUMaster);
if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(modbusUuid)) {
qCWarning(dcModbusCommander()) << "Cannot set up thing" << thing << ". The modbus RTU hardware resource" << modbusUuid.toString() << "does not exist any more. Reconfiguration required.";
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Configured modbus RTU master could not be found. Please reconfigure the client and assign a new valid modbus RTU master."));
return;
}
ModbusRtuMaster *modbusMaster = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(modbusUuid);
qCDebug(dcModbusCommander()) << "Setting up" << thing << "using" << modbusMaster;
m_modbusRtuMasters.insert(thing, modbusMaster);
connect(modbusMaster, &ModbusRtuMaster::connectedChanged, thing, [=](bool connected){
qCDebug(dcModbusCommander()) << "Modbus RTU client" << modbusMaster << "connected changed" << connected;
thing->setStateValue(modbusRTUClientConnectedStateTypeId, connected);
// Note: only set the connected state for the child things if disconnected.
// The child things will be evaluated upon read requests if the slave is connected or not.
if (!connected) {
foreach (Thing *childThing, myThings()) {
if (childThing->parentId() == thing->id()) {
thing->setStateValue(m_connectedStateTypeId[childThing->thingClassId()], connected);
}
}
}
});
connect(thing, &Thing::settingChanged, thing, [thing, modbusRTUMaster] (const ParamTypeId &paramTypeId, const QVariant &value) {
if (paramTypeId == modbusRTUClientSettingsNumberOfRetriesParamTypeId) {
qCDebug(dcModbusCommander()) << "Set number of retries" << thing->name() << value.toUInt();
modbusRTUMaster->setNumberOfRetries(value.toUInt());
} else if (paramTypeId == modbusRTUClientSettingsTimeoutParamTypeId) {
qCDebug(dcModbusCommander()) << "Set timeout " << thing->name() << value.toUInt();
modbusRTUMaster->setTimeout(value.toUInt());
}
});
modbusRTUMaster->connectDevice();
info->finish(Thing::ThingErrorNoError);
} else if ((thing->thingClassId() == coilThingClassId)
|| (thing->thingClassId() == discreteInputThingClassId)
|| (thing->thingClassId() == holdingRegisterThingClassId)
@ -362,9 +344,6 @@ void IntegrationPluginModbusCommander::thingRemoved(Thing *thing)
if (thing->thingClassId() == modbusTCPClientThingClassId) {
ModbusTCPMaster *modbus = m_modbusTCPMasters.take(thing);
modbus->deleteLater();
} else if (thing->thingClassId() == modbusRTUClientThingClassId) {
ModbusRTUMaster *modbus = m_modbusRTUMasters.take(thing);
modbus->deleteLater();
}
if (myThings().empty()) {
@ -392,12 +371,7 @@ void IntegrationPluginModbusCommander::onPluginConfigurationChanged(const ParamT
void IntegrationPluginModbusCommander::onConnectionStateChanged(bool status)
{
auto modbus = sender();
if (m_modbusRTUMasters.values().contains(static_cast<ModbusRTUMaster *>(modbus))) {
Thing *thing = m_modbusRTUMasters.key(static_cast<ModbusRTUMaster *>(modbus));
qCDebug(dcModbusCommander()) << "Connections state changed" << thing->name() << status;
thing->setStateValue(modbusRTUClientConnectedStateTypeId, status);
} else if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
Thing *thing = m_modbusTCPMasters.key(static_cast<ModbusTCPMaster *>(modbus));
qCDebug(dcModbusCommander()) << "Connections state changed" << thing->name() << status;
thing->setStateValue(modbusTCPClientConnectedStateTypeId, status);
@ -439,20 +413,7 @@ void IntegrationPluginModbusCommander::onRequestError(QUuid requestId, const QSt
void IntegrationPluginModbusCommander::onReceivedCoil(quint32 slaveAddress, quint32 modbusRegister, const QVector<quint16> &values)
{
auto modbus = sender();
if (m_modbusRTUMasters.values().contains(static_cast<ModbusRTUMaster *>(modbus))) {
Thing *parent = m_modbusRTUMasters.key(static_cast<ModbusRTUMaster *>(modbus));
foreach (Thing *thing, myThings().filterByParentId(parent->id())) {
if (thing->thingClassId() == coilThingClassId) {
if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress)
&& (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) {
thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), values[0]);
thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
return;
}
}
}
} else if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
Thing *parent = m_modbusTCPMasters.key(static_cast<ModbusTCPMaster *>(modbus));
foreach (Thing *thing, myThings().filterByParentId(parent->id())) {
if (thing->thingClassId() == coilThingClassId) {
@ -471,19 +432,7 @@ void IntegrationPluginModbusCommander::onReceivedDiscreteInput(quint32 slaveAddr
{
auto modbus = sender();
if (m_modbusRTUMasters.values().contains(static_cast<ModbusRTUMaster *>(modbus))) {
Thing *parent = m_modbusRTUMasters.key(static_cast<ModbusRTUMaster *>(modbus));
foreach (Thing *thing, myThings().filterByParentId(parent->id())) {
if (thing->thingClassId() == discreteInputThingClassId) {
if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress)
&& (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) {
thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), values[0]);
thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
return;
}
}
}
} else if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
Thing *parent = m_modbusTCPMasters.key(static_cast<ModbusTCPMaster *>(modbus));
foreach (Thing *thing, myThings().filterByParentId(parent->id())) {
if (thing->thingClassId() == discreteInputThingClassId) {
@ -502,19 +451,7 @@ void IntegrationPluginModbusCommander::onReceivedHoldingRegister(uint slaveAddre
{
auto modbus = sender();
if (m_modbusRTUMasters.values().contains(static_cast<ModbusRTUMaster *>(modbus))) {
Thing *parent = m_modbusRTUMasters.key(static_cast<ModbusRTUMaster *>(modbus));
foreach (Thing *thing, myThings().filterByParentId(parent->id())) {
if (thing->thingClassId() == holdingRegisterThingClassId) {
if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress)
&& (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) {
thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), values[0]);
thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
return;
}
}
}
} else if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
Thing *parent = m_modbusTCPMasters.key(static_cast<ModbusTCPMaster *>(modbus));
foreach (Thing *thing, myThings().filterByParentId(parent->id())) {
if (thing->thingClassId() == holdingRegisterThingClassId) {
@ -533,19 +470,7 @@ void IntegrationPluginModbusCommander::onReceivedInputRegister(uint slaveAddress
{
auto modbus = sender();
if (m_modbusRTUMasters.values().contains(static_cast<ModbusRTUMaster *>(modbus))) {
Thing *parent = m_modbusRTUMasters.key(static_cast<ModbusRTUMaster *>(modbus));
foreach (Thing *thing, myThings().filterByParentId(parent->id())) {
if (thing->thingClassId() == inputRegisterThingClassId) {
if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress)
&& (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) {
thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), values[0]);
thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
return;
}
}
}
} else if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
if (m_modbusTCPMasters.values().contains(static_cast<ModbusTCPMaster *>(modbus))) {
Thing *parent = m_modbusTCPMasters.key(static_cast<ModbusTCPMaster *>(modbus));
foreach (Thing *thing, myThings().filterByParentId(parent->id())) {
if (thing->thingClassId() == inputRegisterThingClassId) {
@ -592,23 +517,76 @@ void IntegrationPluginModbusCommander::readRegister(Thing *thing)
}
} else if (parent->thingClassId() == modbusRTUClientThingClassId) {
ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parent);
if (!modbus)
ModbusRtuMaster *modbusMaster = m_modbusRtuMasters.value(parent);
if (!modbusMaster)
return;
if (!modbus->connected())
if (!modbusMaster->connected())
return; // Send requests only if the modbus interface is connected
if (thing->thingClassId() == coilThingClassId) {
requestId = modbus->readCoil(slaveAddress, registerAddress);
ModbusRtuReply *reply = modbusMaster->readCoil(slaveAddress, registerAddress);
connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
if (reply->error() != ModbusRtuReply::NoError) {
qCWarning(dcModbusCommander()) << "Failed to read coil from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress;
thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
return;
}
if (!reply->result().isEmpty()) {
thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), reply->result().at(0));
}
thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
});
} else if (thing->thingClassId() == discreteInputThingClassId) {
requestId = modbus->readDiscreteInput(slaveAddress, registerAddress);
ModbusRtuReply *reply = modbusMaster->readDiscreteInput(slaveAddress, registerAddress);
connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
if (reply->error() != ModbusRtuReply::NoError) {
qCWarning(dcModbusCommander()) << "Failed to read discrete input from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress;
thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
return;
}
if (!reply->result().isEmpty()) {
thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), reply->result().at(0));
}
thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
});
} else if (thing->thingClassId() == holdingRegisterThingClassId) {
requestId = modbus->readHoldingRegister(slaveAddress, registerAddress);
ModbusRtuReply *reply = modbusMaster->readHoldingRegister(slaveAddress, registerAddress);
connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
if (reply->error() != ModbusRtuReply::NoError) {
qCWarning(dcModbusCommander()) << "Failed to read holding register from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress;
thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
return;
}
if (!reply->result().isEmpty()) {
thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), reply->result().at(0));
}
thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
});
} else if (thing->thingClassId() == inputRegisterThingClassId) {
requestId = modbus->readInputRegister(slaveAddress, registerAddress);
ModbusRtuReply *reply = modbusMaster->readInputRegister(slaveAddress, registerAddress);
connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
if (reply->error() != ModbusRtuReply::NoError) {
qCWarning(dcModbusCommander()) << "Failed to read input register from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress;
thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
return;
}
if (!reply->result().isEmpty()) {
thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), reply->result().at(0));
}
thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
});
}
// Note: we don't want proceed with the method here, since we are not
// working with the requestId any more on RTU
return;
}
if (!requestId.isNull()) {
m_readRequests.insert(requestId, thing);
QTimer::singleShot(5000, this, [requestId, this] {m_readRequests.remove(requestId);});
@ -623,8 +601,10 @@ void IntegrationPluginModbusCommander::writeRegister(Thing *thing, ThingActionIn
Thing *parent = myThings().findById(thing->parentId());
if (!parent) {
qCWarning(dcModbusCommander()) << "Could not find parent device" << thing->name();
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
uint registerAddress = thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())).toUInt();;
uint slaveAddress = thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())).toUInt();
@ -633,8 +613,11 @@ void IntegrationPluginModbusCommander::writeRegister(Thing *thing, ThingActionIn
if (parent->thingClassId() == modbusTCPClientThingClassId) {
ModbusTCPMaster *modbus = m_modbusTCPMasters.value(parent);
if (!modbus)
if (!modbus) {
qCWarning(dcModbusCommander()) << "Could not find modbus TCP master for" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (thing->thingClassId() == coilThingClassId) {
requestId = modbus->writeCoil(slaveAddress, registerAddress, action.param(coilValueActionValueParamTypeId).value().toBool());
@ -643,15 +626,48 @@ void IntegrationPluginModbusCommander::writeRegister(Thing *thing, ThingActionIn
}
} else if (parent->thingClassId() == modbusRTUClientThingClassId) {
ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parent);
if (!modbus)
ModbusRtuMaster *modbusMaster = m_modbusRtuMasters.value(parent);
if (!modbusMaster) {
qCWarning(dcModbusCommander()) << "Could not find modbus RTU master for" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (thing->thingClassId() == coilThingClassId) {
requestId = modbus->writeCoil(slaveAddress, registerAddress, action.param(coilValueActionValueParamTypeId).value().toBool());
QVector<quint16> values;
values.append(static_cast<quint16>(action.param(coilValueActionValueParamTypeId).value().toBool()));
ModbusRtuReply *reply = modbusMaster->writeCoils(slaveAddress, registerAddress, values);
connect(info, &ThingActionInfo::aborted, reply, &ModbusRtuReply::deleteLater);
connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
if (reply->error() != ModbusRtuReply::NoError) {
qCWarning(dcModbusCommander()) << "Failed to write coils from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress << values << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
info->finish(Thing::ThingErrorNoError);
});
} else if (thing->thingClassId() == holdingRegisterThingClassId) {
requestId = modbus->writeHoldingRegister(slaveAddress, registerAddress, action.param(holdingRegisterValueActionValueParamTypeId).value().toUInt());
QVector<quint16> values;
values.append(static_cast<quint16>(action.param(holdingRegisterValueActionValueParamTypeId).value().toUInt()));
ModbusRtuReply *reply = modbusMaster->writeHoldingRegisters(slaveAddress, registerAddress, values);
connect(info, &ThingActionInfo::aborted, reply, &ModbusRtuReply::deleteLater);
connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
if (reply->error() != ModbusRtuReply::NoError) {
qCWarning(dcModbusCommander()) << "Failed to write holding registers from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress << values << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
info->finish(Thing::ThingErrorNoError);
});
}
// Note: we don't want proceed with the method here, since we are not
// working with the requestId any more on RTU
return;
}
if (requestId.toString().isNull()){

View File

@ -31,11 +31,11 @@
#ifndef INTEGRATIONPLUGINMODBUSCOMMANDER_H
#define INTEGRATIONPLUGINMODBUSCOMMANDER_H
#include "integrations/integrationplugin.h"
#include "plugintimer.h"
#include "integrations/integrationplugin.h"
#include "hardware/modbus/modbusrtumaster.h"
#include "../modbus/modbustcpmaster.h"
#include "../modbus/modbusrtumaster.h"
#include <QSerialPortInfo>
#include <QUuid>
@ -60,8 +60,9 @@ public:
private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing*, ModbusRTUMaster*> m_modbusRTUMasters;
//QHash<Thing*, ModbusRTUMaster*> m_modbusRTUMasters;
QHash<Thing*, ModbusTCPMaster*> m_modbusTCPMasters;
QHash<Thing *, ModbusRtuMaster *> m_modbusRtuMasters;
QHash<QUuid, ThingActionInfo*> m_asyncActions;
QHash<QUuid, Thing*> m_readRequests;

View File

@ -72,74 +72,15 @@
"id": "776df314-6186-4eb5-b824-f0d916f6d9c3",
"name": "modbusRTUClient",
"displayName": "Modbus RTU client",
"createMethods": ["discovery", "user"],
"createMethods": ["discovery"],
"interfaces": ["connectable"],
"settingsTypes": [
{
"id": "b0af32f0-b8cc-4642-af5a-576732522b2c",
"name": "timeout",
"displayName": "Timeout",
"type": "uint",
"minValue": 10,
"defaultValue": 100
},
{
"id": "c4f16d6c-c1f2-4862-b0bd-6fae7193eaa8",
"name": "numberOfRetries",
"displayName": "Number of retries",
"type": "uint",
"defaultValue": 3
}
],
"paramTypes": [
{
"id": "ed49f7d8-ab18-4c37-9b80-1004b75dcb91",
"name": "serialPort",
"displayName": "Serial port",
"type": "QString",
"inputType": "TextLine",
"defaultValue": "ttyAMA0"
},
{
"id": "9908b01f-a76b-4b21-8242-b507c9252254",
"name": "serialnumber",
"displayName": "Serial number",
"type": "QString",
"name": "modbusMasterUuid",
"displayName": "Modbus RTU master",
"type": "QUuid",
"defaultValue": ""
},
{
"id": "45dfc828-f238-4263-89a3-9b35cf5dea39",
"name": "baudRate",
"displayName": "Baud rate",
"type": "uint",
"defaultValue": 9600
},
{
"id": "a27c664b-9f43-4573-a2cc-f65a8fa1a069",
"name": "dataBits",
"displayName": "Data bits",
"type": "uint",
"defaultValue": 8
},
{
"id": "4ea8bcdf-d4c5-45a4-a54f-f10ac3f08a78",
"name": "stopBits",
"displayName": "Stop bits",
"type": "uint",
"defaultValue": 1
},
{
"id": "72de1b08-2a27-49c5-90e0-8788c3ea1da3",
"name": "parity",
"displayName": "Parity",
"type": "QString",
"inputType": "TextLine",
"allowedValues": [
"No Parity",
"Even Parity",
"Odd Parity"
],
"defaultValue": "No Parity"
}
],
"stateTypes": [

View File

@ -7,11 +7,9 @@ QT += \
SOURCES += \
integrationpluginmodbuscommander.cpp \
../modbus/modbustcpmaster.cpp \
../modbus/modbusrtumaster.cpp \
../modbus/modbustcpmaster.cpp
HEADERS += \
integrationpluginmodbuscommander.h \
../modbus/modbustcpmaster.h \
../modbus/modbusrtumaster.h \
../modbus/modbustcpmaster.h

View File

@ -6,6 +6,7 @@ PLUGIN_DIRS = \
mypv \
sunspec \
unipi \
idm \
wallbe \
webasto \