diff --git a/amperfied/README.md b/amperfied/README.md
new file mode 100644
index 0000000..0dea547
--- /dev/null
+++ b/amperfied/README.md
@@ -0,0 +1,19 @@
+# Amperfied - Heidelberg
+
+Connects nymea to a Amperfied/Heidelberg wallboxes. Currently supported models are:
+
+* Amperfeid Energy Control
+* Amperfeid connect.home
+
+# Requirements
+
+nymea requires the use of a firmware greator or equal version 1.0.7 on the wallbox.
+
+## Amperfeid Energy Control
+The Amperfeid Energy Control is a Modbus RTU device. This means it must be connected to the nymea system using an RS485 port.
+In order to allow nymea to automatically discover the wallbox on the bus, the Modbus slave ID must be in the range of 1 - 20.
+If a higher slave ID number is required, the manual setup is to be used.
+
+## Amperfied connect.home
+The Amperfeid connect.home is a Modbus TCP device. This means it must be connected to the same network the nymea system is in.
+
diff --git a/amperfied/amperfied-registers.json b/amperfied/amperfied-registers.json
new file mode 100644
index 0000000..f93a92f
--- /dev/null
+++ b/amperfied/amperfied-registers.json
@@ -0,0 +1,250 @@
+{
+ "className": "Amperfied",
+ "protocol": "BOTH",
+ "endianness": "BigEndian",
+ "errorLimitUntilNotReachable": 2,
+ "checkReachableRegister": "chargingCurrent",
+ "enums": [
+ {
+ "name": "ChargingState",
+ "values": [
+ {
+ "key": "Undefined",
+ "value": 1
+ },
+ {
+ "key": "A1",
+ "value": 2
+ },
+ {
+ "key": "A2",
+ "value": 3
+ },
+ {
+ "key": "B1",
+ "value": 4
+ },
+ {
+ "key": "B2",
+ "value": 5
+ },
+ {
+ "key": "C1",
+ "value": 6
+ },
+ {
+ "key": "C2",
+ "value": 7
+ },
+ {
+ "key": "Derating",
+ "value": 8
+ },
+ {
+ "key": "E",
+ "value": 9
+ },
+ {
+ "key": "F",
+ "value": 10
+ },
+ {
+ "key": "Error",
+ "value": 11
+ }
+ ]
+ }
+ ],
+ "blocks": [
+ {
+ "id": "consumptions",
+ "readSchedule": "update",
+ "registers": [
+ {
+ "id": "chargingState",
+ "address": 5,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "ChargingState",
+ "enum": "ChargingState",
+ "defaultValue": "ChargingStateUndefined",
+ "access": "RO"
+ },
+ {
+ "id": "currentL1",
+ "address": 6,
+ "size": 1,
+ "type": "uint16",
+ "unit": "A",
+ "registerType": "inputRegister",
+ "description": "Current L1",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "currentL2",
+ "address": 7,
+ "size": 1,
+ "type": "uint16",
+ "unit": "A",
+ "registerType": "inputRegister",
+ "description": "Current L2",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "currentL3",
+ "address": 8,
+ "size": 1,
+ "type": "uint16",
+ "unit": "A",
+ "registerType": "inputRegister",
+ "description": "Current L3",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "pcbTemperature",
+ "address": 9,
+ "size": 1,
+ "type": "uint16",
+ "unit": "1/10°C",
+ "registerType": "inputRegister",
+ "description": "PCB temperature",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "voltageL1",
+ "address": 10,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "Voltage L1",
+ "unit": "V",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "voltageL2",
+ "address": 11,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "Voltage L2",
+ "unit": "V",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "voltageL3",
+ "address": 12,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "Voltage L3",
+ "unit": "V",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "externalLock",
+ "address": 13,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "External lock",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "currentPower",
+ "address": 14,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "Power (L1+L2+L3)",
+ "unit": "VA",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "sessionEnergy",
+ "address": 15,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "inputRegister",
+ "description": "Energy since PowerOn",
+ "unit": "Wh",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "totalEnergy",
+ "address": 17,
+ "size": 2,
+ "type": "uint32",
+ "registerType": "inputRegister",
+ "description": "Energy since installation",
+ "unit": "Wh",
+ "defaultValue": 0,
+ "access": "RO"
+ }
+ ]
+ },
+ {
+ "id": "minMaxValues",
+ "readSchedule": "update",
+ "registers": [
+ {
+ "id": "maxChargingCurrent",
+ "address": 100,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "Absolute maximum charging current",
+ "unit": "A",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "minChargingCurrent",
+ "address": 101,
+ "size": 1,
+ "type": "uint16",
+ "registerType": "inputRegister",
+ "description": "Absolute minimum charging current",
+ "unit": "A",
+ "defaultValue": 0,
+ "access": "RO"
+ }
+ ]
+ }
+ ],
+ "registers": [
+ {
+ "id": "version",
+ "address": 4,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "init",
+ "registerType": "inputRegister",
+ "description": "Version",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "chargingCurrent",
+ "address": 261,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Charging current",
+ "unit": "A",
+ "defaultValue": "0",
+ "access": "RW"
+ }
+ ]
+}
diff --git a/amperfied/amperfied.jpg b/amperfied/amperfied.jpg
new file mode 100644
index 0000000..2af0f1b
Binary files /dev/null and b/amperfied/amperfied.jpg differ
diff --git a/amperfied/amperfied.pro b/amperfied/amperfied.pro
new file mode 100644
index 0000000..7f967f7
--- /dev/null
+++ b/amperfied/amperfied.pro
@@ -0,0 +1,17 @@
+include(../plugins.pri)
+
+MODBUS_CONNECTIONS += amperfied-registers.json
+
+MODBUS_TOOLS_CONFIG += VERBOSE
+
+include(../modbus.pri)
+
+HEADERS += \
+ energycontroldiscovery.h \
+ connecthomediscovery.h \
+ integrationpluginamperfied.h
+
+SOURCES += \
+ energycontroldiscovery.cpp \
+ connecthomediscovery.cpp \
+ integrationpluginamperfied.cpp
diff --git a/amperfied/connecthomediscovery.cpp b/amperfied/connecthomediscovery.cpp
new file mode 100644
index 0000000..ed377b0
--- /dev/null
+++ b/amperfied/connecthomediscovery.cpp
@@ -0,0 +1,138 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 "connecthomediscovery.h"
+#include "extern-plugininfo.h"
+
+ConnectHomeDiscovery::ConnectHomeDiscovery(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(dcAmperfied()) << "Discovery: Grace period timer triggered.";
+ finishDiscovery();
+ });
+}
+
+void ConnectHomeDiscovery::startDiscovery()
+{
+ qCInfo(dcAmperfied()) << "Discovery: Searching for Amperfied wallboxes in the network...";
+ NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
+
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &ConnectHomeDiscovery::checkNetworkDevice);
+
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
+ qCDebug(dcAmperfied()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
+ m_gracePeriodTimer.start();
+ discoveryReply->deleteLater();
+ });
+}
+
+QList ConnectHomeDiscovery::discoveryResults() const
+{
+ return m_discoveryResults;
+}
+
+void ConnectHomeDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
+{
+ int port = 502;
+ int slaveId = 1;
+ qCDebug(dcAmperfied()) << "Checking network device:" << networkDeviceInfo << "Port:" << port << "Slave ID:" << slaveId;
+
+ AmperfiedModbusTcpConnection *connection = new AmperfiedModbusTcpConnection(networkDeviceInfo.address(), port, slaveId, this);
+ m_connections.append(connection);
+
+ connect(connection, &AmperfiedModbusTcpConnection::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, &AmperfiedModbusTcpConnection::initializationFinished, this, [=](bool success){
+ if (!success) {
+ qCDebug(dcAmperfied()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ return;
+ }
+ Result result;
+ result.firmwareVersion = connection->version();
+ result.networkDeviceInfo = networkDeviceInfo;
+ m_discoveryResults.append(result);
+
+ qCDebug(dcAmperfied()) << "Discovery: --> Found"
+ << "Version:" << result.firmwareVersion
+ << result.networkDeviceInfo;
+
+
+ // Done with this connection
+ cleanupConnection(connection);
+ });
+
+ if (!connection->initialize()) {
+ qCDebug(dcAmperfied()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ }
+ });
+
+ // If check reachability failed...skip this host...
+ connect(connection, &AmperfiedModbusTcpConnection::checkReachabilityFailed, this, [=](){
+ qCDebug(dcAmperfied()) << "Discovery: Checking reachability failed on" << networkDeviceInfo.address().toString();
+ cleanupConnection(connection);
+ });
+
+ // Try to connect, maybe it works, maybe not...
+ connection->connectDevice();
+}
+
+void ConnectHomeDiscovery::cleanupConnection(AmperfiedModbusTcpConnection *connection)
+{
+ m_connections.removeAll(connection);
+ connection->disconnectDevice();
+ connection->deleteLater();
+}
+
+void ConnectHomeDiscovery::finishDiscovery()
+{
+ qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
+
+ // Cleanup any leftovers...we don't care any more
+ foreach (AmperfiedModbusTcpConnection *connection, m_connections)
+ cleanupConnection(connection);
+
+ qCInfo(dcAmperfied()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count()
+ << "Amperfied wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
+ m_gracePeriodTimer.stop();
+
+ emit discoveryFinished();
+}
diff --git a/amperfied/connecthomediscovery.h b/amperfied/connecthomediscovery.h
new file mode 100644
index 0000000..6126921
--- /dev/null
+++ b/amperfied/connecthomediscovery.h
@@ -0,0 +1,75 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 CONNECTHOMEDISCOVERY_H
+#define CONNECTHOMEDISCOVERY_H
+
+#include
+#include
+
+#include
+
+#include "amperfiedmodbustcpconnection.h"
+
+class ConnectHomeDiscovery : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ConnectHomeDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
+ struct Result {
+ quint16 firmwareVersion;
+ quint16 slaveId;
+ 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(AmperfiedModbusTcpConnection *connection);
+
+ void finishDiscovery();
+};
+
+#endif // CONNECTHOMEDISCOVERY_H
diff --git a/amperfied/energycontroldiscovery.cpp b/amperfied/energycontroldiscovery.cpp
new file mode 100644
index 0000000..465a4d6
--- /dev/null
+++ b/amperfied/energycontroldiscovery.cpp
@@ -0,0 +1,95 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 "energycontroldiscovery.h"
+#include "extern-plugininfo.h"
+
+EnergyControlDiscovery::EnergyControlDiscovery(ModbusRtuHardwareResource *modbusRtuResource, QObject *parent) :
+ QObject{parent},
+ m_modbusRtuResource{modbusRtuResource}
+{
+}
+
+void EnergyControlDiscovery::startDiscovery()
+{
+ qCInfo(dcAmperfied()) << "Discovery: Searching for Amperfied EnergyControl wallboxes on modbus RTU...";
+
+ QList candidateMasters;
+ foreach (ModbusRtuMaster *master, m_modbusRtuResource->modbusRtuMasters()) {
+ if (master->baudrate() == 19200 && master->dataBits() == 8 && master->stopBits() == 1 && master->parity() == QSerialPort::EvenParity) {
+ candidateMasters.append(master);
+ }
+ }
+
+ if (candidateMasters.isEmpty()) {
+ qCWarning(dcAmperfied()) << "No usable modbus RTU master found.";
+ emit discoveryFinished(false);
+ return;
+ }
+
+ foreach (ModbusRtuMaster *master, candidateMasters) {
+ if (master->connected()) {
+ tryConnect(master, 1);
+ } else {
+ qCWarning(dcAmperfied()) << "Modbus RTU master" << master->modbusUuid().toString() << "is not connected.";
+ }
+ }
+}
+
+QList EnergyControlDiscovery::discoveryResults() const
+{
+ return m_discoveryResults;
+}
+
+void EnergyControlDiscovery::tryConnect(ModbusRtuMaster *master, quint16 slaveId)
+{
+ qCDebug(dcAmperfied()) << "Scanning modbus RTU master" << master->modbusUuid() << "Slave ID:" << slaveId;
+
+ ModbusRtuReply *reply = master->readInputRegister(slaveId, 4);
+ connect(reply, &ModbusRtuReply::finished, this, [=](){
+ qCDebug(dcAmperfied()) << "Test reply finished!" << reply->error() << reply->result();
+ if (reply->error() == ModbusRtuReply::NoError && reply->result().length() > 0) {
+ quint16 version = reply->result().first();
+ if (version >= 0x0100) {
+ qCDebug(dcAmperfied()) << QString("Version is 0x%1").arg(version, 0, 16);
+ Result result {master->modbusUuid(), version, slaveId};
+ m_discoveryResults.append(result);
+ } else {
+ qCDebug(dcAmperfied()) << "Version must be at least 1.0.0 (0x0100)";
+ }
+ }
+ if (slaveId < 20) {
+ tryConnect(master, slaveId+1);
+ } else {
+ emit discoveryFinished(true);
+ }
+ });
+}
+
diff --git a/amperfied/energycontroldiscovery.h b/amperfied/energycontroldiscovery.h
new file mode 100644
index 0000000..fec47a7
--- /dev/null
+++ b/amperfied/energycontroldiscovery.h
@@ -0,0 +1,66 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 ENERGYCONTROLDISCOVERY_H
+#define ENERGYCONTROLDISCOVERY_H
+
+#include
+#include
+
+#include
+
+class EnergyControlDiscovery : public QObject
+{
+ Q_OBJECT
+public:
+ explicit EnergyControlDiscovery(ModbusRtuHardwareResource *modbusRtuResource, QObject *parent = nullptr);
+ struct Result {
+ QUuid modbusRtuMasterId;
+ quint16 firmwareVersion;
+ quint16 slaveId;
+ };
+
+ void startDiscovery();
+
+ QList discoveryResults() const;
+
+signals:
+ void discoveryFinished(bool modbusRtuMasterAvailable);
+
+private slots:
+ void tryConnect(ModbusRtuMaster *master, quint16 slaveId);
+
+private:
+ ModbusRtuHardwareResource *m_modbusRtuResource = nullptr;
+
+ QList m_discoveryResults;
+};
+
+#endif // ENERGYCONTROLDISCOVERY_H
diff --git a/amperfied/heidelberg-amperfied-modbus-protocol.pdf b/amperfied/heidelberg-amperfied-modbus-protocol.pdf
new file mode 100644
index 0000000..eaefff2
Binary files /dev/null and b/amperfied/heidelberg-amperfied-modbus-protocol.pdf differ
diff --git a/amperfied/integrationpluginamperfied.cpp b/amperfied/integrationpluginamperfied.cpp
new file mode 100644
index 0000000..da6fe21
--- /dev/null
+++ b/amperfied/integrationpluginamperfied.cpp
@@ -0,0 +1,443 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "integrationpluginamperfied.h"
+#include "plugininfo.h"
+#include "energycontroldiscovery.h"
+#include "connecthomediscovery.h"
+
+#include
+#include
+#include
+
+IntegrationPluginAmperfied::IntegrationPluginAmperfied()
+{
+
+}
+
+void IntegrationPluginAmperfied::discoverThings(ThingDiscoveryInfo *info)
+{
+ hardwareManager()->modbusRtuResource();
+
+ if (info->thingClassId() == energyControlThingClassId) {
+ EnergyControlDiscovery *discovery = new EnergyControlDiscovery(hardwareManager()->modbusRtuResource(), info);
+
+ connect(discovery, &EnergyControlDiscovery::discoveryFinished, info, [this, info, discovery](bool modbusMasterAvailable){
+ if (!modbusMasterAvailable) {
+ info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("No modbus RTU master with appropriate settings found. Please set up a modbus RTU master with a baudrate of 19200, 8 data bis, 1 stop bit and even parity first."));
+ return;
+ }
+
+ qCInfo(dcAmperfied()) << "Discovery results:" << discovery->discoveryResults().count();
+
+ foreach (const EnergyControlDiscovery::Result &result, discovery->discoveryResults()) {
+ ThingDescriptor descriptor(energyControlThingClassId, "Amperfied Energy Control", QString("Slave ID: %1").arg(result.slaveId));
+
+ ParamList params{
+ {energyControlThingRtuMasterParamTypeId, result.modbusRtuMasterId},
+ {energyControlThingSlaveIdParamTypeId, result.slaveId}
+ };
+ descriptor.setParams(params);
+
+ Thing *existingThing = myThings().findByParams(params);
+ if (existingThing) {
+ descriptor.setThingId(existingThing->id());
+ }
+ info->addThingDescriptor(descriptor);
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+ });
+
+ discovery->startDiscovery();
+
+ return;
+ }
+
+ if (info->thingClassId() == connectHomeThingClassId) {
+ ConnectHomeDiscovery *discovery = new ConnectHomeDiscovery(hardwareManager()->networkDeviceDiscovery(), info);
+ connect(discovery, &ConnectHomeDiscovery::discoveryFinished, info, [this, info, discovery](){
+ qCInfo(dcAmperfied()) << "Discovery results:" << discovery->discoveryResults().count();
+
+ foreach (const ConnectHomeDiscovery::Result &result, discovery->discoveryResults()) {
+ ThingDescriptor descriptor(connectHomeThingClassId, "Amperfied connect.home", QString("MAC: %1").arg(result.networkDeviceInfo.macAddress()));
+
+ ParamList params{
+ {connectHomeThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress()}
+ };
+ descriptor.setParams(params);
+
+ Thing *existingThing = myThings().findByParams(params);
+ if (existingThing) {
+ descriptor.setThingId(existingThing->id());
+ }
+ info->addThingDescriptor(descriptor);
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+
+ });
+ discovery->startDiscovery();
+ }
+}
+
+void IntegrationPluginAmperfied::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ qCDebug(dcAmperfied()) << "Setup" << thing << thing->params();
+
+ if (thing->thingClassId() == energyControlThingClassId) {
+
+ if (m_rtuConnections.contains(thing)) {
+ qCDebug(dcAmperfied()) << "Reconfiguring existing thing" << thing->name();
+ m_rtuConnections.take(thing)->deleteLater();
+ }
+
+ setupRtuConnection(info);
+ return;
+ }
+
+
+ if (info->thing()->thingClassId() == connectHomeThingClassId) {
+ if (m_tcpConnections.contains(info->thing())) {
+ delete m_tcpConnections.take(info->thing());
+ }
+
+ NetworkDeviceMonitor *monitor = m_monitors.value(info->thing());
+ if (!monitor) {
+ monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(MacAddress(thing->paramValue(connectHomeThingMacAddressParamTypeId).toString()));
+ m_monitors.insert(thing, monitor);
+ }
+
+ connect(info, &ThingSetupInfo::aborted, monitor, [=](){
+ if (m_monitors.contains(thing)) {
+ qCDebug(dcAmperfied()) << "Unregistering monitor because setup has been aborted.";
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
+ }
+ });
+
+ qCDebug(dcAmperfied()) << "Monitor reachable" << monitor->reachable() << thing->paramValue(connectHomeThingMacAddressParamTypeId).toString();
+ if (monitor->reachable()) {
+ setupTcpConnection(info);
+ } else {
+ connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [this, info](bool reachable){
+ qCDebug(dcAmperfied()) << "Monitor reachable changed!" << reachable;
+ if (reachable) {
+ setupTcpConnection(info);
+ }
+ });
+ }
+ }
+}
+
+void IntegrationPluginAmperfied::postSetupThing(Thing *thing)
+{
+ Q_UNUSED(thing)
+ if (!m_pluginTimer) {
+ qCDebug(dcAmperfied()) << "Starting plugin timer...";
+ m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
+ connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
+ foreach(AmperfiedModbusRtuConnection *connection, m_rtuConnections) {
+ qCDebug(dcAmperfied()) << "Updating connection" << connection->modbusRtuMaster() << connection->slaveId();
+ connection->update();
+ }
+ foreach(AmperfiedModbusTcpConnection *connection, m_tcpConnections) {
+ qCDebug(dcAmperfied()) << "Updating connection" << connection->hostAddress();
+ connection->update();
+ }
+ });
+ m_pluginTimer->start();
+ }
+}
+
+void IntegrationPluginAmperfied::executeAction(ThingActionInfo *info)
+{
+ if (info->thing()->thingClassId() == energyControlThingClassId) {
+ AmperfiedModbusRtuConnection *connection = m_rtuConnections.value(info->thing());
+
+ if (info->action().actionTypeId() == energyControlPowerActionTypeId) {
+ bool power = info->action().paramValue(energyControlPowerActionPowerParamTypeId).toBool();
+ ModbusRtuReply *reply = connection->setChargingCurrent(power ? info->thing()->stateValue(energyControlMaxChargingCurrentStateTypeId).toUInt() * 10 : 0);
+ connect(reply, &ModbusRtuReply::finished, info, [info, reply, power](){
+ if (reply->error() == ModbusRtuReply::NoError) {
+ info->thing()->setStateValue(energyControlPowerStateTypeId, power);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ qCWarning(dcAmperfied()) << "Error setting power:" << reply->error() << reply->errorString();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ });
+ return;
+ }
+
+ if (info->action().actionTypeId() == energyControlMaxChargingCurrentActionTypeId) {
+ bool power = info->thing()->stateValue(energyControlPowerStateTypeId).toBool();
+ uint max = info->action().paramValue(energyControlMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt() * 10;
+ ModbusRtuReply *reply = connection->setChargingCurrent(power ? max : 0);
+ connect(reply, &ModbusRtuReply::finished, info, [info, reply, max](){
+ if (reply->error() == ModbusRtuReply::NoError) {
+ info->thing()->setStateValue(energyControlMaxChargingCurrentStateTypeId, max);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ qCWarning(dcAmperfied()) << "Error setting power:" << reply->error() << reply->errorString();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ });
+ }
+
+ }
+
+ if (info->thing()->thingClassId() == connectHomeThingClassId) {
+ AmperfiedModbusTcpConnection *connection = m_tcpConnections.value(info->thing());
+
+ if (info->action().actionTypeId() == connectHomePowerActionTypeId) {
+ bool power = info->action().paramValue(connectHomePowerActionPowerParamTypeId).toBool();
+ QModbusReply *reply = connection->setChargingCurrent(power ? info->thing()->stateValue(connectHomeMaxChargingCurrentStateTypeId).toUInt() * 10 : 0);
+ connect(reply, &QModbusReply::finished, info, [info, reply, power](){
+ if (reply->error() == QModbusDevice::NoError) {
+ info->thing()->setStateValue(connectHomePowerStateTypeId, power);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ qCWarning(dcAmperfied()) << "Error setting power:" << reply->error() << reply->errorString();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ });
+ }
+
+ if (info->action().actionTypeId() == connectHomeMaxChargingCurrentActionTypeId) {
+ bool power = info->thing()->stateValue(connectHomePowerStateTypeId).toBool();
+ uint max = info->action().paramValue(connectHomeMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt() * 10;
+ QModbusReply *reply = connection->setChargingCurrent(power ? max : 0);
+ connect(reply, &QModbusReply::finished, info, [info, reply, max](){
+ if (reply->error() == QModbusDevice::NoError) {
+ info->thing()->setStateValue(connectHomeMaxChargingCurrentStateTypeId, max);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ qCWarning(dcAmperfied()) << "Error setting power:" << reply->error() << reply->errorString();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ });
+ }
+
+ }
+
+}
+
+void IntegrationPluginAmperfied::thingRemoved(Thing *thing)
+{
+ if (thing->thingClassId() == energyControlThingClassId) {
+ delete m_rtuConnections.take(thing);
+ }
+
+ if (thing->thingClassId() == connectHomeThingClassId) {
+ delete m_tcpConnections.take(thing);
+ }
+
+ if (myThings().isEmpty() && m_pluginTimer) {
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
+ m_pluginTimer = nullptr;
+ }
+}
+
+void IntegrationPluginAmperfied::setupRtuConnection(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ ModbusRtuMaster *master = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(thing->paramValue(energyControlThingRtuMasterParamTypeId).toUuid());
+ quint16 slaveId = thing->paramValue(energyControlThingSlaveIdParamTypeId).toUInt();
+ AmperfiedModbusRtuConnection *connection = new AmperfiedModbusRtuConnection(master, slaveId, thing);
+
+ connect(connection, &AmperfiedModbusRtuConnection::reachableChanged, thing, [connection, thing](bool reachable){
+ if (reachable) {
+ connection->initialize();
+ } else {
+ thing->setStateValue(energyControlCurrentPowerStateTypeId, 0);
+ thing->setStateValue(energyControlConnectedStateTypeId, false);
+ }
+ });
+ connect(connection, &AmperfiedModbusRtuConnection::initializationFinished, thing, [thing](bool success){
+ if (success) {
+ thing->setStateValue(energyControlConnectedStateTypeId, true);
+ }
+ });
+
+ connect(connection, &AmperfiedModbusRtuConnection::initializationFinished, info, [this, info, connection](bool success){
+ if (success) {
+ if (connection->version() < 0x0107) {
+ qCWarning(dcAmperfied()) << "We require at least version 1.0.8.";
+ info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The firmware of this wallbox is too old. Please update the wallbox to at least firmware 1.0.7."));
+ delete connection;
+ return;
+ }
+ m_rtuConnections.insert(info->thing(), connection);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The wallbox is not responding"));
+ }
+ });
+
+ connect(connection, &AmperfiedModbusRtuConnection::updateFinished, thing, [connection, thing](){
+ qCDebug(dcAmperfied()) << "Updated:" << connection;
+
+ if (connection->chargingCurrent() == 0) {
+ thing->setStateValue(energyControlPowerStateTypeId, false);
+ } else {
+ thing->setStateValue(energyControlPowerStateTypeId, true);
+ thing->setStateValue(energyControlMaxChargingCurrentStateTypeId, connection->chargingCurrent() / 10);
+ }
+ thing->setStateMinMaxValues(energyControlMaxChargingCurrentStateTypeId, connection->minChargingCurrent(), connection->maxChargingCurrent());
+ thing->setStateValue(energyControlCurrentPowerStateTypeId, connection->currentPower());
+ thing->setStateValue(energyControlTotalEnergyConsumedStateTypeId, connection->totalEnergy() / 1000.0);
+ thing->setStateValue(energyControlSessionEnergyStateTypeId, connection->sessionEnergy() / 1000.0);
+ switch (connection->chargingState()) {
+ case AmperfiedModbusRtuConnection::ChargingStateUndefined:
+ case AmperfiedModbusRtuConnection::ChargingStateA1:
+ case AmperfiedModbusRtuConnection::ChargingStateA2:
+ thing->setStateValue(energyControlPluggedInStateTypeId, false);
+ break;
+ case AmperfiedModbusRtuConnection::ChargingStateB1:
+ case AmperfiedModbusRtuConnection::ChargingStateB2:
+ case AmperfiedModbusRtuConnection::ChargingStateC1:
+ case AmperfiedModbusRtuConnection::ChargingStateC2:
+ thing->setStateValue(energyControlPluggedInStateTypeId, true);
+ break;
+ case AmperfiedModbusRtuConnection::ChargingStateDerating:
+ case AmperfiedModbusRtuConnection::ChargingStateE:
+ case AmperfiedModbusRtuConnection::ChargingStateError:
+ case AmperfiedModbusRtuConnection::ChargingStateF:
+ qCWarning(dcAmperfied()) << "Unhandled charging state:" << connection->chargingState();
+ }
+
+ int phaseCount = 0;
+ if (connection->currentL1() > 1) {
+ phaseCount++;
+ }
+ if (connection->currentL2() > 1) {
+ phaseCount++;
+ }
+ if (connection->currentL3() > 1) {
+ phaseCount++;
+ }
+ if (phaseCount > 0) {
+ thing->setStateValue(energyControlPhaseCountStateTypeId, phaseCount);
+ }
+ thing->setStateValue(energyControlChargingStateTypeId, phaseCount > 0);
+ });
+
+ connection->update();
+
+}
+
+void IntegrationPluginAmperfied::setupTcpConnection(ThingSetupInfo *info)
+{
+ qCDebug(dcAmperfied()) << "setting up TCP connection";
+ Thing *thing = info->thing();
+ NetworkDeviceMonitor *monitor = m_monitors.value(info->thing());
+ AmperfiedModbusTcpConnection *connection = new AmperfiedModbusTcpConnection(monitor->networkDeviceInfo().address(), 502, 1, info->thing());
+
+ connect(connection, &AmperfiedModbusTcpConnection::reachableChanged, thing, [connection, thing](bool reachable){
+ if (reachable) {
+ connection->initialize();
+ } else {
+ thing->setStateValue(connectHomeCurrentPowerStateTypeId, 0);
+ thing->setStateValue(connectHomeConnectedStateTypeId, false);
+ }
+ });
+
+
+ connect(connection, &AmperfiedModbusTcpConnection::initializationFinished, info, [this, info, connection](bool success){
+ if (success) {
+ if (connection->version() < 0x0107) {
+ qCWarning(dcAmperfied()) << "We require at least version 1.0.8.";
+ info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The firmware of this wallbox is too old. Please update the wallbox to at least firmware 1.0.7."));
+ delete connection;
+ return;
+ }
+ m_tcpConnections.insert(info->thing(), connection);
+ info->finish(Thing::ThingErrorNoError);
+ connection->update();
+ } else {
+ info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The wallbox is not responding"));
+ }
+ });
+
+ connect(connection, &AmperfiedModbusTcpConnection::updateFinished, thing, [connection, thing](){
+ qCDebug(dcAmperfied()) << "Updated:" << connection;
+
+ thing->setStateValue(connectHomeConnectedStateTypeId, true);
+
+ if (connection->chargingCurrent() == 0) {
+ thing->setStateValue(connectHomePowerStateTypeId, false);
+ } else {
+ thing->setStateValue(connectHomePowerStateTypeId, true);
+ thing->setStateValue(connectHomeMaxChargingCurrentStateTypeId, connection->chargingCurrent() / 10);
+ }
+ thing->setStateMinMaxValues(connectHomeMaxChargingCurrentStateTypeId, connection->minChargingCurrent(), connection->maxChargingCurrent());
+ thing->setStateValue(connectHomeCurrentPowerStateTypeId, connection->currentPower());
+ thing->setStateValue(connectHomeTotalEnergyConsumedStateTypeId, connection->totalEnergy() / 1000.0);
+ thing->setStateValue(connectHomeSessionEnergyStateTypeId, connection->sessionEnergy() / 1000.0);
+ switch (connection->chargingState()) {
+ case AmperfiedModbusTcpConnection::ChargingStateUndefined:
+ case AmperfiedModbusTcpConnection::ChargingStateA1:
+ case AmperfiedModbusTcpConnection::ChargingStateA2:
+ thing->setStateValue(connectHomePluggedInStateTypeId, false);
+ break;
+ case AmperfiedModbusTcpConnection::ChargingStateB1:
+ case AmperfiedModbusTcpConnection::ChargingStateB2:
+ case AmperfiedModbusTcpConnection::ChargingStateC1:
+ case AmperfiedModbusTcpConnection::ChargingStateC2:
+ thing->setStateValue(connectHomePluggedInStateTypeId, true);
+ break;
+ case AmperfiedModbusTcpConnection::ChargingStateDerating:
+ case AmperfiedModbusTcpConnection::ChargingStateE:
+ case AmperfiedModbusTcpConnection::ChargingStateError:
+ case AmperfiedModbusTcpConnection::ChargingStateF:
+ qCWarning(dcAmperfied()) << "Unhandled charging state:" << connection->chargingState();
+ }
+
+ int phaseCount = 0;
+ if (connection->currentL1() > 1) {
+ phaseCount++;
+ }
+ if (connection->currentL2() > 1) {
+ phaseCount++;
+ }
+ if (connection->currentL3() > 1) {
+ phaseCount++;
+ }
+ if (phaseCount > 0) {
+ thing->setStateValue(connectHomePhaseCountStateTypeId, phaseCount);
+ }
+ thing->setStateValue(connectHomeChargingStateTypeId, phaseCount > 0);
+ });
+
+ connection->connectDevice();
+}
+
+
diff --git a/amperfied/integrationpluginamperfied.h b/amperfied/integrationpluginamperfied.h
new file mode 100644
index 0000000..7a07935
--- /dev/null
+++ b/amperfied/integrationpluginamperfied.h
@@ -0,0 +1,73 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 INTEGRATIONPLUGINHEIDELBERG_H
+#define INTEGRATIONPLUGINHEIDELBERG_H
+
+#include
+#include
+#include
+
+#include "extern-plugininfo.h"
+
+#include "amperfiedmodbusrtuconnection.h"
+#include "amperfiedmodbustcpconnection.h"
+
+class IntegrationPluginAmperfied: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginamperfied.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginAmperfied();
+
+ 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:
+ void setupRtuConnection(ThingSetupInfo *info);
+ void setupTcpConnection(ThingSetupInfo *info);
+
+private:
+ PluginTimer *m_pluginTimer = nullptr;
+ QHash m_rtuConnections;
+ QHash m_tcpConnections;
+ QHash m_monitors;
+
+};
+
+#endif // INTEGRATIONPLUGINHEIDELBERG_H
+
+
diff --git a/amperfied/integrationpluginamperfied.json b/amperfied/integrationpluginamperfied.json
new file mode 100644
index 0000000..d53ba7e
--- /dev/null
+++ b/amperfied/integrationpluginamperfied.json
@@ -0,0 +1,219 @@
+{
+ "name": "amperfied",
+ "displayName": "Amperfied",
+ "id": "2c463ff6-eea8-4977-a0b7-28b70399925b",
+ "vendors": [
+ {
+ "name": "amperfied",
+ "displayName": "Amperfied",
+ "id": "0f1d4317-fd3d-4f1e-b92a-d48df03d94e9",
+ "thingClasses": [
+ {
+ "name": "energyControl",
+ "displayName": "Energy Control",
+ "id": "5b87b9ab-c78b-48c2-9f5f-5a4efebd2c58",
+ "createMethods": ["discovery", "user"],
+ "interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "22aa91ef-d6bc-4ea3-b4c5-c2f773d86510",
+ "name":"rtuMaster",
+ "displayName": "Modbus RTU master",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "fb3a1559-2fb8-4cc5-9b88-bde1714c746d",
+ "name":"slaveId",
+ "displayName": "Modbus slave ID",
+ "type": "uint",
+ "minValue": 1,
+ "maxValue": 248,
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "ed520673-4474-4301-8665-47125e20b5c0",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "22c107c6-a23b-40e4-918c-701bb0a9616e",
+ "name": "pluggedIn",
+ "displayName": "Plugged in",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "452e726a-256d-4e69-ac40-20f62c34e531",
+ "name": "charging",
+ "displayName": "Charging",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "a5254891-a60d-45b7-beee-a49688a09ca8",
+ "name": "phaseCount",
+ "displayName": "Connected phases",
+ "type": "uint",
+ "minValue": 1,
+ "maxValue": 3,
+ "defaultValue": 1
+ },
+ {
+ "id": "b7ea8449-c76f-4d42-8433-505f542abfd4",
+ "name": "currentPower",
+ "displayName": "Active power",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "87269ace-fa6b-44a1-83d3-b2e834982407",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total consumed energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.0,
+ "cached": true
+ },
+ {
+ "id": "bb119584-dec6-4bef-8b49-772feb8ca4dd",
+ "name": "power",
+ "displayName": "Charging enabled",
+ "displayNameAction": "Set charging enabled",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "7f7d729e-91e8-45c6-af40-442a47f63c23",
+ "name": "maxChargingCurrent",
+ "displayName": "Maximum charging current",
+ "displayNameAction": "Set maximum charging current",
+ "type": "uint",
+ "unit": "Ampere",
+ "minValue": 6,
+ "maxValue": 32,
+ "defaultValue": 6,
+ "writable": true
+ },
+ {
+ "id": "6fa264a2-96f4-43d1-b89f-06494830f225",
+ "name": "sessionEnergy",
+ "displayName": "Session energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ }
+ ]
+ },
+ {
+ "name": "connectHome",
+ "displayName": "connect.home",
+ "id": "f8805308-1ddd-496c-bea3-ef9163357bfa",
+ "createMethods": ["discovery", "user"],
+ "interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "b4b0556e-0d5d-4951-b5cd-e0c7986b8dcf",
+ "name":"macAddress",
+ "displayName": "MAC address",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "5ef1f3b5-d477-4458-a711-d3fa12a5fb75",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "0ecb5575-2d6e-45e7-983d-af195aa1227f",
+ "name": "pluggedIn",
+ "displayName": "Plugged in",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "88605046-9a5f-4553-82dc-f5ba15be05d4",
+ "name": "charging",
+ "displayName": "Charging",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "50b3df7c-a633-42d6-a690-9063b0c244f0",
+ "name": "phaseCount",
+ "displayName": "Connected phases",
+ "type": "uint",
+ "minValue": 1,
+ "maxValue": 3,
+ "defaultValue": 1
+ },
+ {
+ "id": "6221212e-e21d-4006-bfdc-90a8a50b2587",
+ "name": "currentPower",
+ "displayName": "Active power",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "11531bb0-82e6-4d07-8d30-7682d538db68",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total consumed energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.0,
+ "cached": true
+ },
+ {
+ "id": "a109e222-86bd-4699-8ade-3da5a6304fa7",
+ "name": "power",
+ "displayName": "Charging enabled",
+ "displayNameAction": "Set charging enabled",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "9e114f85-6f66-4b78-8b03-5d6f191337ab",
+ "name": "maxChargingCurrent",
+ "displayName": "Maximum charging current",
+ "displayNameAction": "Set maximum charging current",
+ "type": "uint",
+ "unit": "Ampere",
+ "minValue": 6,
+ "maxValue": 32,
+ "defaultValue": 6,
+ "writable": true
+ },
+ {
+ "id": "0a3fd1a6-b952-4dd1-95d1-25dfe3b0ce2f",
+ "name": "sessionEnergy",
+ "displayName": "Session energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/amperfied/meta.json b/amperfied/meta.json
new file mode 100644
index 0000000..08f1963
--- /dev/null
+++ b/amperfied/meta.json
@@ -0,0 +1,13 @@
+{
+ "title": "Amperfied",
+ "tagline": "Connect Amperfied/Heidelberg wallboxes to nymea.",
+ "icon": "amperfeid.jpg",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ "energy"
+ ]
+}
diff --git a/amperfied/translations/2c463ff6-eea8-4977-a0b7-28b70399925b-en_US.ts b/amperfied/translations/2c463ff6-eea8-4977-a0b7-28b70399925b-en_US.ts
new file mode 100644
index 0000000..3d4ed12
--- /dev/null
+++ b/amperfied/translations/2c463ff6-eea8-4977-a0b7-28b70399925b-en_US.ts
@@ -0,0 +1,177 @@
+
+
+
+
+ IntegrationPluginAmperfied
+
+
+ No modbus RTU master with appropriate settings found. Please set up a modbus RTU master with a baudrate of 19200, 8 data bis, 1 stop bit and even parity first.
+
+
+
+
+
+ The firmware of this wallbox is too old. Please update the wallbox to at least firmware 1.0.7.
+
+
+
+
+
+ The wallbox is not responding
+
+
+
+
+ amperfied
+
+
+
+ Active power
+ The name of the StateType ({6221212e-e21d-4006-bfdc-90a8a50b2587}) of ThingClass connectHome
+----------
+The name of the StateType ({b7ea8449-c76f-4d42-8433-505f542abfd4}) of ThingClass energyControl
+
+
+
+
+
+ Amperfied
+ The name of the vendor ({0f1d4317-fd3d-4f1e-b92a-d48df03d94e9})
+----------
+The name of the plugin amperfied ({2c463ff6-eea8-4977-a0b7-28b70399925b})
+
+
+
+
+
+ Charging
+ The name of the StateType ({88605046-9a5f-4553-82dc-f5ba15be05d4}) of ThingClass connectHome
+----------
+The name of the StateType ({452e726a-256d-4e69-ac40-20f62c34e531}) of ThingClass energyControl
+
+
+
+
+
+
+
+ Charging enabled
+ The name of the ParamType (ThingClass: connectHome, ActionType: power, ID: {a109e222-86bd-4699-8ade-3da5a6304fa7})
+----------
+The name of the StateType ({a109e222-86bd-4699-8ade-3da5a6304fa7}) of ThingClass connectHome
+----------
+The name of the ParamType (ThingClass: energyControl, ActionType: power, ID: {bb119584-dec6-4bef-8b49-772feb8ca4dd})
+----------
+The name of the StateType ({bb119584-dec6-4bef-8b49-772feb8ca4dd}) of ThingClass energyControl
+
+
+
+
+
+ Connected
+ The name of the StateType ({5ef1f3b5-d477-4458-a711-d3fa12a5fb75}) of ThingClass connectHome
+----------
+The name of the StateType ({ed520673-4474-4301-8665-47125e20b5c0}) of ThingClass energyControl
+
+
+
+
+
+ Connected phases
+ The name of the StateType ({50b3df7c-a633-42d6-a690-9063b0c244f0}) of ThingClass connectHome
+----------
+The name of the StateType ({a5254891-a60d-45b7-beee-a49688a09ca8}) of ThingClass energyControl
+
+
+
+
+ Energy Control
+ The name of the ThingClass ({5b87b9ab-c78b-48c2-9f5f-5a4efebd2c58})
+
+
+
+
+ MAC address
+ The name of the ParamType (ThingClass: connectHome, Type: thing, ID: {b4b0556e-0d5d-4951-b5cd-e0c7986b8dcf})
+
+
+
+
+
+
+
+ Maximum charging current
+ The name of the ParamType (ThingClass: connectHome, ActionType: maxChargingCurrent, ID: {9e114f85-6f66-4b78-8b03-5d6f191337ab})
+----------
+The name of the StateType ({9e114f85-6f66-4b78-8b03-5d6f191337ab}) of ThingClass connectHome
+----------
+The name of the ParamType (ThingClass: energyControl, ActionType: maxChargingCurrent, ID: {7f7d729e-91e8-45c6-af40-442a47f63c23})
+----------
+The name of the StateType ({7f7d729e-91e8-45c6-af40-442a47f63c23}) of ThingClass energyControl
+
+
+
+
+ Modbus RTU master
+ The name of the ParamType (ThingClass: energyControl, Type: thing, ID: {22aa91ef-d6bc-4ea3-b4c5-c2f773d86510})
+
+
+
+
+ Modbus slave ID
+ The name of the ParamType (ThingClass: energyControl, Type: thing, ID: {fb3a1559-2fb8-4cc5-9b88-bde1714c746d})
+
+
+
+
+
+ Plugged in
+ The name of the StateType ({0ecb5575-2d6e-45e7-983d-af195aa1227f}) of ThingClass connectHome
+----------
+The name of the StateType ({22c107c6-a23b-40e4-918c-701bb0a9616e}) of ThingClass energyControl
+
+
+
+
+
+ Session energy
+ The name of the StateType ({0a3fd1a6-b952-4dd1-95d1-25dfe3b0ce2f}) of ThingClass connectHome
+----------
+The name of the StateType ({6fa264a2-96f4-43d1-b89f-06494830f225}) of ThingClass energyControl
+
+
+
+
+
+ Set charging enabled
+ The name of the ActionType ({a109e222-86bd-4699-8ade-3da5a6304fa7}) of ThingClass connectHome
+----------
+The name of the ActionType ({bb119584-dec6-4bef-8b49-772feb8ca4dd}) of ThingClass energyControl
+
+
+
+
+
+ Set maximum charging current
+ The name of the ActionType ({9e114f85-6f66-4b78-8b03-5d6f191337ab}) of ThingClass connectHome
+----------
+The name of the ActionType ({7f7d729e-91e8-45c6-af40-442a47f63c23}) of ThingClass energyControl
+
+
+
+
+
+ Total consumed energy
+ The name of the StateType ({11531bb0-82e6-4d07-8d30-7682d538db68}) of ThingClass connectHome
+----------
+The name of the StateType ({87269ace-fa6b-44a1-83d3-b2e834982407}) of ThingClass energyControl
+
+
+
+
+ connect.home
+ The name of the ThingClass ({f8805308-1ddd-496c-bea3-ef9163357bfa})
+
+
+
+
diff --git a/debian/control b/debian/control
index ddaa124..1dcd7c8 100644
--- a/debian/control
+++ b/debian/control
@@ -78,6 +78,15 @@ Description: nymea integration plugin for alpha innotec heat pumps
This package contains the nymea integration plugin for alpha innotec head pumps.
+Package: nymea-plugin-amperfied
+Architecture: any
+Section: libs
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+Description: nymea integration plugin for Amperfeid/Heidelberg wallboxes
+ This package contains the nymea integration plugin for Amperfeid/Heidelberg wallboxes.
+
+
Package: nymea-plugin-bgetech
Architecture: any
Multi-Arch: same
diff --git a/debian/nymea-plugin-amperfied.install.in b/debian/nymea-plugin-amperfied.install.in
new file mode 100644
index 0000000..a34b904
--- /dev/null
+++ b/debian/nymea-plugin-amperfied.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginamperfied.so
+amperfied/translations/*qm usr/share/nymea/translations/
diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro
index d019202..5e1768d 100644
--- a/nymea-plugins-modbus.pro
+++ b/nymea-plugins-modbus.pro
@@ -6,6 +6,7 @@ SUBDIRS += nymea-modbus-cli libnymea-modbus libnymea-sunspec
PLUGIN_DIRS = \
alphainnotec \
+ amperfied \
bgetech \
drexelundweiss \
huawei \