diff --git a/debian/control b/debian/control
index eefc9d4..7eab6a1 100644
--- a/debian/control
+++ b/debian/control
@@ -136,7 +136,17 @@ Section: libs
Depends: ${shlibs:Depends},
${misc:Depends}
Description: nymea.io plugin for Kostal Solar inverters
- This package will install the nymea.io plugin for Kostal Solar inverters
+ This package contains the nymea.io plugin for Kostal Solar inverters
+
+
+Package: nymea-plugin-mennekes
+Architecture: any
+Multi-Arch: same
+Section: libs
+Depends: ${shlibs:Depends},
+ ${misc:Depends}
+Description: nymea.io plugin for Mennekes wallboxes
+ This package contains the nymea.io plugin for Mennekes wallboxes
Package: nymea-plugin-modbuscommander
diff --git a/debian/nymea-plugin-mennekes.install.in b/debian/nymea-plugin-mennekes.install.in
new file mode 100644
index 0000000..af89e83
--- /dev/null
+++ b/debian/nymea-plugin-mennekes.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginmennekes.so
+mennekes/translations/*qm usr/share/nymea/translations/
diff --git a/libnymea-modbus/tools/connectiontool/modbustcp.py b/libnymea-modbus/tools/connectiontool/modbustcp.py
index 6513107..ae3f11e 100644
--- a/libnymea-modbus/tools/connectiontool/modbustcp.py
+++ b/libnymea-modbus/tools/connectiontool/modbustcp.py
@@ -79,7 +79,7 @@ def writePropertyGetSetMethodImplementationsTcp(fileDescriptor, className, regis
def writePropertyUpdateMethodImplementationsTcp(fileDescriptor, className, registerDefinitions):
for registerDefinition in registerDefinitions:
- if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
+ if not 'readSchedule' in registerDefinition or registerDefinition['readSchedule'] == 'init':
continue
propertyName = registerDefinition['id']
diff --git a/mennekes/Modbus_AMTRON HCC3_v01_2021-06-25_en.pdf b/mennekes/Modbus_AMTRON HCC3_v01_2021-06-25_en.pdf
new file mode 100644
index 0000000..fef1a07
Binary files /dev/null and b/mennekes/Modbus_AMTRON HCC3_v01_2021-06-25_en.pdf differ
diff --git a/mennekes/Modbus_AMTRON_ACU_modbus_tcp_server_spec_rev_1.07.pdf b/mennekes/Modbus_AMTRON_ACU_modbus_tcp_server_spec_rev_1.07.pdf
new file mode 100644
index 0000000..c5f9d9c
Binary files /dev/null and b/mennekes/Modbus_AMTRON_ACU_modbus_tcp_server_spec_rev_1.07.pdf differ
diff --git a/mennekes/README.md b/mennekes/README.md
new file mode 100644
index 0000000..091497c
--- /dev/null
+++ b/mennekes/README.md
@@ -0,0 +1,23 @@
+# MENNEKES
+
+Connects nymea to a MENNEKES wallboxes. Currently supported models are:
+
+* Amtron Xtra
+* Amtron Premium
+* Amtron Professional
+* Amtron Charge Control
+* Amedio Professional
+
+# Requirements
+
+nymea uses the Modbus TCP connection to connect to the wallbox.
+
+> The Modbus TCP connection needs to be enabled manually on the Wallbox.
+
+For the Amtron Charge Control and Premium models, log in to the wallbox's web interface as operator. The login credentials can be obtained
+from the user manual of the wallbox. Once logged in, navigate to the Load Management tab and set the Modbus TCP Server to On.
+
+## More
+
+It is highly recommended to update the wallbox to the latest firmware which can be downloaded from [here](https://www.chargeupyourday.de/services/software-updates/).
+
diff --git a/mennekes/amtron-ecu-registers.json b/mennekes/amtron-ecu-registers.json
new file mode 100644
index 0000000..3f95959
--- /dev/null
+++ b/mennekes/amtron-ecu-registers.json
@@ -0,0 +1,302 @@
+{
+ "className": "AmtronECU",
+ "protocol": "TCP",
+ "endianness": "BigEndian",
+ "errorLimitUntilNotReachable": 20,
+ "checkReachableRegister": "cpSignalState",
+ "enums": [
+ {
+ "name": "CPSignalState",
+ "values": [
+ {
+ "key": "A",
+ "value": 1
+ },
+ {
+ "key": "B",
+ "value": 2
+ },
+ {
+ "key": "C",
+ "value": 3
+ },
+ {
+ "key": "D",
+ "value": 4
+ },
+ {
+ "key": "E",
+ "value": 5
+ }
+ ]
+ }
+ ],
+ "blocks": [
+ {
+ "id": "consumptions",
+ "readSchedule": "update",
+ "registers": [
+ {
+ "id": "meterEnergyL1",
+ "address": 200,
+ "size": 2,
+ "type": "uint32",
+ "unit": "Wh",
+ "registerType": "holdingRegister",
+ "description": "Meter energy L1",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterEnergyL2",
+ "address": 202,
+ "size": 2,
+ "type": "uint32",
+ "unit": "Wh",
+ "registerType": "holdingRegister",
+ "description": "Meter energy L2",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterEnergyL3",
+ "address": 204,
+ "size": 2,
+ "type": "uint32",
+ "unit": "Wh",
+ "registerType": "holdingRegister",
+ "description": "Meter energy L3",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterPowerL1",
+ "address": 206,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter power L1",
+ "unit": "W",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "meterPowerL2",
+ "address": 208,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter power L2",
+ "unit": "W",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "meterPowerL3",
+ "address": 210,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter power L3",
+ "unit": "W",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "meterCurrentL1",
+ "address": 212,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter current L1",
+ "unit": "mA",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterCurrentL2",
+ "address": 214,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter current L2",
+ "unit": "mA",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterCurrentL3",
+ "address": 216,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter current L3",
+ "unit": "mA",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterTotoalEnergy",
+ "address": 218,
+ "size": 2,
+ "type": "uint32",
+ "unit": "Wh",
+ "registerType": "holdingRegister",
+ "description": "Meter total energy",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterTotalPower",
+ "address": 220,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter totoal power",
+ "unit": "W",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterVoltageL1",
+ "address": 222,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter voltage L1",
+ "unit": "V",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterVoltageL2",
+ "address": 224,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter voltage L2",
+ "unit": "V",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "meterVoltageL3",
+ "address": 226,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "holdingRegister",
+ "description": "Meter voltage L3",
+ "unit": "V",
+ "defaultValue": "0",
+ "access": "RO"
+ }
+ ]
+ }
+ ],
+ "registers": [
+ {
+ "id": "firmwareVersion",
+ "address": 100,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "init",
+ "registerType": "holdingRegister",
+ "description": "Firmware version",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "cpSignalState",
+ "address": 122,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "CP signal state",
+ "enum": "CPSignalState",
+ "defaultValue": "CPSignalStateA",
+ "access": "RO"
+ },
+ {
+ "id": "cpAvailability",
+ "address": 124,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Charge Point availability",
+ "defaultValue": 0,
+ "access": "RW"
+ },
+ {
+ "id": "model",
+ "address": 142,
+ "size": 10,
+ "type": "string",
+ "readSchedule": "init",
+ "registerType": "holdingRegister",
+ "description": "Device model",
+ "access": "RO"
+ },
+ {
+ "id": "signalledCurrent",
+ "address": 706,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Signalled current to EV",
+ "unit": "A",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "minCurrentLimit",
+ "address": 712,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Minimum current limit",
+ "unit": "A",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "maxCurrentLimit",
+ "address": 715,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Maximum current limit",
+ "unit": "A",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "chargedEnergy",
+ "address": 716,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Charged energy for current session",
+ "unit": "Wh",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "hemsCurrentLimit",
+ "address": 1000,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "HEMS current limit",
+ "unit": "A",
+ "defaultValue": "0",
+ "access": "RW"
+ }
+ ]
+}
diff --git a/mennekes/amtron-hcc3-registers.json b/mennekes/amtron-hcc3-registers.json
new file mode 100644
index 0000000..c17a910
--- /dev/null
+++ b/mennekes/amtron-hcc3-registers.json
@@ -0,0 +1,368 @@
+{
+ "className": "AmtronHCC3",
+ "protocol": "TCP",
+ "endianness": "LittleEndian",
+ "errorLimitUntilNotReachable": 20,
+ "checkReachableRegister": "customerCurrentLimitation",
+ "enums": [
+ {
+ "name": "CPSignalState",
+ "values": [
+ {
+ "key": "A1",
+ "value": 1
+ },
+ {
+ "key": "A2",
+ "value": 2
+ },
+ {
+ "key": "B1",
+ "value": 3
+ },
+ {
+ "key": "B2",
+ "value": 4
+ },
+ {
+ "key": "C1",
+ "value": 5
+ },
+ {
+ "key": "C2",
+ "value": 6
+ },
+ {
+ "key": "D1",
+ "value": 7
+ },
+ {
+ "key": "D2",
+ "value": 8
+ }
+ ]
+ },
+ {
+ "name": "PPState",
+ "values": [
+ {
+ "key": "Illegal",
+ "value": 0
+ },
+ {
+ "key": "Open",
+ "value": 1
+ },
+ {
+ "key": "13A",
+ "value": 2
+ },
+ {
+ "key": "20A",
+ "value": 3
+ },
+ {
+ "key": "32A",
+ "value": 4
+ }
+ ]
+ },
+ {
+ "name": "ChargeState",
+ "values": [
+ {
+ "key": "Pause",
+ "value": 1
+ },
+ {
+ "key": "Continue",
+ "value": 2
+ },
+ {
+ "key": "Terminate",
+ "value": 3
+ },
+ {
+ "key": "Start",
+ "value": 4
+ }
+ ]
+ },
+ {
+ "name": "HCC3ErrorCode",
+ "values": [
+ {
+ "key": "NoError",
+ "value": 0
+ },
+ {
+ "key": "InstallationFault",
+ "value": 10
+ },
+ {
+ "key": "ControllerFault",
+ "value": 11
+ },
+ {
+ "key": "Misconfiguration",
+ "value": 12
+ },
+ {
+ "key": "Overtemperature",
+ "value": 13
+ },
+ {
+ "key": "MirrorContactorError",
+ "value": 14
+ },
+ {
+ "key": "InvalidDeviceTime",
+ "value": 15
+ },
+ {
+ "key": "EnergyManagerConnectionError",
+ "value": 16
+ },
+ {
+ "key": "DeviceStartup",
+ "value": 30
+ },
+ {
+ "key": "InternalTestNotPassed",
+ "value": 31
+ },
+ {
+ "key": "HMINoConnection",
+ "value": 32
+ },
+ {
+ "key": "BadlyPluggedCable",
+ "value": 50
+ },
+ {
+ "key": "WrongCable",
+ "value": 51
+ },
+ {
+ "key": "DefectCable",
+ "value": 52
+ },
+ {
+ "key": "ACUCommunicationError",
+ "value": 100
+ },
+ {
+ "key": "NotPolledByACU",
+ "value": 101
+ },
+ {
+ "key": "Maintenance",
+ "value": 102
+ },
+ {
+ "key": "Disabled",
+ "value": 103
+ },
+ {
+ "key": "UnknownError",
+ "value": 255
+ }
+ ]
+ },
+ {
+ "name": "AmtronState",
+ "values": [
+ {
+ "key": "Idle",
+ "value": 0
+ },
+ {
+ "key": "StandByAuthorize",
+ "value": 1
+ },
+ {
+ "key": "StandbyConnect",
+ "value": 2
+ },
+ {
+ "key": "Charging",
+ "value": 3
+ },
+ {
+ "key": "Paused",
+ "value": 4
+ },
+ {
+ "key": "Terminated",
+ "value": 5
+ },
+ {
+ "key": "Error",
+ "value": 6
+ }
+ ]
+ }
+ ],
+ "blocks": [
+ {
+ "id": "states",
+ "readSchedule": "update",
+ "registers": [
+ {
+ "id": "cpSignalState",
+ "address": 770,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "CP signal state",
+ "enum": "CPSignalState",
+ "defaultValue": "CPSignalStateA1",
+ "access": "RO"
+ },
+ {
+ "id": "ppState",
+ "address": 771,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "PP state",
+ "enum": "PPState",
+ "defaultValue": "PPStateIllegal",
+ "access": "RO"
+ },
+ {
+ "id": "hcc3ErrorCode",
+ "address": 772,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "HCC3 Error Code",
+ "enum": "HCC3ErrorCode",
+ "defaultValue": "HCC3ErrorCodeNoError",
+ "access": "RO"
+ },
+ {
+ "id": "amtronState",
+ "address": 773,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "AMTRON state",
+ "enum": "AmtronState",
+ "defaultValue": "AmtronStateIdle",
+ "access": "RO"
+ }
+ ]
+ },
+ {
+ "id": "maxValues",
+ "readSchedule": "update",
+ "registers": [
+ {
+ "id": "phaseCount",
+ "address": 776,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "Phase count",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "ratedCurrent",
+ "address": 777,
+ "size": 1,
+ "type": "uint16",
+ "unit": "A",
+ "registerType": "inputRegister",
+ "description": "Rated Current",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "installationCurrent",
+ "address": 778,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "Installation current",
+ "access": "RO"
+ }
+ ]
+ },
+ {
+ "id": "consumptions",
+ "readSchedule": "update",
+ "registers": [
+ {
+ "id": "chargingSessionMeter",
+ "address": 781,
+ "size": 2,
+ "type": "uint32",
+ "unut": "Wh",
+ "registerType": "inputRegister",
+ "description": "Charging session meter count",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "actualPowerConsumption",
+ "address": 783,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "inputRegister",
+ "description": "Actual power consumption",
+ "unit": "W",
+ "defaultValue": "0",
+ "access": "RO"
+ }
+ ]
+ }
+ ],
+ "registers": [
+ {
+ "id": "serialNumber",
+ "address": 779,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "init",
+ "registerType": "inputRegister",
+ "description": "Serial number",
+ "unit": "",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "name",
+ "address": 785,
+ "size": 11,
+ "type": "string",
+ "readSchedule": "init",
+ "registerType": "inputRegister",
+ "description": "Wallbox name",
+ "access": "RO"
+ },
+ {
+ "id": "customerCurrentLimitation",
+ "address": 1024,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Customer Current Limitation",
+ "unit": "A",
+ "defaultValue": "0",
+ "access": "RW"
+ },
+ {
+ "id": "changeChargeState",
+ "address": 1025,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "holdingRegister",
+ "description": "Change charge state",
+ "enum": "ChargeState",
+ "access": "WO"
+ }
+ ]
+}
diff --git a/mennekes/amtronecudiscovery.cpp b/mennekes/amtronecudiscovery.cpp
new file mode 100644
index 0000000..e97d17a
--- /dev/null
+++ b/mennekes/amtronecudiscovery.cpp
@@ -0,0 +1,135 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "amtronecudiscovery.h"
+#include "extern-plugininfo.h"
+
+AmtronECUDiscovery::AmtronECUDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) :
+ QObject{parent},
+ m_networkDeviceDiscovery{networkDeviceDiscovery}
+{
+ m_gracePeriodTimer.setSingleShot(true);
+ m_gracePeriodTimer.setInterval(3000);
+ connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){
+ qCDebug(dcMennekes()) << "Discovery: Grace period timer triggered.";
+ finishDiscovery();
+ });
+}
+
+void AmtronECUDiscovery::startDiscovery()
+{
+ qCInfo(dcMennekes()) << "Discovery: Searching for AMTRON wallboxes in the network...";
+ NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
+
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &AmtronECUDiscovery::checkNetworkDevice);
+
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
+ qCDebug(dcMennekes()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
+ m_gracePeriodTimer.start();
+ discoveryReply->deleteLater();
+ });
+}
+
+QList AmtronECUDiscovery::discoveryResults() const
+{
+ return m_discoveryResults;
+}
+
+void AmtronECUDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
+{
+ if (networkDeviceInfo.macAddressManufacturer() != "GIGA-BYTE TECHNOLOGY CO.,LTD.") {
+ return;
+ }
+
+ int port = 502;
+ int slaveId = 0xff;
+ qCDebug(dcMennekes()) << "Checking network device:" << networkDeviceInfo << "Port:" << port << "Slave ID:" << slaveId;
+
+ AmtronECUModbusTcpConnection *connection = new AmtronECUModbusTcpConnection(networkDeviceInfo.address(), port, slaveId, this);
+ m_connections.append(connection);
+
+ connect(connection, &AmtronECUModbusTcpConnection::reachableChanged, this, [=](bool reachable){
+ if (!reachable) {
+ cleanupConnection(connection);
+ return;
+ }
+
+ connect(connection, &AmtronECUModbusTcpConnection::initializationFinished, this, [=](bool success){
+ if (!success) {
+ qCDebug(dcMennekes()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ return;
+ }
+ Result result;
+ result.firmwareVersion = connection->firmwareVersion();
+ result.model = connection->model();
+ result.networkDeviceInfo = networkDeviceInfo;
+ m_discoveryResults.append(result);
+
+ qCDebug(dcMennekes()) << "Discovery: Found wallbox with firmware version:" << result.firmwareVersion << result.networkDeviceInfo;
+
+ cleanupConnection(connection);
+ });
+
+ if (!connection->initialize()) {
+ qCDebug(dcMennekes()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ }
+ });
+
+ connect(connection, &AmtronECUModbusTcpConnection::checkReachabilityFailed, this, [=](){
+ qCDebug(dcMennekes()) << "Discovery: Checking reachability failed on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ });
+
+ connection->connectDevice();
+}
+
+void AmtronECUDiscovery::cleanupConnection(AmtronECUModbusTcpConnection *connection)
+{
+ m_connections.removeAll(connection);
+ connection->disconnectDevice();
+ connection->deleteLater();
+}
+
+void AmtronECUDiscovery::finishDiscovery()
+{
+ qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
+
+ // Cleanup any leftovers...we don't care any more
+ foreach (AmtronECUModbusTcpConnection *connection, m_connections)
+ cleanupConnection(connection);
+
+ qCInfo(dcMennekes()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count()
+ << "AMTRON wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
+ m_gracePeriodTimer.stop();
+
+ emit discoveryFinished();
+}
diff --git a/mennekes/amtronecudiscovery.h b/mennekes/amtronecudiscovery.h
new file mode 100644
index 0000000..5a997ad
--- /dev/null
+++ b/mennekes/amtronecudiscovery.h
@@ -0,0 +1,75 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 AMTRONECUDISCOVERY_H
+#define AMTRONECUDISCOVERY_H
+
+#include
+#include
+
+#include
+
+#include "amtronecumodbustcpconnection.h"
+
+class AmtronECUDiscovery : public QObject
+{
+ Q_OBJECT
+public:
+ explicit AmtronECUDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
+ struct Result {
+ QString firmwareVersion;
+ QString model;
+ NetworkDeviceInfo networkDeviceInfo;
+ };
+
+ void startDiscovery();
+
+ QList discoveryResults() const;
+
+signals:
+ void discoveryFinished();
+
+private:
+ NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
+
+ QTimer m_gracePeriodTimer;
+ QDateTime m_startDateTime;
+
+ QList m_connections;
+
+ QList m_discoveryResults;
+
+ void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
+ void cleanupConnection(AmtronECUModbusTcpConnection *connection);
+
+ void finishDiscovery();
+};
+
+#endif // AMTRONECUDISCOVERY_H
diff --git a/mennekes/amtronhcc3discovery.cpp b/mennekes/amtronhcc3discovery.cpp
new file mode 100644
index 0000000..81104d6
--- /dev/null
+++ b/mennekes/amtronhcc3discovery.cpp
@@ -0,0 +1,143 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "amtronhcc3discovery.h"
+#include "extern-plugininfo.h"
+
+AmtronHCC3Discovery::AmtronHCC3Discovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) :
+ QObject{parent},
+ m_networkDeviceDiscovery{networkDeviceDiscovery}
+{
+ m_gracePeriodTimer.setSingleShot(true);
+ m_gracePeriodTimer.setInterval(3000);
+ connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){
+ qCDebug(dcMennekes()) << "Discovery: Grace period timer triggered.";
+ finishDiscovery();
+ });
+}
+
+void AmtronHCC3Discovery::startDiscovery()
+{
+ qCInfo(dcMennekes()) << "Discovery: Searching for AMTRON wallboxes in the network...";
+ NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
+
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &AmtronHCC3Discovery::checkNetworkDevice);
+
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
+ qCDebug(dcMennekes()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
+ m_gracePeriodTimer.start();
+ discoveryReply->deleteLater();
+ });
+}
+
+QList AmtronHCC3Discovery::discoveryResults() const
+{
+ return m_discoveryResults;
+}
+
+void AmtronHCC3Discovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
+{
+ if (networkDeviceInfo.macAddressManufacturer() != "GIGA-BYTE TECHNOLOGY CO.,LTD.") {
+ return;
+ }
+
+ int port = 502;
+ int slaveId = 0xff;
+ qCDebug(dcMennekes()) << "Checking network device:" << networkDeviceInfo << "Port:" << port << "Slave ID:" << slaveId;
+
+ AmtronHCC3ModbusTcpConnection *connection = new AmtronHCC3ModbusTcpConnection(networkDeviceInfo.address(), port, slaveId, this);
+ m_connections.append(connection);
+
+ connect(connection, &AmtronHCC3ModbusTcpConnection::reachableChanged, this, [=](bool reachable){
+ if (!reachable) {
+ // Disconnected ... done with this connection
+ cleanupConnection(connection);
+ return;
+ }
+
+ // Modbus TCP connected...ok, let's try to initialize it!
+ connect(connection, &AmtronHCC3ModbusTcpConnection::initializationFinished, this, [=](bool success){
+ if (!success) {
+ qCDebug(dcMennekes()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ return;
+ }
+ AmtronDiscoveryResult result;
+ result.wallboxName = connection->name();
+ result.serialNumber = connection->serialNumber();
+ result.networkDeviceInfo = networkDeviceInfo;
+ m_discoveryResults.append(result);
+
+ qCDebug(dcMennekes()) << "Discovery: --> Found" << result.wallboxName
+ << "Serial number:" << result.serialNumber
+ << result.networkDeviceInfo;
+
+
+ // Done with this connection
+ cleanupConnection(connection);
+ });
+
+ if (!connection->initialize()) {
+ qCDebug(dcMennekes()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ }
+ });
+
+ // If check reachability failed...skip this host...
+ connect(connection, &AmtronHCC3ModbusTcpConnection::checkReachabilityFailed, this, [=](){
+ qCDebug(dcMennekes()) << "Discovery: Checking reachability failed on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ });
+
+ // Try to connect, maybe it works, maybe not...
+ connection->connectDevice();
+}
+
+void AmtronHCC3Discovery::cleanupConnection(AmtronHCC3ModbusTcpConnection *connection)
+{
+ m_connections.removeAll(connection);
+ connection->disconnectDevice();
+ connection->deleteLater();
+}
+
+void AmtronHCC3Discovery::finishDiscovery()
+{
+ qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
+
+ // Cleanup any leftovers...we don't care any more
+ foreach (AmtronHCC3ModbusTcpConnection *connection, m_connections)
+ cleanupConnection(connection);
+
+ qCInfo(dcMennekes()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count()
+ << "AMTRON wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
+ m_gracePeriodTimer.stop();
+
+ emit discoveryFinished();
+}
diff --git a/mennekes/amtronhcc3discovery.h b/mennekes/amtronhcc3discovery.h
new file mode 100644
index 0000000..2d5b8d4
--- /dev/null
+++ b/mennekes/amtronhcc3discovery.h
@@ -0,0 +1,75 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 AMTRONHCC3DISCOVERY_H
+#define AMTRONHCC3DISCOVERY_H
+
+#include
+#include
+
+#include
+
+#include "amtronhcc3modbustcpconnection.h"
+
+class AmtronHCC3Discovery : public QObject
+{
+ Q_OBJECT
+public:
+ explicit AmtronHCC3Discovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
+ typedef struct AmtronDiscoveryResult {
+ QString wallboxName;
+ QString serialNumber;
+ NetworkDeviceInfo networkDeviceInfo;
+ } AmtronDiscoveryResult;
+
+ void startDiscovery();
+
+ QList discoveryResults() const;
+
+signals:
+ void discoveryFinished();
+
+private:
+ NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
+
+ QTimer m_gracePeriodTimer;
+ QDateTime m_startDateTime;
+
+ QList m_connections;
+
+ QList m_discoveryResults;
+
+ void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
+ void cleanupConnection(AmtronHCC3ModbusTcpConnection *connection);
+
+ void finishDiscovery();
+};
+
+#endif // AMTRONHCC3DISCOVERY_H
diff --git a/mennekes/integrationpluginmennekes.cpp b/mennekes/integrationpluginmennekes.cpp
new file mode 100644
index 0000000..cfa4545
--- /dev/null
+++ b/mennekes/integrationpluginmennekes.cpp
@@ -0,0 +1,520 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "integrationpluginmennekes.h"
+#include "plugininfo.h"
+#include "amtronecudiscovery.h"
+#include "amtronhcc3discovery.h"
+
+#include
+#include
+
+IntegrationPluginMennekes::IntegrationPluginMennekes()
+{
+
+}
+
+void IntegrationPluginMennekes::discoverThings(ThingDiscoveryInfo *info)
+{
+ if (!hardwareManager()->networkDeviceDiscovery()->available()) {
+ qCWarning(dcMennekes()) << "The network discovery is not available on this platform.";
+ info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
+ return;
+ }
+
+ if (info->thingClassId() == amtronECUThingClassId) {
+ AmtronECUDiscovery *discovery = new AmtronECUDiscovery(hardwareManager()->networkDeviceDiscovery(), info);
+ connect(discovery, &AmtronECUDiscovery::discoveryFinished, info, [=](){
+ foreach (const AmtronECUDiscovery::Result &result, discovery->discoveryResults()) {
+
+ QString name = "AMTRON Charge Control/Professional";
+ QString description = result.model.isEmpty() ? result.networkDeviceInfo.address().toString() :
+ result.model + " (" + result.networkDeviceInfo.address().toString() + ")";
+ if (result.model.startsWith("CC")) {
+ name = "AMTRON Charge Control";
+ } else if (result.model.startsWith("P")) {
+ name = "AMTRON Professional";
+ }
+ ThingDescriptor descriptor(amtronECUThingClassId, name, description);
+ qCDebug(dcMennekes()) << "Discovered:" << descriptor.title() << descriptor.description();
+
+ // Check if we already have set up this device
+ Things existingThings = myThings().filterByParam(amtronECUThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
+ if (existingThings.count() == 1) {
+ qCDebug(dcMennekes()) << "This wallbox already exists in the system:" << result.networkDeviceInfo;
+ descriptor.setThingId(existingThings.first()->id());
+ }
+
+ ParamList params;
+ params << Param(amtronECUThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
+ // Note: if we discover also the port and modbusaddress, we must fill them in from the discovery here, for now everywhere the defaults...
+ descriptor.setParams(params);
+ info->addThingDescriptor(descriptor);
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+ });
+ discovery->startDiscovery();
+
+ } else if (info->thingClassId() == amtronHCC3ThingClassId) {
+ AmtronHCC3Discovery *discovery = new AmtronHCC3Discovery(hardwareManager()->networkDeviceDiscovery(), info);
+ connect(discovery, &AmtronHCC3Discovery::discoveryFinished, info, [=](){
+ foreach (const AmtronHCC3Discovery::AmtronDiscoveryResult &result, discovery->discoveryResults()) {
+
+ ThingDescriptor descriptor(amtronHCC3ThingClassId, result.wallboxName, "Serial: " + result.serialNumber + " - " + result.networkDeviceInfo.address().toString());
+ qCDebug(dcMennekes()) << "Discovered:" << descriptor.title() << descriptor.description();
+
+ // Check if we already have set up this device
+ Things existingThings = myThings().filterByParam(amtronHCC3ThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
+ if (existingThings.count() == 1) {
+ qCDebug(dcMennekes()) << "This wallbox already exists in the system:" << result.networkDeviceInfo;
+ descriptor.setThingId(existingThings.first()->id());
+ }
+
+ ParamList params;
+ params << Param(amtronHCC3ThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
+ // Note: if we discover also the port and modbusaddress, we must fill them in from the discovery here, for now everywhere the defaults...
+ descriptor.setParams(params);
+ info->addThingDescriptor(descriptor);
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+ });
+ discovery->startDiscovery();
+ }
+}
+
+void IntegrationPluginMennekes::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ qCDebug(dcMennekes()) << "Setup" << thing << thing->params();
+
+ if (thing->thingClassId() == amtronECUThingClassId) {
+
+ if (m_amtronECUConnections.contains(thing)) {
+ qCDebug(dcMennekes()) << "Reconfiguring existing thing" << thing->name();
+ m_amtronECUConnections.take(thing)->deleteLater();
+
+ if (m_monitors.contains(thing)) {
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
+ }
+ }
+
+ MacAddress macAddress = MacAddress(thing->paramValue(amtronECUThingMacAddressParamTypeId).toString());
+ if (!macAddress.isValid()) {
+ qCWarning(dcMennekes()) << "The configured mac address is not valid" << thing->params();
+ info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing."));
+ return;
+ }
+
+ NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
+ m_monitors.insert(thing, monitor);
+
+ QHostAddress address = monitor->networkDeviceInfo().address();
+ if (address.isNull()) {
+ qCWarning(dcMennekes()) << "Cannot set up thing. The host address is not known yet...";
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
+ info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying later again."));
+ return;
+ }
+
+ connect(info, &ThingSetupInfo::aborted, monitor, [=](){
+ if (m_monitors.contains(thing)) {
+ qCDebug(dcMennekes()) << "Unregistering monitor because setup has been aborted.";
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
+ }
+ });
+
+ if (monitor->reachable()) {
+ setupAmtronECUConnection(info);
+ } else {
+ qCDebug(dcMennekes()) << "Waiting for the network monitor to get reachable before continue to set up the connection" << thing->name() << address.toString() << "...";
+ connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
+ if (reachable) {
+ qCDebug(dcMennekes()) << "The monitor for thing setup" << thing->name() << "is now reachable. Continue setup...";
+ setupAmtronECUConnection(info);
+ }
+ });
+ }
+
+ return;
+ }
+
+ if (info->thing()->thingClassId() == amtronHCC3ThingClassId) {
+ if (m_amtronHCC3Connections.contains(thing)) {
+ qCDebug(dcMennekes()) << "Reconfiguring existing thing" << thing->name();
+ m_amtronHCC3Connections.take(thing)->deleteLater();
+
+ if (m_monitors.contains(thing)) {
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
+ }
+ }
+
+ MacAddress macAddress = MacAddress(thing->paramValue(amtronHCC3ThingMacAddressParamTypeId).toString());
+ if (!macAddress.isValid()) {
+ qCWarning(dcMennekes()) << "The configured mac address is not valid" << thing->params();
+ info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing."));
+ return;
+ }
+
+ NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
+ m_monitors.insert(thing, monitor);
+
+ QHostAddress address = monitor->networkDeviceInfo().address();
+ if (address.isNull()) {
+ qCWarning(dcMennekes()) << "Cannot set up thing. The host address is not known yet...";
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
+ info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying later again."));
+ return;
+ }
+
+ connect(info, &ThingSetupInfo::aborted, monitor, [=](){
+ if (m_monitors.contains(thing)) {
+ qCDebug(dcMennekes()) << "Unregistering monitor because setup has been aborted.";
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
+ }
+ });
+
+ if (monitor->reachable()) {
+ setupAmtronHCC3Connection(info);
+ } else {
+ qCDebug(dcMennekes()) << "Waiting for the network monitor to get reachable before continue to set up the connection" << thing->name() << address.toString() << "...";
+ connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
+ if (reachable) {
+ qCDebug(dcMennekes()) << "The monitor for thing setup" << thing->name() << "is now reachable. Continue setup...";
+ setupAmtronHCC3Connection(info);
+ }
+ });
+ }
+
+ return;
+
+ }
+}
+
+void IntegrationPluginMennekes::postSetupThing(Thing *thing)
+{
+ Q_UNUSED(thing)
+ if (!m_pluginTimer) {
+ qCDebug(dcMennekes()) << "Starting plugin timer...";
+ m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
+ connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
+ foreach(AmtronECUModbusTcpConnection *connection, m_amtronECUConnections) {
+ qCDebug(dcMennekes()) << "Updating connection" << connection->hostAddress().toString();
+ connection->update();
+ }
+ foreach(AmtronHCC3ModbusTcpConnection *connection, m_amtronHCC3Connections) {
+ qCDebug(dcMennekes()) << "Updating connection" << connection->hostAddress().toString();
+ connection->update();
+ }
+ });
+
+ m_pluginTimer->start();
+ }
+}
+
+void IntegrationPluginMennekes::executeAction(ThingActionInfo *info)
+{
+ if (info->thing()->thingClassId() == amtronECUThingClassId) {
+ AmtronECUModbusTcpConnection *amtronECUConnection = m_amtronECUConnections.value(info->thing());
+
+ if (info->action().actionTypeId() == amtronECUPowerActionTypeId) {
+ bool power = info->action().paramValue(amtronECUPowerActionPowerParamTypeId).toBool();
+ QModbusReply *reply = amtronECUConnection->setHemsCurrentLimit(power ? info->thing()->stateValue(amtronECUMaxChargingCurrentStateTypeId).toUInt() : 0);
+ connect(reply, &QModbusReply::finished, info, [info, reply, power](){
+ if (reply->error() == QModbusDevice::NoError) {
+ info->thing()->setStateValue(amtronECUPowerStateTypeId, power);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ qCWarning(dcMennekes()) << "Error setting cp availability:" << reply->error() << reply->errorString();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ });
+ }
+ if (info->action().actionTypeId() == amtronECUMaxChargingCurrentActionTypeId) {
+ int maxChargingCurrent = info->action().paramValue(amtronECUMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toInt();
+ QModbusReply *reply = amtronECUConnection->setHemsCurrentLimit(maxChargingCurrent);
+ connect(reply, &QModbusReply::finished, info, [info, reply, maxChargingCurrent](){
+ if (reply->error() == QModbusDevice::NoError) {
+ info->thing()->setStateValue(amtronECUMaxChargingCurrentStateTypeId, maxChargingCurrent);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ });
+ }
+ }
+}
+
+void IntegrationPluginMennekes::thingRemoved(Thing *thing)
+{
+ if (thing->thingClassId() == amtronECUThingClassId && m_amtronECUConnections.contains(thing)) {
+ AmtronECUModbusTcpConnection *connection = m_amtronECUConnections.take(thing);
+ delete connection;
+ }
+
+ if (thing->thingClassId() == amtronHCC3ThingClassId && m_amtronHCC3Connections.contains(thing)) {
+ AmtronHCC3ModbusTcpConnection *connection = m_amtronHCC3Connections.take(thing);
+ delete connection;
+ }
+
+ // Unregister related hardware resources
+ if (m_monitors.contains(thing))
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
+
+ if (myThings().isEmpty() && m_pluginTimer) {
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
+ m_pluginTimer = nullptr;
+ }
+}
+
+void IntegrationPluginMennekes::updateECUPhaseCount(Thing *thing)
+{
+ AmtronECUModbusTcpConnection *amtronECUConnection = m_amtronECUConnections.value(thing);
+ int phaseCount = 0;
+ qCDebug(dcMennekes()) << "Phases: L1" << amtronECUConnection->meterCurrentL1() << "L2" << amtronECUConnection->meterCurrentL2() << "L3" << amtronECUConnection->meterCurrentL3();
+ // the current idles on some 5 - 10 mA when not charging...
+ // We want to detect the phases we're actually charging on. Checking the current flow for that if it's > 500mA
+ // If no phase is charging, let's count all phases that are not 0 instead (to determine how many phases are connected at the wallbox)
+
+ if (amtronECUConnection->meterCurrentL1() > 500) {
+ phaseCount++;
+ }
+ if (amtronECUConnection->meterCurrentL2() > 500) {
+ phaseCount++;
+ }
+ if (amtronECUConnection->meterCurrentL3() > 500) {
+ phaseCount++;
+ }
+ qCDebug(dcMennekes()) << "Actively charging phases:" << phaseCount;
+ if (phaseCount == 0) {
+ if (amtronECUConnection->meterCurrentL1() > 0) {
+ phaseCount++;
+ }
+ if (amtronECUConnection->meterCurrentL2() > 0) {
+ phaseCount++;
+ }
+ if (amtronECUConnection->meterCurrentL3() > 0) {
+ phaseCount++;
+ }
+ qCDebug(dcMennekes()) << "Connected phases:" << phaseCount;
+ }
+
+ thing->setStateValue(amtronECUPhaseCountStateTypeId, phaseCount);
+}
+
+void IntegrationPluginMennekes::setupAmtronECUConnection(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
+
+ qCDebug(dcMennekes()) << "Setting up amtron wallbox on" << address.toString();
+ AmtronECUModbusTcpConnection *amtronECUConnection = new AmtronECUModbusTcpConnection(address, 502, 0xff, this);
+ connect(info, &ThingSetupInfo::aborted, amtronECUConnection, &ModbusTCPMaster::deleteLater);
+
+ // Reconnect on monitor reachable changed
+ NetworkDeviceMonitor *monitor = m_monitors.value(thing);
+ connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
+ qCDebug(dcMennekes()) << "Network device monitor reachable changed for" << thing->name() << reachable;
+ if (!thing->setupComplete())
+ return;
+
+ if (reachable && !thing->stateValue("connected").toBool()) {
+ amtronECUConnection->setHostAddress(monitor->networkDeviceInfo().address());
+ amtronECUConnection->connectDevice();
+ } else if (!reachable) {
+ // Note: We disable autoreconnect explicitly and we will
+ // connect the device once the monitor says it is reachable again
+ amtronECUConnection->disconnectDevice();
+ }
+ });
+
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::reachableChanged, thing, [thing, amtronECUConnection](bool reachable){
+ qCDebug(dcMennekes()) << "Reachable changed to" << reachable << "for" << thing;
+ if (reachable) {
+ amtronECUConnection->initialize();
+ } else {
+ thing->setStateValue(amtronECUConnectedStateTypeId, false);
+ }
+ });
+
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::initializationFinished, thing, [=](bool success){
+ if (!thing->setupComplete())
+ return;
+
+ if (success) {
+ thing->setStateValue(amtronECUConnectedStateTypeId, true);
+ } else {
+ thing->setStateValue(amtronECUConnectedStateTypeId, false);
+ // Try once to reconnect the device
+ amtronECUConnection->reconnectDevice();
+ }
+ });
+
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::initializationFinished, info, [=](bool success){
+ if (!success) {
+ qCWarning(dcMennekes()) << "Connection init finished with errors" << thing->name() << amtronECUConnection->hostAddress().toString();
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
+ amtronECUConnection->deleteLater();
+ info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error communicating with the wallbox."));
+ return;
+ }
+
+ qCDebug(dcMennekes()) << "Connection init finished successfully" << amtronECUConnection;
+ m_amtronECUConnections.insert(thing, amtronECUConnection);
+ info->finish(Thing::ThingErrorNoError);
+
+ thing->setStateValue(amtronECUConnectedStateTypeId, true);
+
+ amtronECUConnection->update();
+ });
+
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::updateFinished, thing, [this, amtronECUConnection, thing](){
+ qCDebug(dcMennekes()) << "Amtron ECU update finished:" << thing->name() << amtronECUConnection;
+ updateECUPhaseCount(thing);
+ });
+
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::cpSignalStateChanged, thing, [thing](AmtronECUModbusTcpConnection::CPSignalState cpSignalState) {
+ qCDebug(dcMennekes()) << "CP signal state changed" << cpSignalState;
+ thing->setStateValue(amtronECUPluggedInStateTypeId, cpSignalState >= AmtronECUModbusTcpConnection::CPSignalStateB);
+ });
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::signalledCurrentChanged, thing, [](quint16 signalledCurrent) {
+ qCDebug(dcMennekes()) << "Signalled current changed:" << signalledCurrent;
+ });
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::minCurrentLimitChanged, thing, [thing](quint16 minCurrentLimit) {
+ qCDebug(dcMennekes()) << "min current limit changed:" << minCurrentLimit;
+ thing->setStateMinValue(amtronECUMaxChargingCurrentStateTypeId, minCurrentLimit);
+ });
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::maxCurrentLimitChanged, thing, [thing](quint16 maxCurrentLimit) {
+ qCDebug(dcMennekes()) << "max current limit changed:" << maxCurrentLimit;
+ thing->setStateMaxValue(amtronECUMaxChargingCurrentStateTypeId, maxCurrentLimit);
+ });
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::hemsCurrentLimitChanged, thing, [thing](quint16 hemsCurrentLimit) {
+ qCDebug(dcMennekes()) << "HEMS current limit changed:" << hemsCurrentLimit;
+ if (hemsCurrentLimit == 0) {
+ thing->setStateValue(amtronECUPowerStateTypeId, false);
+ } else {
+ thing->setStateValue(amtronECUPowerStateTypeId, true);
+ thing->setStateValue(amtronECUMaxChargingCurrentStateTypeId, hemsCurrentLimit);
+ }
+ });
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::meterTotoalEnergyChanged, thing, [thing](quint32 meterTotalEnergy) {
+ qCDebug(dcMennekes()) << "meter total energy changed:" << meterTotalEnergy;
+ thing->setStateValue(amtronECUTotalEnergyConsumedStateTypeId, qRound(meterTotalEnergy / 10.0) / 100.0); // rounded to 2 as it changes on every update
+ });
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::meterTotalPowerChanged, thing, [thing](quint32 meterTotalPower) {
+ qCDebug(dcMennekes()) << "meter total power changed:" << meterTotalPower;
+ thing->setStateValue(amtronECUCurrentPowerStateTypeId, meterTotalPower);
+ thing->setStateValue(amtronECUChargingStateTypeId, meterTotalPower > 0);
+ });
+ connect(amtronECUConnection, &AmtronECUModbusTcpConnection::chargedEnergyChanged, thing, [thing](quint32 chargedEnergy) {
+ qCDebug(dcMennekes()) << "charged energy changed:" << chargedEnergy;
+ thing->setStateValue(amtronECUSessionEnergyStateTypeId, qRound(chargedEnergy / 10.0) / 100.0); // rounded to 2 as it changes on every update
+ });
+
+ amtronECUConnection->connectDevice();
+}
+
+void IntegrationPluginMennekes::setupAmtronHCC3Connection(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
+
+ qCDebug(dcMennekes()) << "Setting up amtron wallbox on" << address.toString();
+ AmtronHCC3ModbusTcpConnection *amtronHCC3Connection = new AmtronHCC3ModbusTcpConnection(address, 502, 0xff, this);
+ connect(info, &ThingSetupInfo::aborted, amtronHCC3Connection, &ModbusTCPMaster::deleteLater);
+
+ // Reconnect on monitor reachable changed
+ NetworkDeviceMonitor *monitor = m_monitors.value(thing);
+ connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
+ qCDebug(dcMennekes()) << "Network device monitor reachable changed for" << thing->name() << reachable;
+ if (!thing->setupComplete())
+ return;
+
+ if (reachable && !thing->stateValue("connected").toBool()) {
+ amtronHCC3Connection->setHostAddress(monitor->networkDeviceInfo().address());
+ amtronHCC3Connection->connectDevice();
+ } else if (!reachable) {
+ // Note: We disable autoreconnect explicitly and we will
+ // connect the device once the monitor says it is reachable again
+ amtronHCC3Connection->disconnectDevice();
+ }
+ });
+
+ connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::reachableChanged, thing, [thing, amtronHCC3Connection](bool reachable){
+ qCDebug(dcMennekes()) << "Reachable changed to" << reachable << "for" << thing;
+ if (reachable) {
+ amtronHCC3Connection->initialize();
+ } else {
+ thing->setStateValue(amtronHCC3ConnectedStateTypeId, false);
+ }
+ });
+
+ connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::initializationFinished, thing, [=](bool success){
+ if (!thing->setupComplete())
+ return;
+
+ if (success) {
+ thing->setStateValue(amtronHCC3ConnectedStateTypeId, true);
+ } else {
+ thing->setStateValue(amtronHCC3ConnectedStateTypeId, false);
+ // Try once to reconnect the device
+ amtronHCC3Connection->reconnectDevice();
+ }
+ });
+
+ connect(amtronHCC3Connection, &AmtronHCC3ModbusTcpConnection::initializationFinished, info, [=](bool success){
+ if (!success) {
+ qCWarning(dcMennekes()) << "Connection init finished with errors" << thing->name() << amtronHCC3Connection->hostAddress().toString();
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
+ amtronHCC3Connection->deleteLater();
+ info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error communicating with the wallbox."));
+ return;
+ }
+
+ qCDebug(dcMennekes()) << "Connection init finished successfully" << amtronHCC3Connection;
+ m_amtronHCC3Connections.insert(thing, amtronHCC3Connection);
+ info->finish(Thing::ThingErrorNoError);
+
+ thing->setStateValue(amtronHCC3ConnectedStateTypeId, true);
+
+ amtronHCC3Connection->update();
+ });
+
+ amtronHCC3Connection->connectDevice();
+}
+
+bool IntegrationPluginMennekes::ensureAmtronECUVersion(AmtronECUModbusTcpConnection *connection, const QString &version)
+{
+ QByteArray deviceVersion = QByteArray::fromHex(QByteArray::number(connection->firmwareVersion(), 16));
+ return deviceVersion >= version;
+}
diff --git a/mennekes/integrationpluginmennekes.h b/mennekes/integrationpluginmennekes.h
new file mode 100644
index 0000000..caaa1b3
--- /dev/null
+++ b/mennekes/integrationpluginmennekes.h
@@ -0,0 +1,77 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 INTEGRATIONPLUGINMENNEKES_H
+#define INTEGRATIONPLUGINMENNEKES_H
+
+#include
+#include
+#include
+
+#include "extern-plugininfo.h"
+
+#include "amtronecumodbustcpconnection.h"
+#include "amtronhcc3modbustcpconnection.h"
+
+class IntegrationPluginMennekes: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginmennekes.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginMennekes();
+
+ void discoverThings(ThingDiscoveryInfo *info) override;
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void executeAction(ThingActionInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+private slots:
+ void updateECUPhaseCount(Thing *thing);
+
+private:
+ void setupAmtronECUConnection(ThingSetupInfo *info);
+ void setupAmtronHCC3Connection(ThingSetupInfo *info);
+
+ bool ensureAmtronECUVersion(AmtronECUModbusTcpConnection *connection, const QString &version);
+
+ PluginTimer *m_pluginTimer = nullptr;
+ QHash m_amtronECUConnections;
+ QHash m_amtronHCC3Connections;
+ QHash m_monitors;
+
+};
+
+#endif // INTEGRATIONPLUGINMENNEKES_H
+
+
diff --git a/mennekes/integrationpluginmennekes.json b/mennekes/integrationpluginmennekes.json
new file mode 100644
index 0000000..1092280
--- /dev/null
+++ b/mennekes/integrationpluginmennekes.json
@@ -0,0 +1,184 @@
+{
+ "name": "Mennekes",
+ "displayName": "Mennekes",
+ "id": "c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38",
+ "vendors": [
+ {
+ "name": "mennekes",
+ "displayName": "MENNEKES",
+ "id": "7c585571-e3a3-458c-a598-e11f510cbc10",
+ "thingClasses": [
+ {
+ "name": "amtronECU",
+ "displayName": "AMTRON Charge Control/Professional",
+ "id": "fb48e067-2237-4eaf-8d2c-681d406395fc",
+ "createMethods": ["discovery", "user"],
+ "interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "0b9c1466-5eb9-4b25-9450-513e2484a3b4",
+ "name":"macAddress",
+ "displayName": "MAC address",
+ "type": "QString",
+ "inputType": "MacAddress",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "352be84a-f5c6-434d-8a92-a4065a24ff0a",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "c93d7377-8a4a-4c35-9876-1032d8b309e3",
+ "name": "pluggedIn",
+ "displayName": "Plugged in",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "c8c812c6-dd56-425c-8dd1-8dd621bd3a11",
+ "name": "charging",
+ "displayName": "Charging",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "a51d0beb-f4fd-4279-85b5-b436b283a86e",
+ "name": "phaseCount",
+ "displayName": "Connected phases",
+ "type": "uint",
+ "minValue": 1,
+ "maxValue": 3,
+ "defaultValue": 1
+ },
+ {
+ "id": "8b2eb039-b4e3-49ae-94fc-a8b825fd8d9b",
+ "name": "currentPower",
+ "displayName": "Active power",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "5b8bfdf0-eaa6-41d0-8f2f-119b06aed181",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total consumed energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.0,
+ "cached": true
+ },
+ {
+ "id": "53dc845a-a397-4c90-890b-fd50512666f4",
+ "name": "power",
+ "displayName": "Charging enabled",
+ "displayNameAction": "Set charging enabled",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "fb12ff61-f88a-4bfc-930f-a4a55b342d1b",
+ "name": "maxChargingCurrent",
+ "displayName": "Maximum charging current",
+ "displayNameAction": "Set maximum charging current",
+ "type": "uint",
+ "unit": "Ampere",
+ "minValue": 6,
+ "maxValue": 32,
+ "defaultValue": 6,
+ "writable": true
+ },
+ {
+ "id": "2ce6b363-5b8d-4703-8376-611a0e573f71",
+ "name": "sessionEnergy",
+ "displayName": "Session energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ }
+ ]
+ },
+ {
+ "name": "amtronHCC3",
+ "displayName": "AMTRON XTRA/Premium",
+ "id": "01995c4f-a7b5-4bdd-9333-486390ff75fd",
+ "createMethods": ["discovery", "user"],
+ "interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "6112045e-e472-4475-bc11-f373e9c39cdd",
+ "name":"macAddress",
+ "displayName": "MAC address",
+ "type": "QString",
+ "inputType": "MacAddress",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "899e270f-7666-44b3-8509-0dad43ac9c4c",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "96b9c121-3caf-44fe-8380-483b9b40dbd9",
+ "name": "currentPower",
+ "displayName": "Active power",
+ "displayNameEvent": "Active power changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "3d1384fc-8b46-42b0-b043-23279d8c7665",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total consumed energy",
+ "displayNameEvent": "Total consumed energy changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.0,
+ "cached": true
+ },
+ {
+ "id": "130585ca-14e9-45b7-87d7-c0d935228104",
+ "name": "power",
+ "displayName": "Charging enabled",
+ "displayNameEvent": "Charging enabled changed",
+ "displayNameAction": "Set charging enabled",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "87b4e9cb-e57b-461a-90f9-207bb5dab44c",
+ "name": "maxChargingCurrent",
+ "displayName": "Maximum charging current",
+ "displayNameEvent": "Maximum charging current changed",
+ "displayNameAction": "Set maximum charging current",
+ "type": "uint",
+ "unit": "Ampere",
+ "minValue": 6,
+ "maxValue": 32,
+ "defaultValue": 6,
+ "writable": true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/mennekes/mennekes.pro b/mennekes/mennekes.pro
new file mode 100644
index 0000000..6f0dc22
--- /dev/null
+++ b/mennekes/mennekes.pro
@@ -0,0 +1,17 @@
+include(../plugins.pri)
+
+# Generate modbus connection
+MODBUS_CONNECTIONS += amtron-ecu-registers.json \
+ amtron-hcc3-registers.json
+#MODBUS_TOOLS_CONFIG += VERBOSE
+include(../modbus.pri)
+
+HEADERS += \
+ amtronecudiscovery.h \
+ amtronhcc3discovery.h \
+ integrationpluginmennekes.h
+
+SOURCES += \
+ amtronecudiscovery.cpp \
+ amtronhcc3discovery.cpp \
+ integrationpluginmennekes.cpp
diff --git a/mennekes/mennekes.svg b/mennekes/mennekes.svg
new file mode 100644
index 0000000..2b3778b
--- /dev/null
+++ b/mennekes/mennekes.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/mennekes/meta.json b/mennekes/meta.json
new file mode 100644
index 0000000..e78d48d
--- /dev/null
+++ b/mennekes/meta.json
@@ -0,0 +1,13 @@
+{
+ "title": "MENNEKES",
+ "tagline": "Connect MENNEKES wallboxes to nymea.",
+ "icon": "mennekes.svg",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ "energy"
+ ]
+}
diff --git a/mennekes/translations/c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38-en_US.ts b/mennekes/translations/c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38-en_US.ts
new file mode 100644
index 0000000..1ae03de
--- /dev/null
+++ b/mennekes/translations/c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38-en_US.ts
@@ -0,0 +1,165 @@
+
+
+
+
+ IntegrationPluginMennekes
+
+
+ The network device discovery is not available.
+
+
+
+
+
+ The MAC address is not known. Please reconfigure the thing.
+
+
+
+
+
+ The host address is not known yet. Trying later again.
+
+
+
+
+
+ Error communicating with the wallbox.
+
+
+
+
+ Mennekes
+
+
+ AMTRON Charge Control/Professional
+ The name of the ThingClass ({fb48e067-2237-4eaf-8d2c-681d406395fc})
+
+
+
+
+ AMTRON XTRA/Premium
+ The name of the ThingClass ({01995c4f-a7b5-4bdd-9333-486390ff75fd})
+
+
+
+
+
+ Active power
+ The name of the StateType ({96b9c121-3caf-44fe-8380-483b9b40dbd9}) of ThingClass amtronHCC3
+----------
+The name of the StateType ({8b2eb039-b4e3-49ae-94fc-a8b825fd8d9b}) of ThingClass amtronECU
+
+
+
+
+ Charging
+ The name of the StateType ({c8c812c6-dd56-425c-8dd1-8dd621bd3a11}) of ThingClass amtronECU
+
+
+
+
+
+
+
+ Charging enabled
+ The name of the ParamType (ThingClass: amtronHCC3, ActionType: power, ID: {130585ca-14e9-45b7-87d7-c0d935228104})
+----------
+The name of the StateType ({130585ca-14e9-45b7-87d7-c0d935228104}) of ThingClass amtronHCC3
+----------
+The name of the ParamType (ThingClass: amtronECU, ActionType: power, ID: {53dc845a-a397-4c90-890b-fd50512666f4})
+----------
+The name of the StateType ({53dc845a-a397-4c90-890b-fd50512666f4}) of ThingClass amtronECU
+
+
+
+
+
+ Connected
+ The name of the StateType ({899e270f-7666-44b3-8509-0dad43ac9c4c}) of ThingClass amtronHCC3
+----------
+The name of the StateType ({352be84a-f5c6-434d-8a92-a4065a24ff0a}) of ThingClass amtronECU
+
+
+
+
+ Connected phases
+ The name of the StateType ({a51d0beb-f4fd-4279-85b5-b436b283a86e}) of ThingClass amtronECU
+
+
+
+
+
+ MAC address
+ The name of the ParamType (ThingClass: amtronHCC3, Type: thing, ID: {6112045e-e472-4475-bc11-f373e9c39cdd})
+----------
+The name of the ParamType (ThingClass: amtronECU, Type: thing, ID: {0b9c1466-5eb9-4b25-9450-513e2484a3b4})
+
+
+
+
+ MENNEKES
+ The name of the vendor ({7c585571-e3a3-458c-a598-e11f510cbc10})
+
+
+
+
+
+
+
+ Maximum charging current
+ The name of the ParamType (ThingClass: amtronHCC3, ActionType: maxChargingCurrent, ID: {87b4e9cb-e57b-461a-90f9-207bb5dab44c})
+----------
+The name of the StateType ({87b4e9cb-e57b-461a-90f9-207bb5dab44c}) of ThingClass amtronHCC3
+----------
+The name of the ParamType (ThingClass: amtronECU, ActionType: maxChargingCurrent, ID: {fb12ff61-f88a-4bfc-930f-a4a55b342d1b})
+----------
+The name of the StateType ({fb12ff61-f88a-4bfc-930f-a4a55b342d1b}) of ThingClass amtronECU
+
+
+
+
+ Mennekes
+ The name of the plugin Mennekes ({c7c3c65c-a0cc-4ab1-90d8-4ad05bfcdc38})
+
+
+
+
+ Plugged in
+ The name of the StateType ({c93d7377-8a4a-4c35-9876-1032d8b309e3}) of ThingClass amtronECU
+
+
+
+
+ Session energy
+ The name of the StateType ({2ce6b363-5b8d-4703-8376-611a0e573f71}) of ThingClass amtronECU
+
+
+
+
+
+ Set charging enabled
+ The name of the ActionType ({130585ca-14e9-45b7-87d7-c0d935228104}) of ThingClass amtronHCC3
+----------
+The name of the ActionType ({53dc845a-a397-4c90-890b-fd50512666f4}) of ThingClass amtronECU
+
+
+
+
+
+ Set maximum charging current
+ The name of the ActionType ({87b4e9cb-e57b-461a-90f9-207bb5dab44c}) of ThingClass amtronHCC3
+----------
+The name of the ActionType ({fb12ff61-f88a-4bfc-930f-a4a55b342d1b}) of ThingClass amtronECU
+
+
+
+
+
+ Total consumed energy
+ The name of the StateType ({3d1384fc-8b46-42b0-b043-23279d8c7665}) of ThingClass amtronHCC3
+----------
+The name of the StateType ({5b8bfdf0-eaa6-41d0-8f2f-119b06aed181}) of ThingClass amtronECU
+
+
+
+
diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro
index a01d925..1176e20 100644
--- a/nymea-plugins-modbus.pro
+++ b/nymea-plugins-modbus.pro
@@ -12,6 +12,7 @@ PLUGIN_DIRS = \
idm \
inepro \
kostal \
+ mennekes \
modbuscommander \
mtec \
mypv \