added my-PV
This commit is contained in:
parent
59a9927f2c
commit
344239fe0d
16
debian/control
vendored
16
debian/control
vendored
@ -33,6 +33,22 @@ Description: nymea.io plugin for Drexel & Weiss heat pumps
|
|||||||
This package will install the nymea.io plugin for Drexel & Weiss heat pumps
|
This package will install the nymea.io plugin for Drexel & Weiss heat pumps
|
||||||
|
|
||||||
|
|
||||||
|
Package: nymea-plugin-mypv
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${shlibs:Depends},
|
||||||
|
${misc:Depends},
|
||||||
|
nymea-plugins-translations,
|
||||||
|
libmodbus,
|
||||||
|
Description: nymea.io plugin for my-pv heating rods
|
||||||
|
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 my-pv
|
||||||
|
|
||||||
|
|
||||||
Package: nymea-plugin-wallbe
|
Package: nymea-plugin-wallbe
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${shlibs:Depends},
|
Depends: ${shlibs:Depends},
|
||||||
|
|||||||
1
debian/nymea-plugin-mypv.install.in
vendored
Normal file
1
debian/nymea-plugin-mypv.install.in
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginmypv.so
|
||||||
@ -171,12 +171,10 @@ void DevicePluginDrexelUndWeiss::executeAction(DeviceActionInfo *info)
|
|||||||
discoverModbusSlaves(modbus, slave);
|
discoverModbusSlaves(modbus, slave);
|
||||||
info->finish(Device::DeviceErrorNoError);
|
info->finish(Device::DeviceErrorNoError);
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
info->finish(Device::DeviceErrorActionTypeNotFound);
|
||||||
}
|
}
|
||||||
info->finish(Device::DeviceErrorActionTypeNotFound);
|
} else if (device->deviceClassId() == x2luDeviceClassId) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device->deviceClassId() == x2luDeviceClassId) {
|
|
||||||
Device *parentDevice = myDevices().findById(device->parentId());
|
Device *parentDevice = myDevices().findById(device->parentId());
|
||||||
if (!parentDevice) {
|
if (!parentDevice) {
|
||||||
qWarning(dcDrexelUndWeiss()) << "Could not find the parent device";
|
qWarning(dcDrexelUndWeiss()) << "Could not find the parent device";
|
||||||
@ -210,12 +208,10 @@ void DevicePluginDrexelUndWeiss::executeAction(DeviceActionInfo *info)
|
|||||||
}
|
}
|
||||||
m_pendingActions.insert(modbus->writeHoldingRegister(slave, ModbusRegisterX2::Betriebsart, data), info);
|
m_pendingActions.insert(modbus->writeHoldingRegister(slave, ModbusRegisterX2::Betriebsart, data), info);
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
info->finish(Device::DeviceErrorActionTypeNotFound);
|
||||||
}
|
}
|
||||||
info->finish(Device::DeviceErrorActionTypeNotFound);
|
} else if (device->deviceClassId() == x2wpDeviceClassId) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device->deviceClassId() == x2wpDeviceClassId) {
|
|
||||||
Device *parentDevice = myDevices().findById(device->parentId());
|
Device *parentDevice = myDevices().findById(device->parentId());
|
||||||
if (!parentDevice) {
|
if (!parentDevice) {
|
||||||
qWarning(dcDrexelUndWeiss()) << "Could not find modbus interface";
|
qWarning(dcDrexelUndWeiss()) << "Could not find modbus interface";
|
||||||
@ -235,18 +231,17 @@ void DevicePluginDrexelUndWeiss::executeAction(DeviceActionInfo *info)
|
|||||||
int data = static_cast<int>(qRound(targetTemp * 1000));
|
int data = static_cast<int>(qRound(targetTemp * 1000));
|
||||||
m_pendingActions.insert(modbus->writeHoldingRegister(slave,ModbusRegisterX2::RaumSoll, data), info);
|
m_pendingActions.insert(modbus->writeHoldingRegister(slave,ModbusRegisterX2::RaumSoll, data), info);
|
||||||
return;
|
return;
|
||||||
}
|
} else if (action.actionTypeId() == x2wpTargetWaterTemperatureActionTypeId) {
|
||||||
if (action.actionTypeId() == x2wpTargetWaterTemperatureActionTypeId) {
|
|
||||||
qreal targetWaterTemp = action.param(x2wpTargetWaterTemperatureActionTargetWaterTemperatureParamTypeId).value().toDouble();
|
qreal targetWaterTemp = action.param(x2wpTargetWaterTemperatureActionTargetWaterTemperatureParamTypeId).value().toDouble();
|
||||||
int data = static_cast<int>(qRound(targetWaterTemp * 1000));
|
int data = static_cast<int>(qRound(targetWaterTemp * 1000));
|
||||||
m_pendingActions.insert(modbus->writeHoldingRegister(slave, ModbusRegisterX2::BrauchwasserSolltermperatur, data), info);
|
m_pendingActions.insert(modbus->writeHoldingRegister(slave, ModbusRegisterX2::BrauchwasserSolltermperatur, data), info);
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
info->finish(Device::DeviceErrorActionTypeNotFound);
|
||||||
}
|
}
|
||||||
info->finish(Device::DeviceErrorActionTypeNotFound);
|
} else {
|
||||||
return;
|
info->finish(Device::DeviceErrorDeviceClassNotFound);
|
||||||
}
|
}
|
||||||
info->finish(Device::DeviceErrorDeviceClassNotFound);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -51,8 +51,6 @@ public:
|
|||||||
void setupDevice(DeviceSetupInfo *info) override;
|
void setupDevice(DeviceSetupInfo *info) override;
|
||||||
void postSetupDevice(Device *device) override;
|
void postSetupDevice(Device *device) override;
|
||||||
void deviceRemoved(Device *device) override;
|
void deviceRemoved(Device *device) override;
|
||||||
|
|
||||||
public slots:
|
|
||||||
void executeAction(DeviceActionInfo *info) override;
|
void executeAction(DeviceActionInfo *info) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
5
mypv/README.md
Normal file
5
mypv/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# mypv
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Description of the plugin...
|
||||||
|
|
||||||
261
mypv/devicepluginmypv.cpp
Normal file
261
mypv/devicepluginmypv.cpp
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "plugininfo.h"
|
||||||
|
#include "devicepluginmypv.h"
|
||||||
|
|
||||||
|
#include <QUdpSocket>
|
||||||
|
#include <QHostAddress>
|
||||||
|
|
||||||
|
DevicePluginMyPv::DevicePluginMyPv()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DevicePluginMyPv::discoverDevices(DeviceDiscoveryInfo *info)
|
||||||
|
{
|
||||||
|
QUdpSocket *searchSocket = new QUdpSocket(this);
|
||||||
|
|
||||||
|
// Note: This will fail, and it's not a problem, but it is required to force the socket to stick to IPv4...
|
||||||
|
searchSocket->bind(QHostAddress::AnyIPv4, 16124);
|
||||||
|
|
||||||
|
QByteArray discoveryString;
|
||||||
|
discoveryString.resize(19);
|
||||||
|
discoveryString.fill(0);
|
||||||
|
discoveryString.insert(0, QByteArray::fromHex("86d93efc"));
|
||||||
|
|
||||||
|
discoveryString.insert(4, "AC ELWA-E");
|
||||||
|
qCDebug(dcMypv()) << "Send datagram:" << discoveryString << "length: " << discoveryString.length();
|
||||||
|
qint64 len = searchSocket->writeDatagram(discoveryString, QHostAddress("255.255.255.255"), 16124);
|
||||||
|
if (len != discoveryString.length()) {
|
||||||
|
searchSocket->deleteLater();
|
||||||
|
qCWarning(dcMypv()) << "Error sending discovery";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTimer::singleShot(2000, this, [this, searchSocket, info](){
|
||||||
|
QList<DeviceDescriptor> descriptorList;
|
||||||
|
while(searchSocket->hasPendingDatagrams()) {
|
||||||
|
char buffer[1024];
|
||||||
|
QHostAddress senderAddress;
|
||||||
|
int len = searchSocket->readDatagram(buffer, 1024, &senderAddress);
|
||||||
|
QByteArray data = QByteArray::fromRawData(buffer, len);
|
||||||
|
qCDebug(dcMypv()) << "Have datagram:" << data;
|
||||||
|
if (data.length() < 64) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Device Id AC•THOR = 0x4e84
|
||||||
|
//Device Id Power = 0x4e8e
|
||||||
|
//Device Id AC ELWA-E = 0x3efc
|
||||||
|
qCDebug(dcMypv()) << "device Id:" << data.mid(2, 2);
|
||||||
|
if (data.mid(2, 2) == QByteArray::fromHex("3efc")) {
|
||||||
|
qCDebug(dcMypv()) << "Found Device: AC ElWA-E";
|
||||||
|
} else if (data.mid(2, 2) == QByteArray::fromHex("0x4e8e")) {
|
||||||
|
qCDebug(dcMypv()) << "Found Device: Powermeter";
|
||||||
|
} else if (data.mid(2, 2) == QByteArray::fromHex("0x4e84")) {
|
||||||
|
qCDebug(dcMypv()) << "Found Device: AC Thor";
|
||||||
|
} else {
|
||||||
|
qCDebug(dcMypv()) << "Failed to parse discovery datagram from" << senderAddress << data;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 uiAddress = 0;
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
|
uiAddress |= data.at(7-i) << (i*8);
|
||||||
|
}
|
||||||
|
QHostAddress address(uiAddress);
|
||||||
|
QByteArray serialNumber = data.mid(8, 16);
|
||||||
|
|
||||||
|
bool existing = false;
|
||||||
|
foreach (Device *existingDev, myDevices()) {
|
||||||
|
if (existingDev->deviceClassId() == info->deviceClassId() && existingDev->paramValue(elwaDeviceIpAddressParamTypeId).toString() == address.toString()) {
|
||||||
|
existing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (existing) {
|
||||||
|
qCDebug(dcMypv()) << "Already have device" << senderAddress << "in configured devices. Skipping...";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DeviceDescriptor deviceDescriptors(info->deviceClassId(), "AC ELWA-E", address.toString());
|
||||||
|
ParamList params;
|
||||||
|
params << Param(elwaDeviceIpAddressParamTypeId, address.toString());
|
||||||
|
params << Param(elwaDeviceSerialNumberParamTypeId, serialNumber);
|
||||||
|
deviceDescriptors.setParams(params);
|
||||||
|
descriptorList << deviceDescriptors;
|
||||||
|
}
|
||||||
|
info->addDeviceDescriptors(descriptorList);;
|
||||||
|
searchSocket->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMyPv::setupDevice(DeviceSetupInfo *info)
|
||||||
|
{
|
||||||
|
Device *device = info->device();
|
||||||
|
|
||||||
|
if(device->deviceClassId() == elwaDeviceClassId) {
|
||||||
|
QHostAddress address = QHostAddress(device->paramValue(elwaDeviceIpAddressParamTypeId).toString());
|
||||||
|
ModbusTCPMaster *modbusTcpMaster = new ModbusTCPMaster(address, 502, this);
|
||||||
|
|
||||||
|
if (!modbusTcpMaster->createInterface()) {
|
||||||
|
modbusTcpMaster->deleteLater();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_modbusTcpMasters.insert(device, modbusTcpMaster);
|
||||||
|
} else {
|
||||||
|
Q_ASSERT_X(false, "setupDevice", QString("Unhandled deviceClassId: %1").arg(device->deviceClassId().toString()).toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMyPv::postSetupDevice(Device *device)
|
||||||
|
{
|
||||||
|
if (device->deviceClassId() == elwaDeviceClassId) {
|
||||||
|
update(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMyPv::deviceRemoved(Device *device)
|
||||||
|
{
|
||||||
|
if (device->deviceClassId() == elwaDeviceClassId) {
|
||||||
|
ModbusTCPMaster *modbusTCPMaster = m_modbusTcpMasters.take(device);
|
||||||
|
modbusTCPMaster->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMyPv::executeAction(DeviceActionInfo *info)
|
||||||
|
{
|
||||||
|
Device *device = info->device();
|
||||||
|
Action action = info->action();
|
||||||
|
|
||||||
|
if (device->deviceClassId() == elwaDeviceClassId) {
|
||||||
|
|
||||||
|
ModbusTCPMaster *modbusTCPMaster = m_modbusTcpMasters.value(device);
|
||||||
|
if (action.actionTypeId() == elwaHeatingPowerActionTypeId) {
|
||||||
|
int heatingPower = action.param(elwaHeatingPowerActionHeatingPowerParamTypeId).value().toInt();
|
||||||
|
|
||||||
|
if(!modbusTCPMaster->setRegister(0xff, ElwaModbusRegisters::Power, heatingPower)){
|
||||||
|
return info->finish(Device::DeviceErrorHardwareFailure);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (action.actionTypeId() == elwaPowerActionTypeId) {
|
||||||
|
bool power = action.param(elwaHeatingPowerActionHeatingPowerParamTypeId).value().toBool();
|
||||||
|
if(power) {
|
||||||
|
if(!modbusTCPMaster->setRegister(0xff, ElwaModbusRegisters::ManuelStart, 1)){
|
||||||
|
return info->finish(Device::DeviceErrorHardwareFailure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Q_ASSERT_X(false, "executeAction", QString("Unhandled deviceClassId: %1").arg(device->deviceClassId().toString()).toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMyPv::onRefreshTimer(){
|
||||||
|
|
||||||
|
foreach (Device *device, myDevices()) {
|
||||||
|
update(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMyPv::update(Device *device) {
|
||||||
|
if (device->deviceClassId() == elwaDeviceClassId)
|
||||||
|
{
|
||||||
|
ModbusTCPMaster *modbusTCPMaster = m_modbusTcpMasters.value(device);
|
||||||
|
|
||||||
|
int data;
|
||||||
|
if (modbusTCPMaster->getRegister(0xff, ElwaModbusRegisters::Status, &data)) {
|
||||||
|
switch (data) {
|
||||||
|
case Heating: {
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Heating");
|
||||||
|
device->setStateValue(elwaPowerStateTypeId, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Standby:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Standby");
|
||||||
|
device->setStateValue(elwaPowerStateTypeId, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Boosted:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Boosted");
|
||||||
|
device->setStateValue(elwaPowerStateTypeId, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HeatFinished:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Heat finished");
|
||||||
|
device->setStateValue(elwaPowerStateTypeId, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Setup:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Setup");
|
||||||
|
device->setStateValue(elwaPowerStateTypeId, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ErrorOvertempFuseBlown:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Error Overtemp Fuse blown");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ErrorOvertempMeasured:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Error Overtemp measured");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ErrorOvertempElectronics:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Error Overtemp Electronics");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ErrorHardwareFault:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Error Hardware Fault");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ErrorTempSensor:{
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Error Temp Sensor");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
device->setStateValue(elwaStatusStateTypeId, "Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
device->setStateValue(elwaConnectedStateTypeId, true);
|
||||||
|
} else {
|
||||||
|
device->setStateValue(elwaConnectedStateTypeId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modbusTCPMaster->getRegister(0xff, ElwaModbusRegisters::WaterTemperature, &data)) {
|
||||||
|
device->setStateValue(elwaTemperatureStateTypeId, data/10.00);
|
||||||
|
}
|
||||||
|
if (modbusTCPMaster->getRegister(0xff, ElwaModbusRegisters::TargetWaterTemperature, &data)) {
|
||||||
|
device->setStateValue(elwaTargetWaterTemperatureStateTypeId, data/10.00);
|
||||||
|
}
|
||||||
|
if (modbusTCPMaster->getRegister(0xff, ElwaModbusRegisters::Power, &data)) {
|
||||||
|
device->setStateValue(elwaHeatingPowerStateTypeId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
87
mypv/devicepluginmypv.h
Normal file
87
mypv/devicepluginmypv.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* 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 DEVICEPLUGINMYPV_H
|
||||||
|
#define DEVICEPLUGINMYPV_H
|
||||||
|
|
||||||
|
#include "devices/deviceplugin.h"
|
||||||
|
#include "plugintimer.h"
|
||||||
|
#include "modbustcpmaster.h"
|
||||||
|
|
||||||
|
#include <QUdpSocket>
|
||||||
|
|
||||||
|
class DevicePluginMyPv: public DevicePlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginmypv.json")
|
||||||
|
Q_INTERFACES(DevicePlugin)
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DevicePluginMyPv();
|
||||||
|
|
||||||
|
void discoverDevices(DeviceDiscoveryInfo *info) override;
|
||||||
|
void setupDevice(DeviceSetupInfo *info) override;
|
||||||
|
void postSetupDevice(Device *device) override;
|
||||||
|
void deviceRemoved(Device *device) override;
|
||||||
|
void executeAction(DeviceActionInfo *info) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum ElwaModbusRegisters {
|
||||||
|
Power= 1000,
|
||||||
|
WaterTemperature = 1001,
|
||||||
|
TargetWaterTemperature = 1002,
|
||||||
|
Status = 1003,
|
||||||
|
ManuelStart = 1012
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ElwaStatus {
|
||||||
|
Heating = 2,
|
||||||
|
Standby = 3,
|
||||||
|
Boosted = 4,
|
||||||
|
HeatFinished = 5,
|
||||||
|
Setup = 9,
|
||||||
|
ErrorOvertempFuseBlown = 201,
|
||||||
|
ErrorOvertempMeasured = 202,
|
||||||
|
ErrorOvertempElectronics = 203,
|
||||||
|
ErrorHardwareFault = 204,
|
||||||
|
ErrorTempSensor = 205
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginTimer *m_refreshTimer = nullptr;
|
||||||
|
QHash<Device *, ModbusTCPMaster *> m_modbusTcpMasters;
|
||||||
|
|
||||||
|
void update(Device *device);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onRefreshTimer();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICEPLUGINMYPV_H
|
||||||
|
|
||||||
112
mypv/devicepluginmypv.json
Normal file
112
mypv/devicepluginmypv.json
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"name": "Mypv",
|
||||||
|
"displayName": "Mypv",
|
||||||
|
"id": "73c7efcc-80d5-4166-ad97-2cbbeb129d91",
|
||||||
|
"vendors": [
|
||||||
|
{
|
||||||
|
"name": "myPV",
|
||||||
|
"displayName": "my-PV",
|
||||||
|
"id": "1f17597f-e0d0-459b-858d-ec9cbcd10b2c",
|
||||||
|
"deviceClasses": [
|
||||||
|
{
|
||||||
|
"name": "elwa",
|
||||||
|
"displayName": "AC Elwa E",
|
||||||
|
"id": "19ac4c7c-9c0a-4998-a8f0-c77d940cbb08",
|
||||||
|
"createMethods": ["Discovery"],
|
||||||
|
"interfaces": ["connectable", "temperaturesensor", "heating"],
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "ae66596f-f6c7-4d9c-9eee-b9190616a9e1",
|
||||||
|
"name":"ipAddress",
|
||||||
|
"displayName": "IP address",
|
||||||
|
"type": "QString"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b31a263a-2fdc-4a88-88ec-9e182025da8f",
|
||||||
|
"name":"serialNumber",
|
||||||
|
"displayName": "Serial number",
|
||||||
|
"type": "QString"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateTypes":[
|
||||||
|
{
|
||||||
|
"id": "a5afaad5-78bf-4cac-b98d-7eae31aac518",
|
||||||
|
"name": "connected",
|
||||||
|
"displayName": "Connected",
|
||||||
|
"displayNameEvent": "Connected changed",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"cached": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e6b0260b-f255-4f17-8ac1-bc87a950f449",
|
||||||
|
"name": "power",
|
||||||
|
"displayName": "Power",
|
||||||
|
"displayNameEvent": "Power changed",
|
||||||
|
"displayNameAction": "Change power",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2eb3c40c-1b43-4b64-82e2-6558f0b8817e",
|
||||||
|
"name": "heatingPower",
|
||||||
|
"displayName": "Power consumption",
|
||||||
|
"displayNameEvent": "Power consumption changed",
|
||||||
|
"displayNameAction": "Change power consumption",
|
||||||
|
"type": "int",
|
||||||
|
"unit": "Watt",
|
||||||
|
"minValue": 0,
|
||||||
|
"maxValue": 3600,
|
||||||
|
"defaultValue": 0,
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "60006f93-8852-433b-bbc0-f10cc3939eeb",
|
||||||
|
"name": "temperature",
|
||||||
|
"displayName": "Water temperature",
|
||||||
|
"displayNameEvent": "Water temperature changed",
|
||||||
|
"type": "double",
|
||||||
|
"unit": "DegreeCelsius",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2b089c93-6411-41f7-96aa-f78d5cf910cb",
|
||||||
|
"name": "targetWaterTemperature",
|
||||||
|
"displayName": "Target water temperature",
|
||||||
|
"displayNameEvent": "Target water temperature changed",
|
||||||
|
"type": "double",
|
||||||
|
"unit": "DegreeCelsius",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d0a7065e-7773-47d7-b474-ce8d21d55aa7",
|
||||||
|
"name": "status",
|
||||||
|
"displayName": "Status",
|
||||||
|
"displayNameEvent": "Status changed",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "Unknown",
|
||||||
|
"possibleValues": [
|
||||||
|
"Unknown",
|
||||||
|
"Heat",
|
||||||
|
"Standby",
|
||||||
|
"Boost heat",
|
||||||
|
"Heat finished",
|
||||||
|
"Setup",
|
||||||
|
"Error Overtemp Fuse blown",
|
||||||
|
"Error Overtemp measured",
|
||||||
|
"Error Overtemp Electronics",
|
||||||
|
"Error Hardware Fault",
|
||||||
|
"Error Temp Sensor"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
181
mypv/modbustcpmaster.cpp
Normal file
181
mypv/modbustcpmaster.cpp
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "modbustcpmaster.h"
|
||||||
|
#include "extern-plugininfo.h"
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(dcModbus)
|
||||||
|
Q_LOGGING_CATEGORY(dcModbus, "Modbus")
|
||||||
|
|
||||||
|
ModbusTCPMaster::ModbusTCPMaster(QHostAddress IPv4Address, int port, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_IPv4Address(IPv4Address),
|
||||||
|
m_port(port)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusTCPMaster::~ModbusTCPMaster()
|
||||||
|
{
|
||||||
|
if (m_mb != NULL) {
|
||||||
|
modbus_close(m_mb);
|
||||||
|
}
|
||||||
|
modbus_free(m_mb);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModbusTCPMaster::createInterface() {
|
||||||
|
// TCP connction to target device
|
||||||
|
qCDebug(dcModbus()) << "Setting up TCP connecion" << m_IPv4Address.toString() << "port:" << m_port;
|
||||||
|
const char *address = m_IPv4Address.toString().toLatin1().data();
|
||||||
|
m_mb = modbus_new_tcp(address, m_port);
|
||||||
|
|
||||||
|
if(m_mb == nullptr){
|
||||||
|
qCWarning(dcMypv()) << "Error modbus TCP: " << modbus_strerror(errno) ;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend the timeout to 3 seconds
|
||||||
|
//struct timeval response_timeout;
|
||||||
|
//response_timeout.tv_sec = 3;
|
||||||
|
//response_timeout.tv_usec = 0;
|
||||||
|
//modbus_set_response_timeout(m_mb, &response_timeout);
|
||||||
|
|
||||||
|
if(modbus_connect(m_mb) == -1){
|
||||||
|
qCWarning(dcMypv()) << "Error connecting modbus:" << modbus_strerror(errno) ;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ModbusTCPMaster::port()
|
||||||
|
{
|
||||||
|
return m_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModbusTCPMaster::setIPv4Address(QHostAddress ipv4Address)
|
||||||
|
{
|
||||||
|
m_IPv4Address = ipv4Address;
|
||||||
|
if (!createInterface()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModbusTCPMaster::setPort(int port)
|
||||||
|
{
|
||||||
|
m_port = port;
|
||||||
|
if (!createInterface()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHostAddress ModbusTCPMaster::ipv4Address()
|
||||||
|
{
|
||||||
|
return m_IPv4Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModbusTCPMaster::setCoil(int slaveAddress, int coilAddress, bool status)
|
||||||
|
{
|
||||||
|
if (m_mb == nullptr) {
|
||||||
|
if (!createInterface())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(modbus_set_slave(m_mb, slaveAddress) == -1){
|
||||||
|
qCWarning(dcMypv()) << "Error setting slave ID" << slaveAddress << "Reason:" << modbus_strerror(errno) ;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modbus_write_bit(m_mb, coilAddress, status) == -1) {
|
||||||
|
qCWarning(dcMypv()) << "Could not write Coil" << coilAddress << "Reason:" << modbus_strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModbusTCPMaster::setRegister(int slaveAddress, int registerAddress, int data)
|
||||||
|
{
|
||||||
|
if (m_mb == nullptr) {
|
||||||
|
if (!createInterface())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(modbus_set_slave(m_mb, slaveAddress) == -1){
|
||||||
|
qCWarning(dcMypv()) << "Error setting slave ID" << slaveAddress << "Reason:" << modbus_strerror(errno) ;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modbus_write_register(m_mb, registerAddress, data) == -1) {
|
||||||
|
qCWarning(dcMypv()) << "Could not write Register" << registerAddress << "Reason:" << modbus_strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModbusTCPMaster::getCoil(int slaveAddress, int coilAddress, bool *result)
|
||||||
|
{
|
||||||
|
if (m_mb == nullptr) {
|
||||||
|
if (!createInterface())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(modbus_set_slave(m_mb, slaveAddress) == -1){
|
||||||
|
qCWarning(dcMypv()) << "Error setting slave ID" << slaveAddress << "Reason:" << modbus_strerror(errno) ;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t status;
|
||||||
|
if (modbus_read_bits(m_mb, coilAddress, 1, &status) == -1){
|
||||||
|
qCWarning(dcMypv()) << "Could not read bits" << coilAddress << "Reason:"<< modbus_strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*result = (bool)status;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModbusTCPMaster::getRegister(int slaveAddress, int registerAddress, int *result)
|
||||||
|
{
|
||||||
|
uint16_t data;
|
||||||
|
|
||||||
|
if (m_mb == nullptr) {
|
||||||
|
if (!createInterface())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(modbus_set_slave(m_mb, slaveAddress) == -1){
|
||||||
|
qCWarning(dcMypv()) << "Error setting slave ID" << slaveAddress << "Reason:" << modbus_strerror(errno) ;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modbus_read_registers(m_mb, registerAddress, 1, &data) == -1){
|
||||||
|
qCWarning(dcMypv()) << "Could not read register" << registerAddress << "Reason:" << modbus_strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*result = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
63
mypv/modbustcpmaster.h
Normal file
63
mypv/modbustcpmaster.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* 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 MODBUSTCPMASTER_H
|
||||||
|
#define MODBUSTCPMASTER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <modbus/modbus.h>
|
||||||
|
|
||||||
|
class ModbusTCPMaster : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ModbusTCPMaster(QHostAddress IPv4Address, int port, QObject *parent = 0);
|
||||||
|
~ModbusTCPMaster();
|
||||||
|
bool createInterface();
|
||||||
|
|
||||||
|
bool getCoil(int slaveAddress, int coilAddress, bool *result);
|
||||||
|
bool getRegister(int slaveAddress, int registerAddress, int *result);
|
||||||
|
bool setCoil(int slaveAddress, int coilAddress, bool status);
|
||||||
|
bool setRegister(int slaveAddress, int registerAddress, int data);
|
||||||
|
QHostAddress ipv4Address();
|
||||||
|
int port();
|
||||||
|
bool setIPv4Address(QHostAddress IPv4Address);
|
||||||
|
bool setPort(int port);
|
||||||
|
|
||||||
|
private:
|
||||||
|
modbus_t *m_mb;
|
||||||
|
QHostAddress m_IPv4Address;
|
||||||
|
int m_port;
|
||||||
|
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MODBUSTCPMASTER_H
|
||||||
19
mypv/mypv.pro
Normal file
19
mypv/mypv.pro
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
include(../plugins.pri)
|
||||||
|
|
||||||
|
TARGET = $$qtLibraryTarget(nymea_devicepluginmypv)
|
||||||
|
|
||||||
|
QT += \
|
||||||
|
network \
|
||||||
|
serialbus \
|
||||||
|
|
||||||
|
LIBS += -lmodbus
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
devicepluginmypv.cpp \
|
||||||
|
modbustcpmaster.cpp \
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
devicepluginmypv.h \
|
||||||
|
modbustcpmaster.h \
|
||||||
|
|
||||||
|
|
||||||
@ -2,6 +2,7 @@ TEMPLATE = subdirs
|
|||||||
|
|
||||||
PLUGIN_DIRS = \
|
PLUGIN_DIRS = \
|
||||||
drexelundweiss \
|
drexelundweiss \
|
||||||
|
mypv \
|
||||||
wallbe \
|
wallbe \
|
||||||
|
|
||||||
message(============================================)
|
message(============================================)
|
||||||
|
|||||||
Reference in New Issue
Block a user