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 00000000..e93b8e4e
Binary files /dev/null and b/bosswerk/bosswerk.png differ
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 ece06626..3fe40c54 100644
--- a/debian/control
+++ b/debian/control
@@ -99,6 +99,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 dae77734..714d90c1 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -9,6 +9,7 @@ PLUGIN_DIRS = \
bluos \
boblight \
bose \
+ bosswerk \
coinmarketcap \
commandlauncher \
datetime \