Merge PR #7: New Plugin: iDM
This commit is contained in:
commit
96e079a05e
16
debian/control
vendored
16
debian/control
vendored
@ -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
1
debian/nymea-plugin-idm.install.in
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginidm.so
|
||||
18
idm/README.md
Normal file
18
idm/README.md
Normal 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
198
idm/idm.cpp
Normal 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
183
idm/idm.h
Normal 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
BIN
idm/idm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
19
idm/idm.pro
Normal file
19
idm/idm.pro
Normal 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
79
idm/idminfo.h
Normal 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
|
||||
|
||||
214
idm/integrationpluginidm.cpp
Normal file
214
idm/integrationpluginidm.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
71
idm/integrationpluginidm.h
Normal file
71
idm/integrationpluginidm.h
Normal 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
|
||||
|
||||
135
idm/integrationpluginidm.json
Normal file
135
idm/integrationpluginidm.json
Normal 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
13
idm/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
200
idm/translations/3968d86d-d51a-4ad1-a185-91faa017e38f-en_US.ts
Normal file
200
idm/translations/3968d86d-d51a-4ad1-a185-91faa017e38f-en_US.ts
Normal 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
61
modbus/modbushelpers.cpp
Normal 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
44
modbus/modbushelpers.h
Normal 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
|
||||
|
||||
@ -54,7 +54,6 @@ ModbusRTUMaster::ModbusRTUMaster(QString serialPort, uint baudrate, QSerialPort:
|
||||
connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusRTUMaster::onReconnectTimer);
|
||||
}
|
||||
|
||||
|
||||
ModbusRTUMaster::~ModbusRTUMaster()
|
||||
{
|
||||
if (!m_modbusRtuSerialMaster) {
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 ¶mTypeId, 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 ¶mTypeId, 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()){
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ PLUGIN_DIRS = \
|
||||
mypv \
|
||||
sunspec \
|
||||
unipi \
|
||||
idm \
|
||||
wallbe \
|
||||
webasto \
|
||||
|
||||
|
||||
Reference in New Issue
Block a user