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 \