commit 28542347a21b9d4e94ee8c7df37122cc9735cb15 Author: Boernsman Date: Wed Jan 22 16:03:12 2020 +0100 added drexel und weiss diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..568d4ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pro.user +builddir +doc/html diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..8ab4ed2 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,296 @@ +nymea-plugins (0.17.0) xenial; urgency=medium + + [ Michael Zanetti ] + * FlowerCare: Add a refresh rate setting + * Packaging: Fix dpkg clean target + * Simulation: Add a simulated barcode scanner device + + [ Bernhard Trinnes ] + * Http commander: Add http server + + [ Michael Zanetti ] + * DaylightSensor: Update README.md + * dweet.io: Update README + * Kodi: Automatically redetect Kodi when its IP address changes + * Various Plugins: Spelling fixes in README files + * Tasmota: Add Blinds support + + [ Federico Leoni ] + * Update Tasmota readme + + [ Michael Zanetti ] + * PhilipsHue: Fix the timeout setting + + -- Jenkins Mon, 09 Dec 2019 10:09:05 +0100 + +nymea-plugins (0.16.0) xenial; urgency=medium + + [ Bernhard Trinnes ] + * New Plugin: CoinMarketCap + * New plugin: Sonos + + [ Michael Zanetti ] + * All Plugins: Devicemanager api rework + * Fix hertz wording + * New plugin: Shelly + * EQ-3: Some fixes for Eqiva Bluetooth Smart Radiator Thermostats + * PhilipsHue: Fix light intensity conversion to lux for the motion + sensor + * Anel: Add anel plugin to main plugin package + * NetworkDetector: Improve packaging and error handling + * Nuimo: Make it work with Bluez 5.48 (except battery service) + * Kodi: Fix active player not updating properly in some circumstances + + -- Jenkins Tue, 22 Oct 2019 01:09:57 +0200 + +nymea-plugins (0.15.2) xenial; urgency=medium + + [ Bernhard Trinnes ] + * Denon Plug-In: Add heos devices + + [ George Yatsev ] + * Tasmota: Add support for 3ch devices + + [ Bernhard Trinnes ] + * TcpCommander: Fixed input and output device class + * New plug-in: systemmonitor + + [ Michael Zanetti ] + * PhilipsHue: Make use of the new alert interface + + [ Bernhard Trinnes ] + * New plugin: One-Wire + * GPIO Plug-In: add counter device + * Senic: Fixed device clean up on a failed device setup + + [ Michael Zanetti ] + * Remove Plugin: UniPi + + -- Jenkins Thu, 19 Sep 2019 12:28:02 +0200 + +nymea-plugins (0.15.1) xenial; urgency=medium + + [ Bernhard Trinnes ] + * New Plugin: WS2812FX + + [ Michael Zanetti ] + * Tasmota: Use new powerswitch interface + + [ Bernhard Trinnes ] + * Add plug-in bose soundtouch + * New plugin: generic interfaces + + [ Michael Zanetti ] + * Kodi: Add support for media browsing + + [ Bernhard Trinnes ] + * Fix senic + + [ Michael Zanetti ] + * Fix hue outdoorsensor + + -- Jenkins Mon, 02 Sep 2019 18:02:07 +0200 + +nymea-plugins (0.15.0) xenial; urgency=medium + + + [ Simon Stürz ] + * All Plugins: Update docs mechanism + + [ Michael Zanetti ] + * NetworkDetector: Change grace period to be a setting instead of a + param + * Philips Hue: Add support for the hue indoor motion sensor + * All Plugins: Update according to new DeviceManager API + * Use the new nymea-plugininfocompiler for building + * GenericElements: Fix toggle button writable state + + -- Jenkins Mon, 22 Jul 2019 12:17:16 +0200 + +nymea-plugins (0.12.3) xenial; urgency=medium + + [ Michael Zanetti ] + * FlowerCare: Drop unneeded name parameter + * PhilipsHue: Improve discovery and fix reconfigure + * PhilipsHue: Drop deprecated old-style Hue Remote button events + * WeMo: Fix discovery if device has been renamed + * TexasInstruments: Fix Package dependencies + * PhilipsHue: Work around a bug in Osram LIGHTIFY RGBW LED stripes + + -- Jenkins Fri, 28 Jun 2019 12:45:50 +0200 + +nymea-plugins (0.12.2) xenial; urgency=medium + + [ Bernhard Trinnes ] + * Removed plugins: Orderbutton, Plantcare and ws2812 + * renamed HTTP commander vendor, fixed segfault on device removed + * Fix serialportcommander + + [ Michael Zanetti ] + * OpenWeatherMap: Add daylightsensor and allow loading custom API keys + + [ Simon Stürz ] + * Update mail notifications plugin + + [ Michael Zanetti ] + * Depend on pkg-config + * Kodi, AvahiMonitor: Update to new ZeroConf API + + -- Jenkins Wed, 19 Jun 2019 23:52:37 +0200 + +nymea-plugins (0.12.1) xenial; urgency=medium + + [ Michael Zanetti ] + * Rename vendor "guh GmbH" to "nymea" + * Don't use dpkg specific tools to set install path + + -- Jenkins Thu, 02 May 2019 11:56:17 +0200 + +nymea-plugins (0.12.0) xenial; urgency=medium + [ Michael Zanetti ] + * aWATTar: Cleanup old unused code and use connectable interface + + [ Simon Stürz ] + * PhilipsHue plugin: Add Hue Outdoor sensor + + [ Michael Zanetti ] + * All Plugins: Update to API changes in core. + * Boblight: Build using internal plugins.pri + * New plugin: Pushbullet + * New Plugin: TexasInstruments, replaces MultiSensor plugin + + -- Jenkins Fri, 12 Apr 2019 13:42:46 +0200 + +nymea-plugins (0.10.3) xenial; urgency=medium + + [ Simon Stürz ] + * Netatmo: Update API changes and improve refresh behavior + + [ Michael Zanetti ] + * Simulation: Update ev charger to new interface spec + * SonoffTasmota: Fix wrong IP settings in device setup + * NetworkDetector: Don't fail the entire discovery if one of the scan + targets fails + * PhilipsHue: Enable reconfiguring Hue bridges (Re-pairing) + * Elgato: Fix reading of color state + * Simulation: Add missing unit to heating plugins + * FlowerCare: Add unit to conductivity sensor + * Simulation: Make use of thermostat interface + * NetworkDetector: Improve monitoring, add a grace period parameter + * EQ-3: Add support for the Eqiva Bluetooth thermostat + * Update Plugins: Set deviceId in Descriptors on discovery + * Flower Care: Fix temperature reading for negative values + + [ Bernhard Trinnes ] + * New Plugin: Serialport commander + + -- Jenkins Wed, 06 Mar 2019 21:13:53 +0100 + +nymea-plugins (0.10.2) xenial; urgency=medium + + [ Michael Zanetti ] + * Simulation: Make use of powersocket interface + * New Plugin: Boblight + + [ Simon Stürz ] + * LG smart TV: General update (use of interfaces, improve connectivity + with TV) + + [ Michael Zanetti ] + * New Plugin: ANEL Elektronik NET-PwrCtrl + * Networkdetector: Implement the presencesensor interface + * New Plugin: Daylight sensor which works offline and without any + hardware + * Sumulation: Fix action simulation for simulated blinds + * PhilipsHue: Fix hue tap button 4 param value typo + * Kodi: Implement new media interfaces + + -- Jenkins Thu, 17 Jan 2019 15:37:27 +0100 + +nymea-plugins (0.10.1) xenial; urgency=medium + + [ Michael Zanetti ] + * Elgato/Avea: Fix handling white channel in set color action + * New plugin: Generic MQTT client plugin (mqttclient) + * PhilipsHue: Improve naming of hue devices using nymea's system name + and syncing to bridge + * New plugin: Sonoff-Tasmota devices plugin (tasmota) + * Netatmo: fix after upstream API change, make use of new interfaces + + [ Simon Stürz ] + * New plugin: UniPi + + -- Jenkins Wed, 12 Dec 2018 14:44:58 +0100 + +nymea-plugins (0.10.0) xenial; urgency=medium + + [ Michael Zanetti ] + * Elgato/Avea: Fix handling white channel in set color action + * New plugin: Generic MQTT client plugin (mqttclient) + * PhilipsHue: Improve naming of hue devices using nymea's system name + and syncing to bridge + * New plugin: Sonoff-Tasmota devices plugin (tasmota) + * Netatmo: fix after upstream API change, make use of new interfaces + + -- Jenkins Sun, 09 Dec 2018 18:56:19 +0100 + +nymea-plugins (0.9.39) xenial; urgency=medium + + [ Jenkins ] + * Prepare for release + + [ Michael Zanetti ] + * use system interface in snapdcontrol + * update hue plugin + * change networkdetector discovery to do parallel scans... + * Make use of new heating/evcharger interfaces in the simulated + devices plugin + + -- Jenkins Tue, 20 Nov 2018 14:32:38 +0100 + +nymea-plugins (0.9.38) xenial; urgency=medium + + [ Jenkins ] + * Prepare for release + + [ Michael Zanetti ] + * add some nicer curves to simulated values + * gateway now inherits connectable + * add simple blind simulation + * implement fingerprint interface in simulation plugin + + -- Jenkins Mon, 29 Oct 2018 14:19:53 +0100 + +nymea-plugins (0.9.37) xenial; urgency=medium + + [ Jenkins ] + * Prepare for release + + [ Michael Zanetti ] + * Update all plugins using the new nymea-generateplugininfo + + -- Jenkins Wed, 10 Oct 2018 04:38:16 +0200 + +nymea-plugins (0.9.36) bionic; urgency=medium + + * Bump version to be in line with core + + -- Michael Zanetti Wed, 10 Oct 2018 04:00:57 +0200 + +nymea-plugins (0.2.0) bionic; urgency=medium + + * rename to nymea + + -- Michael Zanetti Wed, 10 Oct 2018 04:00:00 +0200 + +guh-plugins (0.1.0) xenial; urgency=medium + + * Add metapackages + + -- Simon Stürz Tue, 03 Oct 2017 17:07:31 +0200 + +guh-plugins (0.1) UNRELEASED; urgency=medium + + * Initial release. (Closes: #XXXXXX) + + -- Michael Zanetti Tue, 11 Jul 2017 15:23:34 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..3ba6372 --- /dev/null +++ b/debian/control @@ -0,0 +1,63 @@ +Source: nymea-plugins-modbus +Section: utils +Priority: options +Maintainer: Michael Zanetti +Build-depends: libboblight-dev, + debhelper (>= 0.0.0), + libnymea1-dev (>= 0.17), + libnymea-mqtt-dev, + libqt5serialport5-dev, + libqt5websockets5-dev, + nymea-dev-tools:native, + pkg-config, + python:any, + qtbase5-dev, + qtconnectivity5-dev, + libow-dev, +Standards-Version: 3.9.3 + + +Package: nymea-plugin-drexelundweiss +Architecture: any +Section: libs +Depends: ${shlibs:Depends}, + ${misc:Depends}, + nymea-plugins-translations, +Description: nymea.io plugin for Drexel & Weiss 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 Drexel & Weiss heat pumps + + +Package: nymea-plugins-translations +Section: misc +Architecture: all +Depends: ${misc:Depends} +Replaces: guh-plugins-translations +Description: Translation files for nymea plugins - translations + 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 provides the translation files for all nymea plugins. + + +Package: nymea-plugins-modbus +Section: libs +Architecture: all +Depends: nymea-plugin-drexelundweiss, +Description: Plugins for nymea IoT server - the modbus plugin collection + 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 modbus plugins. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..268ef87 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,77 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: nymea-plugins +Upstream-Contact: Simon Stürz +Copyright: 2014-2017, guh GmbH +Download: http://www.github.com/guh/guh-plugins +Source: https://github.com/guh/guh-plugins.git + + +License: GPL-2+ + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL-2'. + +License: LGPL-2.1 + On Debian systems, the complete text of the GNU Lesser General + Public License can be found in `/usr/share/common-licenses/LGPL-2.1'. + +License: LGPL-3 + On Debian systems, the complete text of the GNU Lesser General + Public License can be found in `/usr/share/common-licenses/LGPL-3'. + + +Files: translations/* +License: LGPL-2.1 +Copyright: 2016-2017, Simon Stürz + +Files: debian/* +License: GPL-2+ +Copyright: 2017, Michael Zanetti + 2017, Simon Stürz + +Files: denon/* + orderbutton/* + ws2812/* + tcpcommander/* +License: LGPL-2.1 +Copyright: 2016, Bernhard Trinnes + +Files: plantcare/* + unipi/* +License: LGPL-2.1 +Copyright: 2016-2018, Simon Stürz + 2016-2018, Bernhard Trinnes + +Files: avahimonitor/* + awattar/* + dollhouse/* + elgato/* + elro/* + eq-3/* + genericelements/* + gpio/* + leynew/* + lgsmarttv/* + mailnotification/* + netatmo/* + networkdetector/* + osdomotics/* + senic/* + udpcommander/* + unitec/* + wemo/* +License: LGPL-2.1 +Copyright: 2014-2017, Simon Stürz + + +Files: wakeonlan/* + commandlauncher/* + conrad/* + datetime/* + intertechno/* + kodi/* + lircd/* + openweathermap/* + philipshue/* +License: LGPL-2.1 +Copyright: 2014-2017, Simon Stürz + 2014-2017, Michael Zanetti diff --git a/debian/nymea-plugin-drexelundweiss.install.in b/debian/nymea-plugin-drexelundweiss.install.in new file mode 100644 index 0000000..5d21d66 --- /dev/null +++ b/debian/nymea-plugin-drexelundweiss.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_deviceplugindrexelundweiss.so diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..7a8679f --- /dev/null +++ b/debian/rules @@ -0,0 +1,25 @@ +#!/usr/bin/make -f + +DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) + +PREPROCESS_FILES := $(wildcard debian/*.in) + +$(PREPROCESS_FILES:.in=): %: %.in + sed 's,/@DEB_HOST_MULTIARCH@,$(DEB_HOST_MULTIARCH:%=/%),g' $< > $@ + +%: + dh $@ --parallel + +override_dh_auto_build: + dh_auto_build + make lrelease + +override_dh_install: $(PREPROCESS_FILES:.in=) + dh_install --fail-missing + +override_dh_auto_clean: + dh_auto_clean + find -name *plugininfo.h -exec rm {} \; + find -name *.qm -exec rm {} \; + rm -rf $(PREPROCESS_FILES:.in=) + diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/drexelundweiss/README.md b/drexelundweiss/README.md new file mode 100644 index 0000000..e69de29 diff --git a/drexelundweiss/deviceplugindrexelundweiss.cpp b/drexelundweiss/deviceplugindrexelundweiss.cpp new file mode 100644 index 0000000..933c327 --- /dev/null +++ b/drexelundweiss/deviceplugindrexelundweiss.cpp @@ -0,0 +1,635 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "deviceplugindrexelundweiss.h" +#include "plugininfo.h" +#include "modbusdegisterdefinition.h" + +DevicePluginDrexelUndWeiss::DevicePluginDrexelUndWeiss() +{ + +} + +DevicePluginDrexelUndWeiss::~DevicePluginDrexelUndWeiss() +{ + +} + +void DevicePluginDrexelUndWeiss::init() +{ + connect(this, &DevicePluginDrexelUndWeiss::configValueChanged, this, &DevicePluginDrexelUndWeiss::onPluginConfigurationChanged); +} + +void DevicePluginDrexelUndWeiss::discoverDevices(DeviceDiscoveryInfo *info) +{ + // Create the list of available serial interfaces + QList deviceDescriptors; + + if (info->deviceClassId() == modbusConnectionDeviceClassId) { + Q_FOREACH(QSerialPortInfo port, QSerialPortInfo::availablePorts()) { + if (m_usedSerialPorts.contains(port.systemLocation())){ + //device already in use + qCDebug(dcDrexelUndWeiss()) << "Found serial port that is already used:" << port.portName(); + } else { + //Serial port is not yet used, create now a new one + qCDebug(dcDrexelUndWeiss()) << "Found serial port:" << port.portName(); + QString description = port.manufacturer() + " " + port.description(); + DeviceDescriptor descriptor(info->deviceClassId(), port.portName(), description); + ParamList parameters; + parameters.append(Param(modbusConnectionDeviceSerialPortParamTypeId, port.systemLocation())); + descriptor.setParams(parameters); + info->addDeviceDescriptor(descriptor); + } + } + info->finish(Device::DeviceErrorNoError); + return; + } + + if (info->deviceClassId() == x2luDeviceClassId) { + info->finish(Device::DeviceErrorNoError); + return; + } + + if (info->deviceClassId() == x2wpDeviceClassId) { + info->finish(Device::DeviceErrorNoError); + return; + } + + info->finish(Device::DeviceErrorDeviceClassNotFound); + return; +} + +void DevicePluginDrexelUndWeiss::setupDevice(DeviceSetupInfo *info) +{ + Device *device = info->device(); + + if (device->deviceClassId() == modbusConnectionDeviceClassId) { + + QString serialPort = device->paramValue(modbusConnectionDeviceSerialPortParamTypeId).toString(); + int baudRate = device->paramValue(modbusConnectionDeviceBaudRateParamTypeId).toInt(); + + ModbusRTUMaster *modbus = new ModbusRTUMaster(serialPort, baudRate, QSerialPort::Parity::NoParity, 8, 1, this); + connect(modbus, &ModbusRTUMaster::connectionStateChanged, this, &DevicePluginDrexelUndWeiss::onConnectionStateChanged); + connect(modbus, &ModbusRTUMaster::receivedCoil, this, &DevicePluginDrexelUndWeiss::onReceivedCoil); + connect(modbus, &ModbusRTUMaster::receivedDiscreteInput, this, &DevicePluginDrexelUndWeiss::onReceivedDiscreteInput); + connect(modbus, &ModbusRTUMaster::receivedHoldingRegister, this, &DevicePluginDrexelUndWeiss::onReceivedHoldingRegister); + connect(modbus, &ModbusRTUMaster::receivedInputRegister, this, &DevicePluginDrexelUndWeiss::onReceivedInputRegister); + + + m_modbusRTUMasters.insert(device, modbus); + m_usedSerialPorts.append(serialPort); + info->finish(Device::DeviceErrorNoError); + return; + } + + if (device->deviceClassId() == x2luDeviceClassId) { + info->finish(Device::DeviceErrorNoError); + return; + } + + if (device->deviceClassId() == x2wpDeviceClassId) { + info->finish(Device::DeviceErrorNoError); + return; + } + info->finish(Device::DeviceErrorDeviceClassNotFound); + return; +} + +void DevicePluginDrexelUndWeiss::postSetupDevice(Device *device) +{ + if (!m_refreshTimer) { + // Refresh timer for TCP read + int refreshTime = configValue(drexelUndWeissPluginUpdateIntervalParamTypeId).toInt(); + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(refreshTime); + connect(m_refreshTimer, &PluginTimer::timeout, this, &DevicePluginDrexelUndWeiss::onRefreshTimer); + } + + if (device->deviceClassId() == modbusConnectionDeviceClassId) { + // read Register 5000 and emit auto-device + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(device); + if (!modbus){ + qCWarning(dcDrexelUndWeiss()) << "No modbus master available"; + } + device->setStateValue(modbusConnectionConnectedStateTypeId, true); + } + + if ((device->deviceClassId() == x2luDeviceClassId) || (device->deviceClassId() == x2wpDeviceClassId)) { + Device *parentDevice = myDevices().findById(device->parentId()); + if (!parentDevice) { + qWarning(dcDrexelUndWeiss()) << "Could not find the parent device"; + return; + } + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parentDevice); + if (!modbus){ + qCWarning(dcDrexelUndWeiss()) << "No modbus interface available"; + } + updateStates(device); + // Update states + } +} + +void DevicePluginDrexelUndWeiss::executeAction(DeviceActionInfo *info) +{ + Device *device = info->device(); + Action action = info->action(); + + if (device->deviceClassId() == modbusConnectionDeviceClassId) { + + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(device); + if (!modbus){ + qCWarning(dcDrexelUndWeiss()) << "No modbus interface available"; + info->finish(Device::DeviceErrorHardwareFailure); + return; + } + + if (action.actionTypeId() == modbusConnectionDiscoverDevicesActionTypeId) { + int slave = action.param(modbusConnectionDiscoverDevicesActionSlaveAddressParamTypeId).value().toInt(); + discoverModbusSlaves(modbus, slave); + info->finish(Device::DeviceErrorNoError); + return; + } + info->finish(Device::DeviceErrorActionTypeNotFound); + return; + } + + if (device->deviceClassId() == x2luDeviceClassId) { + Device *parentDevice = myDevices().findById(device->parentId()); + if (!parentDevice) { + qWarning(dcDrexelUndWeiss()) << "Could not find the parent device"; + info->finish(Device::DeviceErrorHardwareFailure); + return; + } + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parentDevice); + int slave = device->paramValue(x2luDeviceSlaveAddressParamTypeId).toInt(); + if (!modbus){ + qCWarning(dcDrexelUndWeiss()) << "No modbus interface available"; + info->finish(Device::DeviceErrorHardwareFailure); + return; + } + + if (action.actionTypeId() == x2luVentilationModeActionTypeId) { + QString mode = action.param(x2luVentilationModeActionVentilationModeParamTypeId).value().toString(); + int data = 0; + + if (mode == "Manual level 0") { + data = VentialtionMode::ManuellStufe0; + } else if(mode == "Manual level 1") { + data = VentialtionMode::ManuellStufe1; + } else if(mode == "Manual level 2") { + data = VentialtionMode::ManuellStufe2; + }else if(mode == "Manual level 3") { + data = VentialtionMode::ManuellStufe3; + } else if(mode == "Automatic") { + data = VentialtionMode::Automatikbetrieb; + } else if(mode == "Party") { + data = VentialtionMode::Party; + } + m_pendingActions.insert(modbus->writeHoldingRegister(slave, ModbusRegisterX2::Betriebsart, data), info); + return; + } + info->finish(Device::DeviceErrorActionTypeNotFound); + return; + } + + if (device->deviceClassId() == x2wpDeviceClassId) { + Device *parentDevice = myDevices().findById(device->parentId()); + if (!parentDevice) { + qWarning(dcDrexelUndWeiss()) << "Could not find modbus interface"; + info->finish(Device::DeviceErrorHardwareFailure); + return; + } + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parentDevice); + int slave = device->paramValue(x2wpDeviceSlaveAddressParamTypeId).toInt(); + if (!modbus){ + qCWarning(dcDrexelUndWeiss()) << "No modbus master available"; + info->finish(Device::DeviceErrorHardwareFailure); + return; + } + + if (action.actionTypeId() == x2wpTargetTemperatureActionTypeId) { + qreal targetTemp = (action.param(x2wpTargetTemperatureActionTargetTemperatureParamTypeId).value().toDouble()); + int data = static_cast(qRound(targetTemp * 1000)); + m_pendingActions.insert(modbus->writeHoldingRegister(slave,ModbusRegisterX2::RaumSoll, data), info); + return; + } + 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; + } + info->finish(Device::DeviceErrorActionTypeNotFound); + return; + } + info->finish(Device::DeviceErrorDeviceClassNotFound); + return; +} + + +void DevicePluginDrexelUndWeiss::deviceRemoved(Device *device) +{ + if (device->deviceClassId() == modbusConnectionDeviceClassId) { + + ModbusRTUMaster *modbus = m_modbusRTUMasters.take(device); + if (!modbus){ + qCWarning(dcDrexelUndWeiss()) << "No modbus interface available"; + return; + } + m_usedSerialPorts.removeAll(modbus->serialPort()); + modbus->deleteLater(); + } +} + +void DevicePluginDrexelUndWeiss::onRefreshTimer() +{ + foreach (Device *device, myDevices()) { + + if (device->deviceClassId() == modbusConnectionDeviceClassId) { + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(device); + + if (!modbus) { + qCWarning(dcDrexelUndWeiss()) << "No modbus master available"; + return; + } + } else if (device->deviceClassId() == x2luDeviceClassId || device->deviceClassId() == x2wpDeviceClassId){ + updateStates(device); + } + } +} + +void DevicePluginDrexelUndWeiss::updateStates(Device *device) +{ + if (device->deviceClassId() == x2luDeviceClassId) { + Device *parent = myDevices().findById(device->parentId()); + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parent); + int slave = device->paramValue(x2luDeviceSlaveAddressParamTypeId).toInt(); + + modbus->readHoldingRegister(slave, ModbusRegisterX2::AktiveLuefterstufe); + modbus->readHoldingRegister(slave, ModbusRegisterX2::Betriebsart); // Ventilation mode + modbus->readHoldingRegister(slave, ModbusRegisterX2::CO2); + } + + if (device->deviceClassId() == x2wpDeviceClassId) { + Device *parent = myDevices().findById(device->parentId()); + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parent); + int slave = device->paramValue(x2wpDeviceSlaveAddressParamTypeId).toInt(); + + modbus->readHoldingRegister(slave, ModbusRegisterX2::Waermepumpe); + modbus->readHoldingRegister(slave, ModbusRegisterX2::RaumSoll); + modbus->readHoldingRegister(slave, ModbusRegisterX2::Raum); + modbus->readHoldingRegister(slave, ModbusRegisterX2::TemperaturWarmwasserspeicherUnten); + modbus->readHoldingRegister(slave, ModbusRegisterX2::BrauchwasserSolltermperatur); + modbus->readHoldingRegister(slave, ModbusRegisterX2::Auszenluft); + modbus->readHoldingRegister(slave, ModbusRegisterX2::Summenstoerung); + modbus->readHoldingRegister(slave, ModbusRegisterX2::LeistungKompressor); + modbus->readHoldingRegister(slave, ModbusRegisterX2::LeistungWarmwasser); + modbus->readHoldingRegister(slave, ModbusRegisterX2::LeistungRaumheizung); + modbus->readHoldingRegister(slave, ModbusRegisterX2::LeistungLuftvorwaermung); + modbus->readHoldingRegister(slave, ModbusRegisterX2::EnergieKompressor); + modbus->readHoldingRegister(slave, ModbusRegisterX2::EnergieWarmwasser); + modbus->readHoldingRegister(slave, ModbusRegisterX2::EnergieRaumheizung); + modbus->readHoldingRegister(slave, ModbusRegisterX2::EnergieLuftvorerwarrmung); + } +} + + +void DevicePluginDrexelUndWeiss::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) +{ + // Check refresh schedule + if (paramTypeId == drexelUndWeissPluginUpdateIntervalParamTypeId) { + if (m_refreshTimer) { + int refreshTime = value.toInt(); + m_refreshTimer->stop(); + m_refreshTimer->startTimer(refreshTime); + } + } +} + +void DevicePluginDrexelUndWeiss::onConnectionStateChanged(bool status) +{ + Q_UNUSED(status) +} + +void DevicePluginDrexelUndWeiss::onReceivedCoil(int slaveAddress, int modbusRegister, bool value) +{ + Q_UNUSED(slaveAddress) + Q_UNUSED(modbusRegister) + Q_UNUSED(value) +} + +void DevicePluginDrexelUndWeiss::onReceivedDiscreteInput(int slaveAddress, int modbusRegister, bool value) +{ + Q_UNUSED(slaveAddress) + Q_UNUSED(modbusRegister) + Q_UNUSED(value) +} + +void DevicePluginDrexelUndWeiss::onReceivedHoldingRegister(int slaveAddress, int modbusRegister, int value) +{ + ModbusRTUMaster *modbus = static_cast(sender()); + + if (m_modbusRTUMasters.values().contains(modbus) ){ + Device *parentDevice = m_modbusRTUMasters.key(static_cast(modbus)); + + foreach(Device *device, myDevices().filterByParentDeviceId(parentDevice->id())) { + if (device->deviceClassId() == x2luDeviceClassId && device->paramValue(x2luDeviceSlaveAddressParamTypeId) == slaveAddress) { + switch (modbusRegister) { + case ModbusRegisterX2::Waermepumpe: + device->setStateValue(x2wpPowerStateTypeId, value); + break; + + case ModbusRegisterX2::RaumSoll: + device->setStateValue(x2wpTargetTemperatureStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::Raum: + device->setStateValue(x2wpTemperatureStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::TemperaturWarmwasserspeicherUnten: + device->setStateValue(x2wpWaterTemperatureStateTypeId, value/1000.00); + break; + case ModbusRegisterX2::BrauchwasserSolltermperatur: + device->setStateValue(x2wpTargetWaterTemperatureStateTypeId, value/1000.00); + break; + case ModbusRegisterX2::Auszenluft: + device->setStateValue(x2wpOutsideAirTemperatureStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::Summenstoerung: + if (value != 0) { + //get actual error + } else { + device->setStateValue(x2wpErrorStateTypeId, "No Error"); + } + break; + + case ModbusRegisterX2::StoerungAbluftventilator: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Exhaust fan"); + break; + + case ModbusRegisterX2::StoerungBoilerfuehlerElektroheizstab: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Boiler sensor electric heating element"); + break; + + case ModbusRegisterX2::StoerungBoilerfuehlerSolar: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Boiler sensor solar"); + break; + + case ModbusRegisterX2::StoerungBoilerfuehlerWaermepumpe: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Boiler sensor heat pump"); + break; + + case ModbusRegisterX2::StoerungBoileruebertemperatur: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Boiler overtemperature"); + break; + + case ModbusRegisterX2::StoerungCO2Sensor: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "CO2-Sensor"); + break; + + case ModbusRegisterX2::StoerungDruckverlustAbluftZuGrosz: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Pressure loss exhaust air too big"); + break; + + case ModbusRegisterX2::StoerungDruckverlustZuluftZuGrosz: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Pressure loss supply air too large"); + break; + + case ModbusRegisterX2::StoerungDurchflussmengeHeizgkreis: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Flow rate of heating circuit"); + break; + + case ModbusRegisterX2::StoerungDurchflussmengeSolekreis: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Flow rate brine circuit"); + break; + + case ModbusRegisterX2::StoerungTeilnehmerNichtErreichbar: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Participant not available"); + break; + + case ModbusRegisterX2::StoerungTemperaturfuehlerAuszenluft: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Temperature sensor outside air"); + break; + + case ModbusRegisterX2::StoerungTemperaturfuehlerHeizkreisVorlauf: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Temperature sensor heating circuit flow"); + break; + + case ModbusRegisterX2::StoerungTemperaturfuehlerRaum: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Temperature sensor room"); + break; + + case ModbusRegisterX2::StoerungTemperaturfuehlerSolarkollektor: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Temperature sensor solar collector"); + break; + + case ModbusRegisterX2::StoerungTemperaturfuehlerSole: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Temperature sensor brine"); + break; + + case ModbusRegisterX2::StoerungTemperaturfuehlerSoleAuszenluft: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Temperature sensor brine outside air"); + break; + + case ModbusRegisterX2::StoerungWaermepumpeHochdruck: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Heat pump high pressure"); + break; + + case ModbusRegisterX2::StoerungWaermepumpeNiederdruck: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Heat pump low pressure"); + break; + + case ModbusRegisterX2::StoerungWertNichtZulaessig: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Value not allowed"); + break; + + case ModbusRegisterX2::StoerungZuluftventilator: + if (value != 0) + device->setStateValue(x2wpErrorStateTypeId, "Supply air fan"); + break; + + case ModbusRegisterX2::LeistungKompressor: + device->setStateValue(x2wpPowerCompressorStateTypeId, value/1000.00); + break; + case ModbusRegisterX2::LeistungWarmwasser: + device->setStateValue(x2wpPowerWaterHeatingStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::LeistungRaumheizung: + device->setStateValue(x2wpPowerRoomHeatingStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::LeistungLuftvorwaermung: + device->setStateValue(x2wpPowerAirPreheatingStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::EnergieKompressor: + device->setStateValue(x2wpEnergyCompressorStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::EnergieWarmwasser: + device->setStateValue(x2wpEnergyWaterHeatingStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::EnergieRaumheizung: + device->setStateValue(x2wpEnergyRoomHeatingStateTypeId, value/1000.00); + break; + + case ModbusRegisterX2::EnergieLuftvorerwarrmung: + device->setStateValue(x2wpEnergyAirPreheatingStateTypeId, value/1000.00); + break; + default: + break; + } + } else if (device->deviceClassId() == x2wpDeviceClassId && device->paramValue(x2wpDeviceSlaveAddressParamTypeId) == slaveAddress) { + + switch (modbusRegister) { + case ModbusRegisterX2::Betriebsart: + if (value == VentialtionMode::ManuellStufe0) { + device->setStateValue(x2luVentilationModeStateTypeId, "Manual level 0"); + } else if (value == VentialtionMode::ManuellStufe1) { + device->setStateValue(x2luVentilationModeStateTypeId, "Manual level 1"); + } else if (value == VentialtionMode::ManuellStufe2) { + device->setStateValue(x2luVentilationModeStateTypeId, "Manual level 2"); + } else if (value == VentialtionMode::ManuellStufe3) { + device->setStateValue(x2luVentilationModeStateTypeId, "Manual level 3"); + } else if (value == VentialtionMode::Automatikbetrieb) { + device->setStateValue(x2luVentilationModeStateTypeId, "Automatic"); + } else if (value == VentialtionMode::Party) { + device->setStateValue(x2luVentilationModeStateTypeId, "Party"); + } + break; + case ModbusRegisterX2::AktiveLuefterstufe: + device->setStateValue(x2luActiveVentilationLevelStateTypeId, value); + break; + case ModbusRegisterX2::CO2: + device->setStateValue(x2luCo2StateTypeId, value); + break; + } + } + } + + if (modbusRegister == ModbusRegisterX2::Geraetetyp) { + switch (value) { + case DeviceType::X2_WP: { + qDebug(dcDrexelUndWeiss()) << "Discovered X2 heat pump"; + QList deviceDescriptors; + DeviceDescriptor descriptor(x2wpDeviceClassId, "X2 WP", "Drexel und Weiss", parentDevice->id()); + ParamList params; + + //modbus->readHoldingRegister(slaveAddress, ModbusRegisterX2::SoftwareVersion); + //params.append(Param(x2wpDeviceSofwareVersionParamTypeId, data)); + params.append(Param(x2wpDeviceSlaveAddressParamTypeId, slaveAddress)); + descriptor.setParams(params); + deviceDescriptors.append(descriptor); + emit autoDevicesAppeared(deviceDescriptors); + break; + } + case DeviceType::X2_LU: { + qDebug(dcDrexelUndWeiss()) << "Discovered X2 ventilation unit"; + QList deviceDescriptors; + DeviceDescriptor descriptor(x2luDeviceClassId, "X2 LU", "Drexel und Weiss", parentDevice->id()); + ParamList params; + + //modbus->readHoldingRegister(slaveAddress, ModbusRegisterX2::SoftwareVersion); + //params.append(Param(x2luDeviceSofwareVersionParamTypeId, data)); + params.append(Param(x2luDeviceSlaveAddressParamTypeId, slaveAddress)); + descriptor.setParams(params); + deviceDescriptors.append(descriptor); + emit autoDevicesAppeared(deviceDescriptors); + break; + } + case DeviceType::AerosilentBianco: + //Just a test + qDebug(dcDrexelUndWeiss()) << "Discovered Aerosilent Bianco"; + break; + default: + qDebug(dcDrexelUndWeiss()) << "Unkown Devicetype" << value; + } + } + } +} + +void DevicePluginDrexelUndWeiss::onReceivedInputRegister(int slaveAddress, int modbusRegister, int value) +{ + Q_UNUSED(slaveAddress) + Q_UNUSED(modbusRegister) + Q_UNUSED(value) +} + +void DevicePluginDrexelUndWeiss::onWriteRequestFinished(QUuid requestId, bool success) +{ + DeviceActionInfo *info = m_pendingActions.take(requestId); + if (!info) + return; + + if (success) { + info->finish(Device::DeviceErrorNoError); + } else { + info->finish(Device::DeviceErrorHardwareFailure); + } +} + +void DevicePluginDrexelUndWeiss::discoverModbusSlaves(ModbusRTUMaster *modbus, int slaveAddress) +{ + foreach (Device *device, myDevices()) { + if (device->deviceClassId() == x2luDeviceClassId) { + if (device->paramValue(x2luDeviceSlaveAddressParamTypeId).toInt() == slaveAddress) { + qWarning(dcDrexelUndWeiss()) << "Device with slave address" << slaveAddress << "already added"; + return; + } + } + if (device->deviceClassId() == x2wpDeviceClassId) { + if (device->paramValue(x2wpDeviceSlaveAddressParamTypeId).toInt() == slaveAddress) { + qWarning(dcDrexelUndWeiss()) << "Device with slave address" << slaveAddress << "already added"; + return; + } + } + } + modbus->readHoldingRegister(slaveAddress, ModbusRegisterX2::Geraetetyp); +} diff --git a/drexelundweiss/deviceplugindrexelundweiss.h b/drexelundweiss/deviceplugindrexelundweiss.h new file mode 100644 index 0000000..9f079b0 --- /dev/null +++ b/drexelundweiss/deviceplugindrexelundweiss.h @@ -0,0 +1,80 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 DEVICEPLUGINDREXELUNDWEISS_H +#define DEVICEPLUGINDREXELUNDWEISS_H + +#include "devices/devicemanager.h" +#include "plugintimer.h" + +#include +#include "modbusrtumaster.h" +#include + +class DevicePluginDrexelUndWeiss : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "deviceplugindrexelundweiss.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginDrexelUndWeiss(); + ~DevicePluginDrexelUndWeiss() override; + void init() override; + + void discoverDevices(DeviceDiscoveryInfo *info) override; + void setupDevice(DeviceSetupInfo *info) override; + void postSetupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + +public slots: + void executeAction(DeviceActionInfo *info) override; + +private: + QList m_usedSerialPorts; + QHash m_modbusRTUMasters; + PluginTimer *m_refreshTimer = nullptr; + QHash m_pendingActions; + + void updateStates(Device *device); + void discoverModbusSlaves(ModbusRTUMaster *modbus, int slaveAddress); + +private slots: + void onRefreshTimer(); + void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value); + + void onConnectionStateChanged(bool status); + void onReceivedCoil(int slaveAddress, int modbusRegister, bool value); + void onReceivedDiscreteInput(int slaveAddress, int modbusRegister, bool value); + void onReceivedHoldingRegister(int slaveAddress, int modbusRegister, int value); + void onReceivedInputRegister(int slaveAddress, int modbusRegister, int value); + + void onWriteRequestFinished(QUuid requestId, bool success); +}; + +#endif // DEVICEPLUGINDREXELUNDWEISS_H diff --git a/drexelundweiss/deviceplugindrexelundweiss.json b/drexelundweiss/deviceplugindrexelundweiss.json new file mode 100644 index 0000000..462ed36 --- /dev/null +++ b/drexelundweiss/deviceplugindrexelundweiss.json @@ -0,0 +1,374 @@ +{ + "name": "DrexelUndWeiss", + "displayName": "Drexel und Weiss", + "id": "68d78ce6-82d0-4a5b-b901-7c3b843ef63c", + "paramTypes":[ + { + "id": "ecc8e0f1-5fac-4ea9-b5ef-459d75c4fe78", + "name": "updateInterval", + "displayName": "Update interval", + "type": "int", + "unit": "Seconds", + "defaultValue": 15 + } + ], + "vendors": [ + { + "name": "DrexelUndWeiss", + "displayName": "Drexel und Weiss", + "id": "9f476e8b-7e95-448e-b03b-874747e8fb1f", + "deviceClasses": [ + { + "name": "modbusConnection", + "displayName": "Modbus Connection", + "id": "06d04eec-ab5d-479a-b9e6-8c89efc18a8b", + "createMethods": ["discovery", "user"], + "interfaces": ["gateway"], + "paramTypes": [ + { + "id": "ed49f7d8-ab18-4c37-9b80-1004b75dcb91", + "name": "serialPort", + "displayName": "Serial port", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "ttyAMA0" + }, + { + "id": "d0c04612-cc3e-4d38-b4c9-708e28dc4eb3", + "name": "baudRate", + "displayName": "Baudrate", + "type": "int", + "defaultValue": 9600 + } + ], + "stateTypes": [ + { + "id": "181ce6e2-9c55-45c6-b329-adf379679e07a", + "name": "connected", + "displayName": "connected", + "displayNameEvent": "connection status changed", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + { + "id": "b9a24ecc-4433-4f31-99ba-596033bda421", + "name": "discoverDevices", + "displayName": "Discover devices", + "paramTypes": [ + { + "id": "22413a22-31d4-4b8c-b855-8a29da5946bc", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "minValue": 0, + "maxValue": 250, + "defaultValue": 0 + } + ] + } + ] + }, + { + "name": "x2lu", + "displayName": "X2 LU", + "id": "0de8e21e-392a-4790-a78a-b1a7eaa7571b", + "createMethods": ["auto"], + "interfaces": ["co2sensor", "connectable"], + "paramTypes": [ + { + "id": "22413a22-31d4-4b8c-b855-8a29da5946bc", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "minValue": 0, + "maxValue": 250, + "defaultValue": 0 + }, + { + "id": "91ef76cf-6c53-4a8a-a278-6f6e2ef68cc6", + "name": "sofwareVersion", + "displayName": "Software version", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "-" + } + ], + "stateTypes":[ + { + "id": "181ce6e2-9c55-45c6-b329-adf379679e07a", + "name": "connected", + "displayName": "connected", + "displayNameEvent": "connection status changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "0a6b44c8-e7af-4148-92ff-682ae717f3a8", + "name": "co2", + "displayName": "CO2", + "displayNameEvent": "CO2 changed", + "unit": "PartsPerMillion", + "type": "double", + "defaultValue": 350 + }, + { + "id": "4269d9d0-ddff-4e7a-9d7a-bf9a7db50f98", + "name": "ventilationMode", + "displayName": "Ventilation mode", + "displayNameEvent": "Ventilation mode changed", + "displayNameAction": "Change ventilation mode", + "type": "QString", + "defaultValue": "Manual level 0", + "possibleValues": [ + "Manual level 0", + "Manual level 1", + "Manual level 2", + "Manual level 3", + "Automatic", + "Party" + ], + "writable": true + }, + { + "id": "1f26a013-7836-4f3e-b369-7ce07310fc59", + "name": "activeVentilationLevel", + "displayName": "Ventilation level", + "displayNameEvent": "Ventilation level changed", + "type": "int", + "defaultValue": 0 + } + ] + }, + { + "name": "x2wp", + "displayName": "X2 WP", + "id": "e548f962-92db-4110-8279-10fbcde35f93", + "createMethods": ["auto"], + "interfaces": ["thermostat", "heating", "temperaturesensor", "connectable"], + "paramTypes": [ + { + "id": "22413a22-31d4-4b8c-b855-8a29da5946bc", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "minValue": 0, + "maxValue": 250, + "defaultValue": 0 + }, + { + "id": "91ef76cf-6c53-4a8a-a278-6f6e2ef68cc6", + "name": "sofwareVersion", + "displayName": "Software version", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "-" + } + ], + "stateTypes":[ + + { + "id": "181ce6e2-9c55-45c6-b329-adf379679e07a", + "name": "connected", + "displayName": "connected", + "displayNameEvent": "connection status changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "f2ce8389-c33f-4f10-8484-f2e993841762", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Change power", + "type": "bool", + "defaultValue": 0, + "writable": true + }, + { + "id": "3ab2d609-1686-4fd7-84e3-580c8e0537d0", + "name": "temperature", + "displayName": "Room temperature", + "displayNameEvent": "Room temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "id": "77a96b57-fa0a-4946-af5b-39c3b66d9422", + "name": "waterTemperature", + "displayName": "Water temperature", + "displayNameEvent": "Water temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "id": "32378843-5478-4b86-9c0e-ccbf978c02be", + "name": "outsideAirTemperature", + "displayName": "Outside air temperature", + "displayNameEvent": "Outside air temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "id": "fb98754d-0fba-4163-9b74-3e5a07d71421", + "name": "targetTemperature", + "displayName": "Target room temperature", + "displayNameEvent": "Target room temperature changed", + "displayNameAction": "Change room target temperature", + "type": "double", + "unit": "DegreeCelsius", + "minValue": 14.00, + "maxValue": 26.00, + "defaultValue": 22.00, + "writable": true + }, + { + "id": "fb021cac-1236-4324-a45c-8d89ad069052", + "name": "targetWaterTemperature", + "displayName": "Target water temperature", + "displayNameEvent": "Target water temperature changed", + "displayNameAction": "Change water target temperature", + "type": "double", + "unit": "DegreeCelsius", + "minValue": 20.00, + "maxValue": 55.00, + "defaultValue": 46.00, + "writable": true + }, + { + "id": "5c125ddd-a0db-40fe-9998-2afea6c727f1", + "name": "heatPumpMode", + "displayName": "Heat pump mode", + "displayNameEvent": "Heat pump mode changed", + "type": "QString", + "defaultValue": "Heat pump off", + "possibleValues": [ + "Restart interlock" , + "Heat pump off", + "Lead time brine pump", + "Fan run-up time", + "Open hot gas valve", + "Open LPG valve", + "Start compressor", + "Minimum runtime heat pump", + "Heat pump on", + "Draw off refrigerant", + "Vacuum during defrosting", + "Defrost", + "Drain after defrosting" + ] + }, + { + "id": "8d6e52ef-992d-47ac-90a8-9dba95ab200e", + "name": "error", + "displayName": "Error", + "displayNameEvent": "Error occured", + "type": "QString", + "defaultValue": "No error", + "possibleValues": [ + "No error", + "Exhaust fan" , + "Boiler sensor electric heating element", + "Boiler sensor solar", + "Boiler sensor heat pump", + "Boiler overtemperature", + "CO2-Sensor", + "Pressure loss exhaust air too big", + "Pressure loss supply air too large", + "Flow rate of heating circuit", + "Flow rate brine circuit", + "Participant not available", + "Temperature sensor outside air", + "Temperature sensor heating circuit flow", + "Temperature sensor room", + "Temperature sensor solar collector", + "Temperature sensor brine", + "Temperature sensor brine outside air", + "Heat pump high pressure", + "Heat pump low pressure", + "Value not allowed", + "Supply air fan" + ] + }, + { + "id": "7287943a-ea6d-4c92-abbd-f55f6c7ee9e5", + "name": "powerCompressor", + "displayName": "Power consumption compressor", + "displayNameEvent": "Power consumption compressor changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "59beeff5-89c1-4996-9e07-48d53d74684d", + "name": "powerAirPreheating", + "displayName": "Power consumption air preheating", + "displayNameEvent": "Power consumption air preheating changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "c4237da0-0ead-42b8-b192-6a681509dc90", + "name": "powerRoomHeating", + "displayName": "Power consumption room heating", + "displayNameEvent": "Power consumption room heating changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "21dfc736-f35c-469f-be57-afc1976d8328", + "name": "powerWaterHeating", + "displayName": "Power consumption water heating", + "displayNameEvent": "Power consumption water heating changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "b423657b-4e59-41cd-89a3-4f5cb1c3a271", + "name": "energyCompressor", + "displayName": "Energy compressor", + "displayNameEvent": "Energy compressor changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "0816ca6d-a178-4a2a-8183-c26a794fb0ca", + "name": "energyAirPreheating", + "displayName": "Energy air preheating", + "displayNameEvent": "Energy air preheating changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "cb189b75-3634-4674-a847-f29ca322d4be", + "name": "energyWaterHeating", + "displayName": "Energy water heating", + "displayNameEvent": "Energy water heating changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "4495618e-5a43-46ac-9f76-32aae3f3e954", + "name": "energyRoomHeating", + "displayName": "Energy room heating", + "displayNameEvent": "Energy room heating changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ] + } + ] + } + ] +} diff --git a/drexelundweiss/drexelundweiss.pro b/drexelundweiss/drexelundweiss.pro new file mode 100644 index 0000000..b68e01e --- /dev/null +++ b/drexelundweiss/drexelundweiss.pro @@ -0,0 +1,16 @@ +include(../plugins.pri) + +QT += \ + serialport \ + serialbus \ + +SOURCES += \ + deviceplugindrexelundweiss.cpp \ + modbusrtumaster.cpp \ + +HEADERS += \ + deviceplugindrexelundweiss.h \ + modbusrtumaster.h \ + modbusdegisterdefinition.h + + diff --git a/drexelundweiss/modbusdegisterdefinition.h b/drexelundweiss/modbusdegisterdefinition.h new file mode 100644 index 0000000..2440882 --- /dev/null +++ b/drexelundweiss/modbusdegisterdefinition.h @@ -0,0 +1,248 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 MODBUSDEGISTERDEFINITION +#define MODBUSDEGISTERDEFINITION + +#endif // MODBUSDEGISTERDEFINITION + +enum ModbusRegisterX2 { + AbsenkungderLuefterstufe1 = 5328, + AktiveLuefterstufe = 1066, + AnforderungDerVentilatorenDurchWaermepumpe = 1292, + AnforderungDerVentilatorenDurchZonenregelung = 1336, + AnforderungDisbalance = 1338, + AnforderungBeschattung = 1218, + AnforderungBrauchwasserheizungElektoheizstab = 1038, + AnforderungBrauchwasserheizungWaermepumpe = 1036, + AnforderungBypassklappe = 1216, + AnforderungFrostschutz = 1146, + AnforderungLST3EXT = 228, + AnforderungRaumHeizstufe1 = 1032, + AnforderungRaumHeizstufe2 = 1034, + AnforderungSoleFuerAuszenlufterwaermung = 1198, + AnforderungSoleFuerWaermepumpe = 1196, + AnforderungSoleKuehlung = 1320, + Auszenluft = 202, + AuszenluftfuehlerVorhanden = 5304 , + Beschattungsfunktion = 5336, + Betriebsart = 5002, + BetriebsstundenAbluftventilator = 902, + BetriebsstundenBeschattung = 964, + BetriebsstundenBypassklappe = 932, + BetriebsstundenFeinstaub = 928, + BetriebsstundenFrostschutzeinrichtung = 940, + BetriebsstundenGrobstaub = 926, + BetriebsstundenHeizstufe1 = 912, + BetriebsstundenHeizstufe2 = 914, + BetriebsstundenHeizung = 938, + BetriebsstundenLuefterstufe0 = 962, + BetriebsstundenLuefterstufe1 = 904, + BetriebsstundenLuefterstufe2 = 906, + BetriebsstundenLuefterstufe3 = 908, + BetriebsstundenReduzierteLuefterstufe = 944, + BetriebsstundenSoleKreis = 934, + BetriebsstundenSoleKreisAuszenluft = 970, + BetriebsstundenZuluftventilator = 900, + Brandmeldealarm = 838, + BrandmeldeanlageVorhanden = 5068, + CO2 = 230, + CO2Messung = 1048, + CO2SensorVorhanden = 5054, + Datum = 5210, + DrehzahlAbluftventilator = 1094, + DrehzahlZuluftventilator = 1092, + ErhoehungDerLuefterstufe3 = 5330, + FeinstaubfilterVorhanden = 5034, + FeinstaubfilterWechseln = 7004, + FeinstaubfilterStandzeit = 5032, + FunktionHeizungPlus = 5492, + Geraetetyp = 5000, + GesamtBefoerderteKubikmeter = 946, + GesamtBefoerderteLuftmengeSeitFilterwechsel = 960, + GrobstaubfilterVorhanden = 5154, + GrobstaubfilterWechseln = 7002, + GrobstaubfilterBetriebsartFiltereberwachung = 5164, + GrobstaubfilterMaximalesFoerdervolumen = 5166, + GrobstaubfilterStandzeit = 5030, + IstDrehzahlAbluftventilator = 1186, + IstDrehzahlZuluftventilator = 1184, + KontaktBrandmeldealarm = 238, + KuehlungVorhanden = 5192, + MaximalZulaessigeDrehzahlAbluftventilator = 5270, + MaximalZulaessigeDrehzahlZuluftventilator = 5268, + PelletofenVorhanden = 5168, + PelletofenAnlaufverzoegerung = 5282, + PelletofenMindestlaufzeit = 5284, + Raum = 200, + RaumSoll = 5016, + RaumtemperaturBeschattung = 5338, + RelaiskontaktEXT = 252, + Revisionstuere = 226, + SollVolumenstromAbluft = 1084, + SollVolumenstromLuefterstufe = 5060, + SollVolumenstromZuluft = 1082, + SollwertErhoehungFunktionHeizungPlus = 5496, + StoerungAbluftventilator = 826, + StoerungBoilerfuehlerElektroheizstab = 828, + StoerungBoilerfuehlerSolar = 844, + StoerungBoilerfuehlerWaermepumpe = 830, + StoerungBoileruebertemperatur = 810, + StoerungCO2Sensor = 832, + StoerungDruckverlustAbluftZuGrosz = 854, + StoerungDruckverlustZuluftZuGrosz = 852, + StoerungDurchflussmengeHeizgkreis = 848, + StoerungDurchflussmengeSolekreis = 846, + StoerungTeilnehmerNichtErreichbar = 856, + StoerungTemperaturfuehlerAuszenluft = 806, + StoerungTemperaturfuehlerHeizkreisVorlauf = 850, + StoerungTemperaturfuehlerRaum = 804, + StoerungTemperaturfuehlerSolarkollektor = 842, + StoerungTemperaturfuehlerSole = 812, + StoerungTemperaturfuehlerSoleAuszenluft = 7504, + StoerungWaermepumpeHochdruck = 818, + StoerungWaermepumpeNiederdruck = 820, + StoerungWertNichtZulaessig = 840, + StoerungZuluftventilator = 824, + Summenstoerung = 800, + Summenstoerung2 = 7500, + SoftwareVersion = 1156, + Tag = 1174, + TemperaturAuszenluftBeschattung = 5340, + TemperaturAuszenluftBypassKuehlungAus = 5452, + TemperaturAuszenluftBypassKuehlungEin = 5450, + TemperaturAuszenluftBypassOeffnen = 5084, + TemperaturAuszenluftBypassSchlieszen = 5086, + TemperaturAuszenluftFrostschutz= 5206, + TemperaturAuszenluftFrostschutzAus = 5090, + TemperaturAuszenluftFrostschutzEin = 5088, + TemperaturAuszenluftKuehlungAus = 5200, + TemperaturAuszenluftKuelungEin = 5198, + TemperaturAuszenluftReduktionLuftmenge10Prozent = 5422, + TemperaturAuszenluftReduktionLuftmenge20Prozent = 5424, + TemperaturSole = 216, + TemperaturSoleNachAuszenluftvorwaermung = 206, + TemperaturschwelleAuszenluftHeizenKuehlen = 5186, + TimeOutSolltemperaturRaum = 7508, + TimeOutTemperaturfuehlerAuszenluft = 7510, + TimeoutTemperaturfuehlerHeizkreisVorlauf = 7512, + TimeoutTemperaturfuehlerRaum = 7506, + Uhrzeit = 5212, + UhrzeitundDatumVerschicken = 5442, + VolumenstrombalanceZuluftAbluft = 5026, + VorlaufNiedertemperaturHeizkreis = 240, + ZeitspanneFunktionHeizung = 5494, + ZeitspanneFunktionParty = 5038, + HochdruckWaermepumpe = 222, + NiederdruckWaermepumpe = 224, //nicht vorhanden, vorhanden + HystereseSolareRaumheizungAus = 5302, + KontaktEVU = 232, + MaximalZulaessigeVorlauftemperaturHeizung = 5202, + MinimalZulaessigeRuecklauftemperaturPassiveKuehlung = 5204, + MinimaleBrauchwassertemperaturSolareRaumheizungEin = 5300, + RaumtemperaturPassiveKuehlungEin = 5182, + RelaiskontaktExt = 262, + SolaranlageVorhanden = 5190, + SolaranlageAusschaltschwelleSolarpumpe = 5180, + SolaranlageEinschaltschwelleSolarpumpe =5178, + SolaranlageMaximaleLadetemperatur = 5176, + SolareRaumheizungVorhanden = 5306, + StatusWaermepumpe = 1314, + StatusWaermepumpeRestzeit = 1316, // in seconds + EVUAnlageVorhanden = 5146, + EVUSperreBrauchwasserAktiv = 1270, + EVUSperreElektroheizstabAktiv = 1274, + EVUSperreRaumheizungAktiv = 1272, + BrauchwasserSolltermperatur = 5064, + BrauchwassertemperaturRaumheizungssperre = 5130, + FunktionBadPlus = 5036, + ElektroheizstabVorhanden = 5126, + EnergieKompressor = 4500, + EnergieLuftvorerwarrmung = 4506, + EnergieRaumheizung = 4502, + EnergieWarmwasser = 4504, + LeistungKompressor = 4000, + LeistungLuftvorwaermung = 4006 , + LeistungRaumheizung = 4002, + LeistungWarmwasser = 4004, + TemperaturBoilerSolar = 244, + TemperaturSolarkollektor = 242, + TemperaturWarmwasserspeicherOben = 212, + TemperaturWarmwasserspeicherUnten = 214, + Waermepumpe = 1044, + ZentralgeraetAdresse = 5436 +}; + + +enum DeviceType { + NotDefined, + AerosilentPrimus = 1, + AerosilentTopo, + AerosilentMicro, + AerosmartS, + AerosmartM, + AerosmartL, + AerosmartXls, + AerosilentCentro, + TermosmartSc, + X2_LU = 25, + X2_WP = 26, + AerosmartMono, + Vbox120, + AerosilentBianco, + X2Plus, + AerosilentBusiness, + CentralDevice, + AerosilentStratos, + ZoneControl, + Vbox300 +}; + +enum HeatPumpStatus { + Wiedereinschaltsperre, + Waermepumpeaus, + VorlaufzeitSolepumpe, + VorlaufzeitVentilatoren, + OeffnenHeissgasventil, + OeffnenFluessiggasventil, + StartKompressor, + MindestlaufzeitWaermepumpe, + WaermepumpeEin, + KaeltemittleAbsaugen, + BeimAbtauenAbsaugen, + AbtauenEin, + NachAbtauenAbtropfen +}; + +enum VentialtionMode { + ManuellStufe0 = 0, + ManuellStufe1, + ManuellStufe2, + ManuellStufe3, + Automatikbetrieb, + Party +}; diff --git a/drexelundweiss/modbusrtumaster.cpp b/drexelundweiss/modbusrtumaster.cpp new file mode 100644 index 0000000..5b14dcd --- /dev/null +++ b/drexelundweiss/modbusrtumaster.cpp @@ -0,0 +1,362 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "modbusrtumaster.h" +#include "extern-plugininfo.h" + +#include + +ModbusRTUMaster::ModbusRTUMaster(QString serialPort, int baudrate, QSerialPort::Parity parity, int dataBits, int stopBits, QObject *parent) : + QObject(parent) +{ + m_modbusRtuSerialMaster = new QModbusRtuSerialMaster(this); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialPortNameParameter, serialPort); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, baudrate); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, dataBits); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, stopBits); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialParityParameter, parity); + //m_modbusRtuSerialMaster->setTimeout(100); + //m_modbusRtuSerialMaster->setNumberOfRetries(1); + connect(m_modbusRtuSerialMaster, &QModbusTcpClient::stateChanged, this, &ModbusRTUMaster::onModbusStateChanged); + connect(m_modbusRtuSerialMaster, &QModbusRtuSerialMaster::errorOccurred, this, &ModbusRTUMaster::onModbusErrorOccurred); + + m_reconnectTimer = new QTimer(this); + m_reconnectTimer->setSingleShot(true); + connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusRTUMaster::onReconnectTimer); +} + + +ModbusRTUMaster::~ModbusRTUMaster() +{ + if (!m_modbusRtuSerialMaster) { + m_modbusRtuSerialMaster->disconnectDevice(); + m_modbusRtuSerialMaster->deleteLater(); + } + if (!m_reconnectTimer) { + m_reconnectTimer->stop(); + m_reconnectTimer->deleteLater(); + } +} + +bool ModbusRTUMaster::connectDevice() +{ + qDebug(dcDrexelUndWeiss()) << "Setting up TCP connecion"; + + if (!m_modbusRtuSerialMaster) + return false; + + return m_modbusRtuSerialMaster->connectDevice(); +} + +QString ModbusRTUMaster::serialPort() +{ + return m_modbusRtuSerialMaster->connectionParameter(QModbusDevice::SerialPortNameParameter).toString(); +} + +void ModbusRTUMaster::onReplyFinished() +{ + QModbusReply *reply = qobject_cast(sender()); + if (!reply) + return; + reply->deleteLater(); + + int modbusAddress = 0; + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + + for (int i = 0; i < static_cast(unit.valueCount()); i++) { + //qCDebug(dcUniPi()) << "Start Address:" << unit.startAddress() << "Register Type:" << unit.registerType() << "Value:" << unit.value(i); + modbusAddress = unit.startAddress() + i; + + switch (unit.registerType()) { + case QModbusDataUnit::RegisterType::Coils: + emit receivedCoil(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::DiscreteInputs: + emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::InputRegisters: + emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::HoldingRegisters: + emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::Invalid: + break; + } + } + + } else if (reply->error() == QModbusDevice::ProtocolError) { + qCWarning(dcDrexelUndWeiss()) << "Read response error:" << reply->errorString() << reply->rawResult().exceptionCode(); + } else { + qCWarning(dcDrexelUndWeiss()) << "Read response error:" << reply->error(); + } +} + +void ModbusRTUMaster::onReplyErrorOccured(QModbusDevice::Error error) +{ + qCWarning(dcDrexelUndWeiss()) << "Modbus replay error:" << error; + QModbusReply *reply = qobject_cast(sender()); + if (!reply) + return; + reply->finished(); //to make sure it will be deleted +} + +void ModbusRTUMaster::onReconnectTimer() +{ + if(!m_modbusRtuSerialMaster->connectDevice()) { + m_reconnectTimer->start(10000); + } +} + +bool ModbusRTUMaster::readCoil(int slaveAddress, int registerAddress) +{ + if (!m_modbusRtuSerialMaster) + return false; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, 1); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, &ModbusRTUMaster::onReplyFinished); + connect(reply, &QModbusReply::errorOccurred, this, &ModbusRTUMaster::onReplyErrorOccured); + QTimer::singleShot(200, reply, SLOT(deleteLater())); + } else { + delete reply; // broadcast replies return immediately + } + } else { + qCWarning(dcDrexelUndWeiss()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + } + return true; +} + +QUuid ModbusRTUMaster::writeCoil(int slaveAddress, int registerAddress, bool value) +{ + QUuid requestId = QUuid::createUuid(); + if (!m_modbusRtuSerialMaster) + return requestId; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, 1); + request.setValue(0, static_cast(value)); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendWriteRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, [requestId, this] { + + QModbusReply *reply = qobject_cast(sender()); + if (!reply) + return; + reply->deleteLater(); + + int modbusAddress = 0; + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + emit writeRequestFinished(requestId, true); + + for (int i = 0; i < static_cast(unit.valueCount()); i++) { + //qCDebug(dcUniPi()) << "Start Address:" << unit.startAddress() << "Register Type:" << unit.registerType() << "Value:" << unit.value(i); + modbusAddress = unit.startAddress() + i; + + switch (unit.registerType()) { + case QModbusDataUnit::RegisterType::Coils: + emit receivedCoil(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::DiscreteInputs: + emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::InputRegisters: + emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::HoldingRegisters: + emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::Invalid: + break; + } + } + } else if (reply->error() == QModbusDevice::ProtocolError) { + emit writeRequestFinished(requestId, false); + qCWarning(dcDrexelUndWeiss()) << "Read response error:" << reply->errorString() << reply->rawResult().exceptionCode(); + } else { + emit writeRequestFinished(requestId, false); + qCWarning(dcDrexelUndWeiss()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, &ModbusRTUMaster::onReplyErrorOccured); + QTimer::singleShot(200, reply, SLOT(deleteLater())); + } else { + delete reply; // broadcast replies return immediately + } + } else { + qCWarning(dcDrexelUndWeiss()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + } + return requestId; +} + +QUuid ModbusRTUMaster::writeHoldingRegister(int slaveAddress, int registerAddress, int value) +{ + QUuid requestId = QUuid::createUuid(); + if (!m_modbusRtuSerialMaster){ + qCWarning(dcDrexelUndWeiss()) << "Modbus RTU interface not available"; + return requestId; + } + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, 1); + request.setValue(0, static_cast(value)); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendWriteRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, [requestId, this] { + + QModbusReply *reply = qobject_cast(sender()); + if (!reply) + return; + reply->deleteLater(); + + int modbusAddress = 0; + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + emit writeRequestFinished(requestId, true); + + for (int i = 0; i < static_cast(unit.valueCount()); i++) { + //qCDebug(dcUniPi()) << "Start Address:" << unit.startAddress() << "Register Type:" << unit.registerType() << "Value:" << unit.value(i); + modbusAddress = unit.startAddress() + i; + + switch (unit.registerType()) { + case QModbusDataUnit::RegisterType::Coils: + emit receivedCoil(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::DiscreteInputs: + emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::InputRegisters: + emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::HoldingRegisters: + emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.value(i)); + break; + case QModbusDataUnit::RegisterType::Invalid: + break; + } + } + } else if (reply->error() == QModbusDevice::ProtocolError) { + emit writeRequestFinished(requestId, false); + qCWarning(dcDrexelUndWeiss()) << "Read response error:" << reply->errorString() << reply->rawResult().exceptionCode(); + } else { + emit writeRequestFinished(requestId, false); + qCWarning(dcDrexelUndWeiss()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, &ModbusRTUMaster::onReplyErrorOccured); + QTimer::singleShot(200, reply, SLOT(deleteLater())); + } else { + delete reply; // broadcast replies return immediately + } + } else { + qCWarning(dcDrexelUndWeiss()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + } + return requestId; +} + +bool ModbusRTUMaster::readDiscreteInput(int slaveAddress, int registerAddress) +{ + if (!m_modbusRtuSerialMaster) + return false; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, registerAddress, 1); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, &ModbusRTUMaster::onReplyFinished); + connect(reply, &QModbusReply::errorOccurred, this, &ModbusRTUMaster::onReplyErrorOccured); + QTimer::singleShot(200, reply, SLOT(deleteLater())); + } else { + delete reply; // broadcast replies return immediately + } + } else { + qCWarning(dcDrexelUndWeiss()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + } + return true; +} + +bool ModbusRTUMaster::readInputRegister(int slaveAddress, int registerAddress) +{ + if (!m_modbusRtuSerialMaster) + return false; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, registerAddress, 1); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, &ModbusRTUMaster::onReplyFinished); + connect(reply, &QModbusReply::errorOccurred, this, &ModbusRTUMaster::onReplyErrorOccured); + QTimer::singleShot(200, reply, SLOT(deleteLater())); + } else { + delete reply; // broadcast replies return immediately + } + } else { + qCWarning(dcDrexelUndWeiss()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + } + return true; +} + +bool ModbusRTUMaster::readHoldingRegister(int slaveAddress, int registerAddress) +{ + if (!m_modbusRtuSerialMaster) + return false; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, 1); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, &ModbusRTUMaster::onReplyFinished); + connect(reply, &QModbusReply::errorOccurred, this, &ModbusRTUMaster::onReplyErrorOccured); + QTimer::singleShot(200, reply, SLOT(deleteLater())); + } else { + delete reply; // broadcast replies return immediately + } + } else { + qCWarning(dcDrexelUndWeiss()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + } + return true; +} + + +void ModbusRTUMaster::onModbusErrorOccurred(QModbusDevice::Error error) +{ + qCWarning(dcDrexelUndWeiss()) << "An error occured" << error; +} + + +void ModbusRTUMaster::onModbusStateChanged(QModbusDevice::State state) +{ + bool connected = (state != QModbusDevice::UnconnectedState); + if (!connected) { + //try to reconnect in 10 seconds + m_reconnectTimer->start(10000); + } + emit connectionStateChanged(connected); +} diff --git a/drexelundweiss/modbusrtumaster.h b/drexelundweiss/modbusrtumaster.h new file mode 100644 index 0000000..e281f73 --- /dev/null +++ b/drexelundweiss/modbusrtumaster.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 MODBUSRTUMASTER_H +#define MODBUSRTUMASTER_H + +#include +#include +#include +#include +#include + +class ModbusRTUMaster : public QObject +{ + Q_OBJECT +public: + explicit ModbusRTUMaster(QString serialPort, int baudrate, QSerialPort::Parity parity, int dataBits, int stopBits, QObject *parent = nullptr); + ~ModbusRTUMaster(); + + bool connectDevice(); + + bool readCoil(int slaveAddress, int registerAddress); + bool readDiscreteInput(int slaveAddress, int registerAddress); + bool readInputRegister(int slaveAddress, int registerAddress); + bool readHoldingRegister(int slaveAddress, int registerAddress); + + QUuid writeCoil(int slaveAddress, int registerAddress, bool status); + QUuid writeHoldingRegister(int slaveAddress, int registerAddress, int data); + + QString serialPort(); + +private: + QModbusRtuSerialMaster *m_modbusRtuSerialMaster; + QTimer *m_reconnectTimer = nullptr; + +private slots: + void onReplyFinished(); + void onReplyErrorOccured(QModbusDevice::Error error); + void onReconnectTimer(); + + void onModbusErrorOccurred(QModbusDevice::Error error); + void onModbusStateChanged(QModbusDevice::State state); + +signals: + void connectionStateChanged(bool status); + void receivedCoil(int slaveAddress, int modbusRegister, bool value); + void receivedDiscreteInput(int slaveAddress, int modbusRegister, bool value); + void receivedHoldingRegister(int slaveAddress, int modbusRegister, int value); + void receivedInputRegister(int slaveAddress, int modbusRegister, int value); + + void writeRequestFinished(QUuid requestId, bool success); +}; + +#endif // MODBUSRTUMASTER_H diff --git a/drexelundweiss/translations/68d78ce6-82d0-4a5b-b901-7c3b843ef63c-en_US.qm b/drexelundweiss/translations/68d78ce6-82d0-4a5b-b901-7c3b843ef63c-en_US.qm new file mode 100644 index 0000000..be651ee --- /dev/null +++ b/drexelundweiss/translations/68d78ce6-82d0-4a5b-b901-7c3b843ef63c-en_US.qm @@ -0,0 +1 @@ +<¸dÊÍ!¿`¡½Ý \ No newline at end of file diff --git a/drexelundweiss/translations/68d78ce6-82d0-4a5b-b901-7c3b843ef63c-en_US.ts b/drexelundweiss/translations/68d78ce6-82d0-4a5b-b901-7c3b843ef63c-en_US.ts new file mode 100644 index 0000000..6401616 --- /dev/null +++ b/drexelundweiss/translations/68d78ce6-82d0-4a5b-b901-7c3b843ef63c-en_US.ts @@ -0,0 +1,4 @@ + + + + diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro new file mode 100644 index 0000000..8f5a1f0 --- /dev/null +++ b/nymea-plugins-modbus.pro @@ -0,0 +1,45 @@ +TEMPLATE = subdirs + +PLUGIN_DIRS = \ + drexelundweiss \ + + +message(============================================) +message("Qt version:" $$[QT_VERSION]) + +plugininfo.depends = FORCE +for (entry, PLUGIN_DIRS):plugininfo.commands += test -d $${entry} || mkdir -p $${entry}; cd $${entry} && qmake -o Makefile $$PWD/$${entry}/$${entry}.pro && cd ..; +for (entry, PLUGIN_DIRS):plugininfo.commands += make -C $${entry} plugininfo.h; +QMAKE_EXTRA_TARGETS += plugininfo + +# Translations: +# make lupdate to update .ts files +lupdate.depends = FORCE plugininfo +for (entry, PLUGIN_DIRS):lupdate.commands += make -C $${entry} lupdate; +QMAKE_EXTRA_TARGETS += lupdate + +# make lrelease to build .qm from .ts +lrelease.depends = FORCE +for (entry, PLUGIN_DIRS):lrelease.commands += lrelease $$files($$PWD/$${entry}/translations/*.ts, true); +for (entry, PLUGIN_DIRS):lrelease.commands += rsync -a $$PWD/$${entry}/translations/*.qm $$OUT_PWD/translations/; +QMAKE_EXTRA_TARGETS += lrelease + +# For Qt-Creator's code model: Add CPATH to INCLUDEPATH explicitly +INCLUDEPATH += $$(CPATH) + +# Verify if building only a selection of plugins +contains(CONFIG, selection) { + # Check each plugin if the subdir exists + for(plugin, PLUGINS) { + contains(PLUGIN_DIRS, $${plugin}) { + SUBDIRS*= $${plugin} + } else { + error("Invalid plugin passed. There is no subdirectory with the name $${plugin}.") + } + } + message("Building plugin selection: $${SUBDIRS}") +} else { + SUBDIRS *= $${PLUGIN_DIRS} + message("Building all plugins") +} + diff --git a/plugins.pri b/plugins.pri new file mode 100644 index 0000000..f180f53 --- /dev/null +++ b/plugins.pri @@ -0,0 +1,17 @@ +isEmpty(PLUGIN_PRI) { + exists($$[QT_INSTALL_PREFIX]/include/nymea/plugin.pri) { + include($$[QT_INSTALL_PREFIX]/include/nymea/plugin.pri) + } else { + message("plugin.pri not found. Either install libnymea1-dev or use the PLUGIN_PRI argument to point to it.") + message("For building this project without nymea installed system-wide, you will want to export those variables in addition:") + message("PKG_CONFIG_PATH=/path/to/build-nymea/libnymea/pkgconfig/") + message("CPATH=/path/to/nymea/libnymea/") + message("LIBRARY_PATH=/path/to/build-nymea/libnymea/") + message("PATH=/path/to/build-nymea/tools/nymea-plugininfocompiler:$PATH") + message("LD_LIBRARY_PATH=/path/to/build-nymea/libnymea/") + error("plugin.pri not found. Cannot continue") + } +} else { +# message("Using $$PLUGIN_PRI") + include($$PLUGIN_PRI) +}