diff --git a/debian/control b/debian/control index f129778b..ddaa1241 100644 --- a/debian/control +++ b/debian/control @@ -185,6 +185,14 @@ Description: nymea integration plugin for Schrack wallboxes This package contains the nymea integration plugin for Schrack wallboxes. +Package: nymea-plugin-senseair +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: nymea integration plugin for Senseair sensors + This package contains the nymea integration plugin for Senseair sensors. + + Package: nymea-plugin-sma Architecture: any Depends: ${shlibs:Depends}, diff --git a/debian/nymea-plugin-senseair.install.in b/debian/nymea-plugin-senseair.install.in new file mode 100644 index 00000000..8dbaf24a --- /dev/null +++ b/debian/nymea-plugin-senseair.install.in @@ -0,0 +1,2 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsenseair.so +senseair/translations/*qm usr/share/nymea/translations/ diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index 5387f044..d0192021 100644 --- a/nymea-plugins-modbus.pro +++ b/nymea-plugins-modbus.pro @@ -18,6 +18,7 @@ PLUGIN_DIRS = \ mypv \ phoenixconnect \ schrack \ + senseair \ sma \ stiebeleltron \ sunspec \ diff --git a/senseair/README.md b/senseair/README.md new file mode 100644 index 00000000..a441cee7 --- /dev/null +++ b/senseair/README.md @@ -0,0 +1,10 @@ +# Senseair + +Connects Senseair sensors to nymea. Currently supported sensors are: + +* S8 CO2 sensor + +# Requirements + +* A working RS485 (Modbus RTU) connection to the sensor. +* A Modbus RTU master configured in nymea with baudrate 9600, 8 data bits, 1 stop bits and no parity. diff --git a/senseair/ba-senseair-s8-modbus.pdf b/senseair/ba-senseair-s8-modbus.pdf new file mode 100644 index 00000000..3ea41d64 Binary files /dev/null and b/senseair/ba-senseair-s8-modbus.pdf differ diff --git a/senseair/integrationpluginsenseair.cpp b/senseair/integrationpluginsenseair.cpp new file mode 100644 index 00000000..b3e44909 --- /dev/null +++ b/senseair/integrationpluginsenseair.cpp @@ -0,0 +1,147 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 "integrationpluginsenseair.h" +#include "plugininfo.h" +#include "hardwaremanager.h" +#include "hardware/modbus/modbusrtuhardwareresource.h" + +IntegrationPluginSenseAir::IntegrationPluginSenseAir() +{ + +} + +void IntegrationPluginSenseAir::discoverThings(ThingDiscoveryInfo *info) +{ + foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) { + qCDebug(dcSenseAir()) << "Found RTU master resource" << modbusMaster; + if (modbusMaster->connected() && modbusMaster->baudrate() == 9600 && modbusMaster->dataBits() == 8 && modbusMaster->stopBits() == 1 && modbusMaster->parity() == QSerialPort::NoParity) { + ParamList parameters; + ThingDescriptor thingDescriptor(s8ThingClassId, "Modbus RTU master", modbusMaster->serialPort()); + parameters.append(Param(s8ThingRtuMasterParamTypeId, modbusMaster->modbusUuid())); + thingDescriptor.setParams(parameters); + info->addThingDescriptor(thingDescriptor); + } else { + qCWarning(dcSenseAir()) << "Found configured resource" << modbusMaster << "but it is not connected. Skipping."; + } + } + + QString displayMessage; + if (info->thingDescriptors().count() == 0) { + displayMessage = QT_TR_NOOP("Please set up a Modbus RTU master with baudrate 9600, 8 data bits, 1 stop bits and no parity first."); + } + + info->finish(Thing::ThingErrorNoError, displayMessage); +} + +void IntegrationPluginSenseAir::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + qCDebug(dcSenseAir()) << "Setup" << thing << thing->params(); + + + if (m_s8Connections.contains(thing)) { + qCDebug(dcSenseAir()) << "Reconfiguring existing thing" << thing->name(); + m_s8Connections.take(thing)->deleteLater(); + } + + ModbusRtuMaster *master = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(thing->paramValue(s8ThingRtuMasterParamTypeId).toUuid()); + if (!master) { + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The Modbus RTU master is not available.")); + return; + } + + SenseAirS8ModbusRtuConnection *s8Connection = new SenseAirS8ModbusRtuConnection(master, 0xfe, this); + connect(info, &ThingSetupInfo::aborted, s8Connection, &SenseAirS8ModbusRtuConnection::deleteLater); + + connect(s8Connection, &SenseAirS8ModbusRtuConnection::reachableChanged, thing, [s8Connection, thing](bool reachable){ + qCDebug(dcSenseAir()) << "Reachable state changed" << reachable; + if (reachable) { + s8Connection->initialize(); + } else { + thing->setStateValue(s8ConnectedStateTypeId, false); + } + }); + connect(s8Connection, &SenseAirS8ModbusRtuConnection::initializationFinished, info, [=](bool success){ + qCDebug(dcSenseAir()) << "Initialisation finished" << success; + if (success) { + qCDebug(dcSenseAir()) << "Meter status:" << s8Connection->meterStatus(); + m_s8Connections.insert(thing, s8Connection); + info->finish(Thing::ThingErrorNoError); + } else { + delete s8Connection; + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + }); + connect(s8Connection, &SenseAirS8ModbusRtuConnection::initializationFinished, thing, [=](bool success){ + if (success) { + thing->setStateValue(s8ConnectedStateTypeId, true); + } + }); + + connect(s8Connection, &SenseAirS8ModbusRtuConnection::spaceCo2Changed, thing, [=](quint16 spaceCo2){ + qCDebug(dcSenseAir()) << "CO2 changed:" << spaceCo2; + thing->setStateValue(s8Co2StateTypeId, spaceCo2); + }); + + s8Connection->update(); +} + +void IntegrationPluginSenseAir::postSetupThing(Thing *thing) +{ + Q_UNUSED(thing) + if (!m_pluginTimer) { + qCDebug(dcSenseAir()) << "Starting plugin timer..."; + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { + foreach(SenseAirS8ModbusRtuConnection *connection, m_s8Connections) { + qCDebug(dcSenseAir()) << "Updating..."; + if (connection->reachable()) { + connection->update(); + } + } + }); + + m_pluginTimer->start(); + + } +} + +void IntegrationPluginSenseAir::thingRemoved(Thing *thing) +{ + SenseAirS8ModbusRtuConnection *connection = m_s8Connections.take(thing); + delete connection; + + + if (myThings().isEmpty() && m_pluginTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} diff --git a/senseair/integrationpluginsenseair.h b/senseair/integrationpluginsenseair.h new file mode 100644 index 00000000..d4d3cb81 --- /dev/null +++ b/senseair/integrationpluginsenseair.h @@ -0,0 +1,64 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 INTEGRATIONPLUGINSENSEAIR_H +#define INTEGRATIONPLUGINSENSEAIR_H + +#include +#include + +#include "extern-plugininfo.h" +#include "senseairs8modbusrtuconnection.h" + +class IntegrationPluginSenseAir: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsenseair.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginSenseAir(); + + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void thingRemoved(Thing *thing) override; + +private: + + PluginTimer *m_pluginTimer = nullptr; + QHash m_s8Connections; + +}; + +#endif // INTEGRATIONPLUGINSENSEAIR_H + + diff --git a/senseair/integrationpluginsenseair.json b/senseair/integrationpluginsenseair.json new file mode 100644 index 00000000..1a70518e --- /dev/null +++ b/senseair/integrationpluginsenseair.json @@ -0,0 +1,50 @@ +{ + "name": "SenseAir", + "displayName": "Senseair", + "id": "c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38", + "vendors": [ + { + "name": "senseAir", + "displayName": "SenseAir", + "id": "f8427109-ada2-4428-9cfe-61631f763f5a", + "thingClasses": [ + { + "name": "s8", + "displayName": "SenseAir S8", + "id": "b7c80f1c-b583-4e81-a161-c7e1a0b13ea0", + "createMethods": ["discovery"], + "interfaces": ["co2sensor", "connectable"], + "paramTypes": [ + { + "id": "5de7968f-e433-4143-bd0f-b8c3776782b7", + "name":"rtuMaster", + "displayName": "Modbus RTU master UUID", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes": [ + { + "id": "71d6ba02-ca41-41f6-af87-f6e60d371591", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "7506d7c3-ebf7-408b-9625-03be99736a00", + "name": "co2", + "displayName": "CO2", + "type": "double", + "unit": "PartsPerMillion", + "defaultValue": 0, + "minValue": 0, + "maxValue": 2000 + } + ] + } + ] + } + ] +} diff --git a/senseair/meta.json b/senseair/meta.json new file mode 100644 index 00000000..0d860813 --- /dev/null +++ b/senseair/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Senseair", + "tagline": "Connect Senseair sensors nymea.", + "icon": "senseair.png", + "stability": "consumer", + "offline": true, + "technologies": [ + "modbus" + ], + "categories": [ + "sensor" + ] +} diff --git a/senseair/s8-registers.json b/senseair/s8-registers.json new file mode 100644 index 00000000..f9da1f59 --- /dev/null +++ b/senseair/s8-registers.json @@ -0,0 +1,31 @@ +{ + "className": "SenseAirS8", + "protocol": "RTU", + "errorLimitUntilNotReachable": 20, + "endianness": "BigEndian", + "checkReachableRegister": "spaceCo2", + "registers": [ + { + "id": "meterStatus", + "address": 0, + "size": 1, + "type": "uint16", + "readSchedule": "init", + "registerType": "inputRegister", + "description": "Meter status", + "defaultValue": 0, + "access": "RO" + }, + { + "id": "spaceCo2", + "address": 3, + "size": 1, + "type": "uint16", + "readSchedule": "update", + "registerType": "inputRegister", + "description": "Space CO2", + "defaultValue": 0, + "access": "RO" + } + ] +} diff --git a/senseair/senseair.png b/senseair/senseair.png new file mode 100644 index 00000000..30a78553 Binary files /dev/null and b/senseair/senseair.png differ diff --git a/senseair/senseair.pro b/senseair/senseair.pro new file mode 100644 index 00000000..c1fb4c0a --- /dev/null +++ b/senseair/senseair.pro @@ -0,0 +1,13 @@ +include(../plugins.pri) + +# Generate modbus connection +MODBUS_CONNECTIONS += s8-registers.json +MODBUS_TOOLS_CONFIG += VERBOSE + +include(../modbus.pri) + +HEADERS += \ + integrationpluginsenseair.h + +SOURCES += \ + integrationpluginsenseair.cpp diff --git a/senseair/translations/c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38-en_US.ts b/senseair/translations/c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38-en_US.ts new file mode 100644 index 00000000..44fbfcfc --- /dev/null +++ b/senseair/translations/c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38-en_US.ts @@ -0,0 +1,56 @@ + + + + + IntegrationPluginSenseAir + + + Please set up a Modbus RTU master with baudrate 9600, 8 data bits, 1 stop bits and no parity first. + + + + + The Modbus RTU master is not available. + + + + + SenseAir + + + CO2 + The name of the StateType ({7506d7c3-ebf7-408b-9625-03be99736a00}) of ThingClass s8 + + + + + Connected + The name of the StateType ({71d6ba02-ca41-41f6-af87-f6e60d371591}) of ThingClass s8 + + + + + Modbus RTU master UUID + The name of the ParamType (ThingClass: s8, Type: thing, ID: {5de7968f-e433-4143-bd0f-b8c3776782b7}) + + + + + SenseAir + The name of the vendor ({f8427109-ada2-4428-9cfe-61631f763f5a}) + + + + + SenseAir S8 + The name of the ThingClass ({b7c80f1c-b583-4e81-a161-c7e1a0b13ea0}) + + + + + Senseair + The name of the plugin SenseAir ({c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38}) + + + +