From 7d915296f4f5019b94256aa3033228bdde1e5023 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 2 Jun 2022 16:00:58 +0200 Subject: [PATCH] New Plugin: Bosswerk micro inverters --- bosswerk/README.md | 18 ++ bosswerk/bosswerk.png | Bin 0 -> 7200 bytes bosswerk/bosswerk.pro | 9 + bosswerk/integrationpluginbosswerk.cpp | 221 ++++++++++++++++++ bosswerk/integrationpluginbosswerk.h | 73 ++++++ bosswerk/integrationpluginbosswerk.json | 73 ++++++ bosswerk/meta.json | 13 ++ ...b7759-336d-4677-a014-1b0fd11f45ea-en_US.ts | 70 ++++++ debian/control | 8 + debian/nymea-plugin-bosswerk.install.in | 2 + nymea-plugins.pro | 1 + 11 files changed, 488 insertions(+) create mode 100644 bosswerk/README.md create mode 100644 bosswerk/bosswerk.png create mode 100644 bosswerk/bosswerk.pro create mode 100644 bosswerk/integrationpluginbosswerk.cpp create mode 100644 bosswerk/integrationpluginbosswerk.h create mode 100644 bosswerk/integrationpluginbosswerk.json create mode 100644 bosswerk/meta.json create mode 100644 bosswerk/translations/595b7759-336d-4677-a014-1b0fd11f45ea-en_US.ts create mode 100644 debian/nymea-plugin-bosswerk.install.in diff --git a/bosswerk/README.md b/bosswerk/README.md new file mode 100644 index 00000000..70e52287 --- /dev/null +++ b/bosswerk/README.md @@ -0,0 +1,18 @@ +# Bosswerk + +This integration plugin allows to add Bosswerk Micro Inverters to nymea. + +## Supported devices + +* MI-300 +* MI-600 + +## Requirements + +The solar inverter needs to be connected to the same network as nymea. Once powered on, the +inverter will open a wireless hotspot named AP_XXXXXXXXXX where XXXXXXXXXX is the serial number +written on the casing. The default password for this WiFi is 12345678. After connecting, +open [http://10.10.100.254](http://10.10.100.254) with the browser and use the web interface +on the inverter to connect it to a WiFi with nymea in it. + +Once done so, set up the inverter in nymea as any other thing. diff --git a/bosswerk/bosswerk.png b/bosswerk/bosswerk.png new file mode 100644 index 0000000000000000000000000000000000000000..e93b8e4e4d253cb45d71b47f8e2a64dc5aba59c7 GIT binary patch literal 7200 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 Kr78vUkpBaA<^iq% literal 0 HcmV?d00001 diff --git a/bosswerk/bosswerk.pro b/bosswerk/bosswerk.pro new file mode 100644 index 00000000..e3a95cdb --- /dev/null +++ b/bosswerk/bosswerk.pro @@ -0,0 +1,9 @@ +include(../plugins.pri) + +QT += network + +SOURCES += \ + integrationpluginbosswerk.cpp \ + +HEADERS += \ + integrationpluginbosswerk.h \ diff --git a/bosswerk/integrationpluginbosswerk.cpp b/bosswerk/integrationpluginbosswerk.cpp new file mode 100644 index 00000000..bbe9dbfc --- /dev/null +++ b/bosswerk/integrationpluginbosswerk.cpp @@ -0,0 +1,221 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginbosswerk.h" +#include "plugininfo.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +IntegrationPluginBosswerk::IntegrationPluginBosswerk() +{ + +} + +IntegrationPluginBosswerk::~IntegrationPluginBosswerk() +{ +} + +void IntegrationPluginBosswerk::discoverThings(ThingDiscoveryInfo *info) +{ + NetworkDeviceDiscoveryReply *reply = hardwareManager()->networkDeviceDiscovery()->discover(); + connect(reply, &NetworkDeviceDiscoveryReply::finished, info, [info, reply, this](){ + foreach (const NetworkDeviceInfo &deviceInfo, reply->networkDeviceInfos()) { +// qCDebug(dcBosswerk) << "Discovery result" << deviceInfo; + QString cover_mid = deviceInfo.hostName().split(".").first(); +// qCDebug(dcBosswerk()) << "Cover mid" << cover_mid; + + if (QRegExp("[0-9]{10}").exactMatch(cover_mid)) { + qCDebug(dcBosswerk()) << "Potential result:" << deviceInfo; + ThingDescriptor descriptor(mix00ThingClassId, "MI-300/600", deviceInfo.hostName()); + descriptor.setParams({Param(mix00ThingMacAddressParamTypeId, deviceInfo.macAddress())}); + Thing *existingThing = myThings().findByParams({Param(mix00ThingMacAddressParamTypeId, deviceInfo.macAddress())}); + if (existingThing) { + descriptor.setThingId(existingThing->id()); + } + info->addThingDescriptor(descriptor); + } + } + qCInfo(dcBosswerk) << "Discovery finished." << info->thingDescriptors().count() << "results."; + info->finish(Thing::ThingErrorNoError); + }); +} + +void IntegrationPluginBosswerk::startPairing(ThingPairingInfo *info) +{ + info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter your login credentials.")); +} + +void IntegrationPluginBosswerk::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) +{ + MacAddress mac(info->params().paramValue(mix00ThingMacAddressParamTypeId).toString()); + + QHash cache = hardwareManager()->networkDeviceDiscovery()->cache(); + + if (!cache.contains(mac)) { + qCWarning(dcBosswerk()) << "MAC" << mac << "not found in network device cache."; + info->finish(Thing::ThingErrorItemNotFound, QT_TR_NOOP("An error happened in the network communication.")); + return; + } + + NetworkDeviceInfo networkDeviceInfo = cache.value(mac); + + QUrl url("http://" + username + ":" + secret + "@" + networkDeviceInfo.address().toString() + "/status.html"); + QNetworkRequest request(url); + + qCDebug(dcBosswerk) << "Requesting" << request.url(); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, info, [=](){ + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcBosswerk()) << "Error logging in at inverter" << reply->error(); + info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to log in at the inverter.")); + return; + } + + QByteArray payload = reply->readAll(); + qCDebug(dcBosswerk()) << "Reply data:" << payload; + + pluginStorage()->beginGroup(info->thingId().toString()); + pluginStorage()->setValue("username", username); + pluginStorage()->setValue("password", secret); + pluginStorage()->endGroup(); + + info->finish(Thing::ThingErrorNoError); + }); +} + +void IntegrationPluginBosswerk::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + NetworkDeviceMonitor *monitor = m_deviceMonitors.take(thing); + if (monitor) { + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor); + } + PluginTimer *timer = m_timers.take(thing); + if (timer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(timer); + } + + monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(MacAddress(thing->paramValue(mix00ThingMacAddressParamTypeId).toString())); + m_deviceMonitors.insert(thing, monitor); + + timer = hardwareManager()->pluginTimerManager()->registerTimer(5); + m_timers.insert(thing, timer); + + connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [timer, thing](bool reachable) { + thing->setStateValue(mix00ConnectedStateTypeId, reachable); + if (reachable) { + timer->start(); + } else { + timer->stop(); + thing->setStateValue(mix00CurrentPowerStateTypeId, 0); + } + }); + + connect(timer, &PluginTimer::timeout, thing, [this, thing](){ + pollDevice(thing); + }); + + pollDevice(thing); + + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginBosswerk::thingRemoved(Thing *thing) +{ + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_deviceMonitors.take(thing)); + hardwareManager()->pluginTimerManager()->unregisterTimer(m_timers.take(thing)); +} + +void IntegrationPluginBosswerk::pollDevice(Thing *thing) +{ + NetworkDeviceMonitor *monitor = m_deviceMonitors.value(thing); + + pluginStorage()->beginGroup(thing->id().toString()); + QString username = pluginStorage()->value("username").toString(); + QString password = pluginStorage()->value("password").toString(); + pluginStorage()->endGroup(); + + QUrl url("http://" + username + ":" + password + "@" + monitor->networkDeviceInfo().address().toString() + "/status.html"); + qCDebug(dcBosswerk()) << "Requesting" << url.toString(); + QNetworkRequest request(url); + + QNetworkReply *statusReply = hardwareManager()->networkManager()->get(request); + connect(statusReply, &QNetworkReply::finished, statusReply, &QNetworkReply::deleteLater); + connect(statusReply, &QNetworkReply::finished, thing, [=](){ + if (statusReply->error() != QNetworkReply::NoError) { + qCWarning(dcBosswerk) << "Error polling" << thing->name() << ":" << statusReply->error() << statusReply->errorString(); + return; + } + QByteArray data = statusReply->readAll(); + +// qCDebug(dcBosswerk) << "Status:" << qUtf8Printable(data); + foreach (const QString &line, QString(data).split("\n")) { + if (line.startsWith("var ")) { + qCDebug(dcBosswerk()) << "Data line:" << line; + } + if (line.startsWith("var webdata_now_p")) { + bool ok; + double currentPower = line.split("\"").at(1).toDouble(&ok); + if (ok) { + thing->setStateValue(mix00CurrentPowerStateTypeId, -currentPower); + } + } + if (line.startsWith("var webdata_total_e")) { + bool ok; + double totalEnergyProduced = line.split("\"").at(1).toDouble(&ok); + if (ok && totalEnergyProduced != 0) { // When it's not producing anything, it will also give 0 for the total. Ignoring that... + thing->setStateValue(mix00TotalEnergyProducedStateTypeId, totalEnergyProduced); + } + } + if (line.startsWith("var cover_sta_rssi")) { + bool ok; + QString rssiString = line.split("\"").at(1); + rssiString.remove("%"); + int rssi = rssiString.toInt(&ok); + if (ok) { + thing->setStateValue(mix00SignalStrengthStateTypeId, rssi); + } + } + } + }); +} diff --git a/bosswerk/integrationpluginbosswerk.h b/bosswerk/integrationpluginbosswerk.h new file mode 100644 index 00000000..ba1a08f5 --- /dev/null +++ b/bosswerk/integrationpluginbosswerk.h @@ -0,0 +1,73 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 INTEGRATIONPLUGINMEROSS_H +#define INTEGRATIONPLUGINMEROSS_H + +#include "integrations/integrationplugin.h" +#include "extern-plugininfo.h" + +class PluginTimer; +class NetworkDeviceMonitor; +class QNetworkReply; + +class IntegrationPluginBosswerk: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginbosswerk.json") + Q_INTERFACES(IntegrationPlugin) + +public: + enum Method { + GET, + SET + }; + Q_ENUM(Method) + + explicit IntegrationPluginBosswerk(); + ~IntegrationPluginBosswerk(); + + void discoverThings(ThingDiscoveryInfo *info) override; + void startPairing(ThingPairingInfo *info) override; + void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override; + + void setupThing(ThingSetupInfo *info) override; + void thingRemoved(Thing *thing) override; + +private slots: + void pollDevice(Thing *thing); + +private: + QHash m_deviceMonitors; + QHash m_timers; +}; + +#endif // INTEGRATIONPLUGINMEROSS_H diff --git a/bosswerk/integrationpluginbosswerk.json b/bosswerk/integrationpluginbosswerk.json new file mode 100644 index 00000000..e0910f63 --- /dev/null +++ b/bosswerk/integrationpluginbosswerk.json @@ -0,0 +1,73 @@ +{ + "name": "bosswerk", + "displayName": "Bosswerk", + "id": "595b7759-336d-4677-a014-1b0fd11f45ea", + "vendors": [ + { + "name": "bosswerk", + "displayName": "Bosswerk", + "id": "26ec1591-cc37-4ac1-b943-04844e002601", + "thingClasses": [ + { + "id": "31ee3e61-eb3f-470b-8957-293fe65f404d", + "name": "mix00", + "displayName": "MI-300/600", + "createMethods": ["discovery"], + "setupMethod": "userandpassword", + "interfaces": [ "solarinverter", "wirelessconnectable" ], + "paramTypes": [ + { + "id": "6fbe5f08-3539-447d-9281-916abe9d8128", + "name":"macAddress", + "displayName": "MAC address", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "b1a9bdf7-1c87-4c5d-b7e5-835697e7b7e5", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "4187873d-50dd-4470-8bd1-2787436db84d", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "type": "uint", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0, + "filter": "adaptive", + "cached": false + }, + { + "id": "044c26ce-67a7-4c81-99b1-4aa35285b109", + "name": "currentPower", + "displayName": "Current power consumption", + "displayNameEvent": "Current power consumption changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0, + "cached": false + }, + { + "id": "4a596301-3a8d-41de-bc97-275d23c0e5cd", + "name": "totalEnergyProduced", + "displayName": "Total produced energy", + "displayNameEvent": "Total produced energy changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ] + } + ] + } + ] +} diff --git a/bosswerk/meta.json b/bosswerk/meta.json new file mode 100644 index 00000000..c6aa1fee --- /dev/null +++ b/bosswerk/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Bosswerk", + "tagline": "Integrates Bosswerk solar inverters with nymea.", + "icon": "bosswerk.jpg", + "stability": "consumer", + "offline": true, + "technologies": [ + "network" + ], + "categories": [ + "energy" + ] +} diff --git a/bosswerk/translations/595b7759-336d-4677-a014-1b0fd11f45ea-en_US.ts b/bosswerk/translations/595b7759-336d-4677-a014-1b0fd11f45ea-en_US.ts new file mode 100644 index 00000000..7f795b8d --- /dev/null +++ b/bosswerk/translations/595b7759-336d-4677-a014-1b0fd11f45ea-en_US.ts @@ -0,0 +1,70 @@ + + + + + IntegrationPluginBosswerk + + + Please enter your login credentials. + + + + + An error happened in the network communication. + + + + + Failed to log in at the inverter. + + + + + bosswerk + + + + Bosswerk + The name of the vendor ({26ec1591-cc37-4ac1-b943-04844e002601}) +---------- +The name of the plugin bosswerk ({595b7759-336d-4677-a014-1b0fd11f45ea}) + + + + + Connected + The name of the StateType ({b1a9bdf7-1c87-4c5d-b7e5-835697e7b7e5}) of ThingClass mix00 + + + + + Current power consumption + The name of the StateType ({044c26ce-67a7-4c81-99b1-4aa35285b109}) of ThingClass mix00 + + + + + MAC address + The name of the ParamType (ThingClass: mix00, Type: thing, ID: {6fbe5f08-3539-447d-9281-916abe9d8128}) + + + + + MI-300/600 + The name of the ThingClass ({31ee3e61-eb3f-470b-8957-293fe65f404d}) + + + + + Signal strength + The name of the StateType ({4187873d-50dd-4470-8bd1-2787436db84d}) of ThingClass mix00 + + + + + Total produced energy + The name of the StateType ({4a596301-3a8d-41de-bc97-275d23c0e5cd}) of ThingClass mix00 + + + + diff --git a/debian/control b/debian/control index f63e1acf..d1ed51d0 100644 --- a/debian/control +++ b/debian/control @@ -90,6 +90,14 @@ Description: nymea integration plugin for bose soundtouch This package contains the nymea integration plugin for bose soundtouch devices. +Package: nymea-plugin-bosswerk +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: nymea integration plugin for Bosswerk micro inverters + This package contains the nymea integration plugin for Bosswerk micro inverters. + + Package: nymea-plugin-coinmarketcap Architecture: any Depends: ${shlibs:Depends}, diff --git a/debian/nymea-plugin-bosswerk.install.in b/debian/nymea-plugin-bosswerk.install.in new file mode 100644 index 00000000..3cf04b21 --- /dev/null +++ b/debian/nymea-plugin-bosswerk.install.in @@ -0,0 +1,2 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginbosswerk.so +bosswerk/translations/*qm usr/share/nymea/translations/ diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 6bd0c1c4..44334f91 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -8,6 +8,7 @@ PLUGIN_DIRS = \ bluos \ boblight \ bose \ + bosswerk \ coinmarketcap \ commandlauncher \ datetime \