From 344239fe0dc8011556688ed65a4eae94ceb42b53 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 22 Jan 2020 16:58:48 +0100 Subject: [PATCH] added my-PV --- debian/control | 16 ++ debian/nymea-plugin-mypv.install.in | 1 + drexelundweiss/deviceplugindrexelundweiss.cpp | 27 +- drexelundweiss/deviceplugindrexelundweiss.h | 2 - mypv/README.md | 5 + mypv/devicepluginmypv.cpp | 261 ++++++++++++++++++ mypv/devicepluginmypv.h | 87 ++++++ mypv/devicepluginmypv.json | 112 ++++++++ mypv/modbustcpmaster.cpp | 181 ++++++++++++ mypv/modbustcpmaster.h | 63 +++++ mypv/mypv.pro | 19 ++ nymea-plugins-modbus.pro | 1 + 12 files changed, 757 insertions(+), 18 deletions(-) create mode 100644 debian/nymea-plugin-mypv.install.in create mode 100644 mypv/README.md create mode 100644 mypv/devicepluginmypv.cpp create mode 100644 mypv/devicepluginmypv.h create mode 100644 mypv/devicepluginmypv.json create mode 100644 mypv/modbustcpmaster.cpp create mode 100644 mypv/modbustcpmaster.h create mode 100644 mypv/mypv.pro diff --git a/debian/control b/debian/control index 1ad4456..287838e 100644 --- a/debian/control +++ b/debian/control @@ -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 +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 Architecture: any Depends: ${shlibs:Depends}, diff --git a/debian/nymea-plugin-mypv.install.in b/debian/nymea-plugin-mypv.install.in new file mode 100644 index 0000000..d4fea6f --- /dev/null +++ b/debian/nymea-plugin-mypv.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginmypv.so diff --git a/drexelundweiss/deviceplugindrexelundweiss.cpp b/drexelundweiss/deviceplugindrexelundweiss.cpp index 933c327..efefece 100644 --- a/drexelundweiss/deviceplugindrexelundweiss.cpp +++ b/drexelundweiss/deviceplugindrexelundweiss.cpp @@ -171,12 +171,10 @@ void DevicePluginDrexelUndWeiss::executeAction(DeviceActionInfo *info) discoverModbusSlaves(modbus, slave); info->finish(Device::DeviceErrorNoError); return; + } else { + info->finish(Device::DeviceErrorActionTypeNotFound); } - info->finish(Device::DeviceErrorActionTypeNotFound); - return; - } - - if (device->deviceClassId() == x2luDeviceClassId) { + } else if (device->deviceClassId() == x2luDeviceClassId) { Device *parentDevice = myDevices().findById(device->parentId()); if (!parentDevice) { 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); return; + } else { + info->finish(Device::DeviceErrorActionTypeNotFound); } - info->finish(Device::DeviceErrorActionTypeNotFound); - return; - } - - if (device->deviceClassId() == x2wpDeviceClassId) { + } else if (device->deviceClassId() == x2wpDeviceClassId) { Device *parentDevice = myDevices().findById(device->parentId()); if (!parentDevice) { qWarning(dcDrexelUndWeiss()) << "Could not find modbus interface"; @@ -235,18 +231,17 @@ void DevicePluginDrexelUndWeiss::executeAction(DeviceActionInfo *info) int data = static_cast(qRound(targetTemp * 1000)); m_pendingActions.insert(modbus->writeHoldingRegister(slave,ModbusRegisterX2::RaumSoll, data), info); return; - } - if (action.actionTypeId() == x2wpTargetWaterTemperatureActionTypeId) { + } else if (action.actionTypeId() == x2wpTargetWaterTemperatureActionTypeId) { qreal targetWaterTemp = action.param(x2wpTargetWaterTemperatureActionTargetWaterTemperatureParamTypeId).value().toDouble(); int data = static_cast(qRound(targetWaterTemp * 1000)); m_pendingActions.insert(modbus->writeHoldingRegister(slave, ModbusRegisterX2::BrauchwasserSolltermperatur, data), info); return; + } else { + info->finish(Device::DeviceErrorActionTypeNotFound); } - info->finish(Device::DeviceErrorActionTypeNotFound); - return; + } else { + info->finish(Device::DeviceErrorDeviceClassNotFound); } - info->finish(Device::DeviceErrorDeviceClassNotFound); - return; } diff --git a/drexelundweiss/deviceplugindrexelundweiss.h b/drexelundweiss/deviceplugindrexelundweiss.h index 9f079b0..87f72ec 100644 --- a/drexelundweiss/deviceplugindrexelundweiss.h +++ b/drexelundweiss/deviceplugindrexelundweiss.h @@ -51,8 +51,6 @@ public: void setupDevice(DeviceSetupInfo *info) override; void postSetupDevice(Device *device) override; void deviceRemoved(Device *device) override; - -public slots: void executeAction(DeviceActionInfo *info) override; private: diff --git a/mypv/README.md b/mypv/README.md new file mode 100644 index 0000000..4630b4a --- /dev/null +++ b/mypv/README.md @@ -0,0 +1,5 @@ +# mypv +-------------------------------- + +Description of the plugin... + diff --git a/mypv/devicepluginmypv.cpp b/mypv/devicepluginmypv.cpp new file mode 100644 index 0000000..dd1d734 --- /dev/null +++ b/mypv/devicepluginmypv.cpp @@ -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 . +* +* 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 +#include + +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 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); + } + } +} + diff --git a/mypv/devicepluginmypv.h b/mypv/devicepluginmypv.h new file mode 100644 index 0000000..48a14f2 --- /dev/null +++ b/mypv/devicepluginmypv.h @@ -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 . +* +* 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 + +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 m_modbusTcpMasters; + + void update(Device *device); + +private slots: + void onRefreshTimer(); +}; + +#endif // DEVICEPLUGINMYPV_H + diff --git a/mypv/devicepluginmypv.json b/mypv/devicepluginmypv.json new file mode 100644 index 0000000..4327b07 --- /dev/null +++ b/mypv/devicepluginmypv.json @@ -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" + ] + } + ] + } + ] + } + ] +} + + + + diff --git a/mypv/modbustcpmaster.cpp b/mypv/modbustcpmaster.cpp new file mode 100644 index 0000000..4590e9b --- /dev/null +++ b/mypv/modbustcpmaster.cpp @@ -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 . +* +* 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 + +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; +} diff --git a/mypv/modbustcpmaster.h b/mypv/modbustcpmaster.h new file mode 100644 index 0000000..b214282 --- /dev/null +++ b/mypv/modbustcpmaster.h @@ -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 . +* +* 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 +#include +#include + +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 diff --git a/mypv/mypv.pro b/mypv/mypv.pro new file mode 100644 index 0000000..51ab21d --- /dev/null +++ b/mypv/mypv.pro @@ -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 \ + + diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index 0a6f508..fe5f300 100644 --- a/nymea-plugins-modbus.pro +++ b/nymea-plugins-modbus.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs PLUGIN_DIRS = \ drexelundweiss \ + mypv \ wallbe \ message(============================================)