diff --git a/debian/control b/debian/control index 3fdbb21d..5b7039f0 100644 --- a/debian/control +++ b/debian/control @@ -219,6 +219,15 @@ Description: nymea integration plugin for ESPuino This package contains the nymea integration plugin for ESPuino devices. +Package: nymea-plugin-evbox +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: nymea integration plugin for EVBox + This package contains the nymea integration plugin for EVBox wallboxes + implementing the Protocol Max v4. + + Package: nymea-plugin-fastcom Architecture: any Depends: ${misc:Depends}, diff --git a/debian/nymea-plugin-evbox.install.in b/debian/nymea-plugin-evbox.install.in new file mode 100644 index 00000000..1973c152 --- /dev/null +++ b/debian/nymea-plugin-evbox.install.in @@ -0,0 +1,2 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginevbox.so +evbox/translations/*qm usr/share/nymea/translations/ diff --git a/evbox/README.md b/evbox/README.md new file mode 100644 index 00000000..fed95b89 --- /dev/null +++ b/evbox/README.md @@ -0,0 +1,17 @@ +# EVBox + +This integration allows nymea to control EVBox wallboxes supporting the Protocol Max v4. + +## Supported things + +Generally, all EVBox wallboxes supporting the Protocol Max v4 are supported. We've tested it with + +* Elvi + +## Requirements + +The EVBox Protocol Max v4 is based on a RS485 connection. This means, a RS485 port, either onboard or via USB adapter, is required on the nymea system. +The wallbox must be configured to not be cloud controlled, in order to accept commands on the RS485 port. + + + diff --git a/evbox/evbox-logo-blue.png b/evbox/evbox-logo-blue.png new file mode 100644 index 00000000..0ba3785e Binary files /dev/null and b/evbox/evbox-logo-blue.png differ diff --git a/evbox/evbox.pro b/evbox/evbox.pro new file mode 100644 index 00000000..2126f7ef --- /dev/null +++ b/evbox/evbox.pro @@ -0,0 +1,9 @@ +include(../plugins.pri) + +QT += network serialport + +SOURCES += \ + integrationpluginevbox.cpp \ + +HEADERS += \ + integrationpluginevbox.h \ diff --git a/evbox/integrationpluginevbox.cpp b/evbox/integrationpluginevbox.cpp new file mode 100644 index 00000000..9f1188f0 --- /dev/null +++ b/evbox/integrationpluginevbox.cpp @@ -0,0 +1,348 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginevbox.h" +#include "plugininfo.h" +#include "plugintimer.h" + +#include +#include +#include + +#define STX 0x02 +#define ETX 0x03 + +IntegrationPluginEVBox::IntegrationPluginEVBox() +{ + +} + +IntegrationPluginEVBox::~IntegrationPluginEVBox() +{ +} + +void IntegrationPluginEVBox::discoverThings(ThingDiscoveryInfo *info) +{ + // Create the list of available serial interfaces + + foreach(QSerialPortInfo port, QSerialPortInfo::availablePorts()) { + + qCDebug(dcEVBox()) << "Found serial port:" << port.portName(); + QString description = port.portName() + " " + port.manufacturer() + " " + port.description(); + ThingDescriptor thingDescriptor(info->thingClassId(), "EVBox Elvi", description); + ParamList parameters; + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(evboxThingSerialPortParamTypeId).toString() == port.portName()) { + thingDescriptor.setThingId(existingThing->id()); + break; + } + } + parameters.append(Param(evboxThingSerialPortParamTypeId, port.portName())); + thingDescriptor.setParams(parameters); + info->addThingDescriptor(thingDescriptor); + } + + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginEVBox::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + QString interface = thing->paramValue(evboxThingSerialPortParamTypeId).toString(); + QSerialPort *serialPort = new QSerialPort(interface, info->thing()); + + serialPort->setBaudRate(QSerialPort::Baud38400); + serialPort->setDataBits(QSerialPort::Data8); + serialPort->setStopBits(QSerialPort::OneStop); + serialPort->setParity(QSerialPort::NoParity); + + connect(serialPort, &QSerialPort::readyRead, thing, [=]() { + thing->setStateValue(evboxConnectedStateTypeId, true); + QByteArray data = serialPort->readAll(); +// qCDebug(dcEVBox()) << "Data received from serial port:" << data; + m_inputBuffers[thing].append(data); + processInputBuffer(thing); + }); + + connect(serialPort, static_cast(&QSerialPort::error), thing, [=](){ + qCWarning(dcEVBox()) << "Serial Port error" << serialPort->error() << serialPort->errorString(); + if (serialPort->error() != QSerialPort::NoError) { + if (serialPort->isOpen()) { + serialPort->close(); + } + thing->setStateValue(evboxConnectedStateTypeId, false); + QTimer::singleShot(1000, this, [=](){ + serialPort->open(QSerialPort::ReadWrite); + }); + } + }); + + if (!serialPort->open(QSerialPort::ReadWrite)) { + qCWarning(dcEVBox()) << "Unable to open serial port"; + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to open the RS485 port. Please make sure the RS485 adapter is connected properly.")); + return; + } + + m_serialPorts.insert(thing, serialPort); + + m_pendingSetups.insert(thing, info); + connect(info, &ThingSetupInfo::finished, this, [=](){ + m_pendingSetups.remove(thing); + }); + QTimer::singleShot(2000, info, [=](){ + qCDebug(dcEVBox()) << "Timeout during setup"; + delete m_serialPorts.take(info->thing()); + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The EVBox is not responding.")); + }); + + + sendCommand(thing, Command69, 1); +} + +void IntegrationPluginEVBox::thingRemoved(Thing *thing) +{ + m_timers.remove(thing); + delete m_serialPorts.take(thing); +} + +void IntegrationPluginEVBox::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + + if (info->action().actionTypeId() == evboxPowerActionTypeId) { + bool power = info->action().paramValue(evboxPowerActionPowerParamTypeId).toBool(); + sendCommand(info->thing(), Command69, power ? info->thing()->stateValue(evboxMaxChargingCurrentStateTypeId).toUInt() : 0); + } else if (info->action().actionTypeId() == evboxMaxChargingCurrentActionTypeId) { + int maxChargingCurrent = info->action().paramValue(evboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toInt(); + sendCommand(info->thing(), Command69, maxChargingCurrent); + } + + m_pendingActions[thing].append(info); + connect(info, &ThingActionInfo::finished, this, [=](){ + m_pendingActions[thing].removeAll(info); + }); + +} + +bool IntegrationPluginEVBox::sendCommand(Thing *thing, Command command, quint16 maxChargingCurrent) +{ + QByteArray commandData; + + commandData += "80"; // Dst addr + commandData += "A0"; // Sender address + commandData += QString::number(command); + commandData += QString("%1").arg(maxChargingCurrent * 10, 4, 10, QChar('0')); + commandData += QString("%1").arg(maxChargingCurrent * 10, 4, 10, QChar('0')); + commandData += QString("%1").arg(maxChargingCurrent * 10, 4, 10, QChar('0')); + commandData += "003C"; // Timeout (60 sec) + // If we fail to refresh the wallbox after the timeout, it shall turn off, which is what we'll use as default + // when we don't know what its set to (as we can't read it). + // Hence we do *not* cache the power and maxChargingCurrent states for this one + commandData += QString("%1").arg(0, 4, 10, QChar('0')); + commandData += QString("%1").arg(0, 4, 10, QChar('0')); + commandData += QString("%1").arg(0, 4, 10, QChar('0')); + + commandData += createChecksum(commandData); + + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << static_cast(STX); + stream.writeRawData(commandData.data(), commandData.length()); + stream << static_cast(ETX); + + qCDebug(dcEVBox()) << "Writing data:" << data << "->" << data.toHex(); + QSerialPort *serialPort = m_serialPorts.value(thing); + qint64 count = serialPort->write(data); + if (count == data.length()) { + m_waitingForResponses[thing] = true; + } + return count == data.length(); +} + +QByteArray IntegrationPluginEVBox::createChecksum(const QByteArray &data) const +{ + QDataStream checksumStream(data); + quint8 sum = 0; + quint8 xOr = 0; + while (!checksumStream.atEnd()) { + quint8 byte; + checksumStream >> byte; + sum += byte; + xOr ^= byte; + } + return QString("%1%2").arg(sum,2,16, QChar('0')).arg(xOr,2,16, QChar('0')).toUpper().toLocal8Bit(); +} + +void IntegrationPluginEVBox::processInputBuffer(Thing *thing) +{ + QByteArray packet; + QDataStream inputStream(m_inputBuffers.value(thing)); + QDataStream outputStream(&packet, QIODevice::WriteOnly); + bool startFound = false, endFound = false; + + while (!inputStream.atEnd()) { + quint8 byte; + inputStream >> byte; + if (!startFound) { + if (byte == STX) { + startFound = true; + continue; + } else { + qCWarning(dcEVBox()) << "Discarding byte not matching start of frame 0x" + QString::number(byte, 16); + continue; + } + } + + if (byte == ETX) { + endFound = true; + break; + } + + outputStream << byte; + } + + if (startFound && endFound) { + m_inputBuffers[thing].remove(0, packet.length() + 2); + } else { +// qCDebug(dcEVBox()) << "Data is incomplete... Waiting for more..."; + return; + } + + if (packet.length() < 2) { // In practice it'll be longer, but let's make sure we won't crash checking the checksum on erraneous data + qCWarning(dcEVBox()) << "Packet is too short. Discarding packet..."; + return; + } + + qCDebug(dcEVBox()) << "Packet received:" << packet; + + QByteArray checksum = createChecksum(packet.left(packet.length() - 4)); + if (checksum != packet.right(4)) { + qCWarning(dcEVBox()) << "Checksum mismatch for incoming packet:" << packet << "Given checksum:" << packet.right(4) << "Expected:" << checksum; + return; + } + + // We received something valid... Assuming the last command we've sent is OK. + // There's no way to properly match a response to a command, so... + if (m_pendingSetups.contains(thing)) { + qCDebug(dcEVBox()) << "Finishing setup"; + + // Can't use a pluginTimer because it may collide with data on the wire, so we're + // manually re-starting the timer whenever we receive something. + QTimer *timer = new QTimer(thing); + m_timers.insert(thing, timer); + timer->setInterval(1000); + + connect(timer, &QTimer::timeout, thing, [=](){ + thing->setStateValue(evboxConnectedStateTypeId, !m_waitingForResponses[thing]); + + if (thing->stateValue(evboxPowerStateTypeId).toBool()) { + sendCommand(thing, Command69, thing->stateValue(evboxMaxChargingCurrentStateTypeId).toDouble()); + } else { + sendCommand(thing, Command69, 0); + } + }); + + m_pendingSetups.take(thing)->finish(Thing::ThingErrorNoError); + } + if (!m_pendingActions.value(thing).isEmpty()) { + ThingActionInfo *info = m_pendingActions.value(thing).first(); + if (info->action().actionTypeId() == evboxPowerActionTypeId) { + thing->setStateValue(evboxPowerStateTypeId, info->action().paramValue(evboxPowerActionPowerParamTypeId)); + } else if (info->action().actionTypeId() == evboxMaxChargingCurrentActionTypeId) { + thing->setStateValue(evboxMaxChargingCurrentStateTypeId, info->action().paramValue(evboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId)); + } + info->finish(Thing::ThingErrorNoError); + } + + processDataPacket(thing, packet); +} + +void IntegrationPluginEVBox::processDataPacket(Thing *thing, const QByteArray &packet) +{ + + // The data is a mess of hex and dec values... So do not wonder about the weird from/to hex mess in here... + QDataStream stream(QByteArray::fromHex(packet)); + + quint8 from, to, commandId, wallboxCount; + quint16 minPollInterval, maxChargingCurrent; + stream >> from >> to >> commandId >> minPollInterval >> maxChargingCurrent >> wallboxCount; + + commandId = QString::number(commandId, 16).toInt(); + + qCDebug(dcEVBox()) << QString("From: %1, To: %2, CMD: %3, MinPollInterval: %4, maxChargingCurrent: %5, Wallbox data count: %6") + .arg(from).arg(to).arg(commandId).arg(minPollInterval).arg(maxChargingCurrent).arg(wallboxCount); + + if (commandId != Command69) { + qCWarning(dcEVBox()) << "Only command 69 is implemented! Adjust response parsing if sending other commands."; + return; + } + + m_waitingForResponses[thing] = false; + + // Command 69 would give a list of wallboxes (they can be chained apparently) but we only support a single one for now +// for (int i = 0; i < wallboxCount; i++) { + + if (wallboxCount > 0) { + quint16 minChargingCurrent, chargingCurrentL1, chargingCurrentL2, chargingCurrentL3, cosinePhiL1, cosinePhiL2, cosinePhiL3, totalEnergyConsumed; + stream >> minChargingCurrent >> chargingCurrentL1 >> chargingCurrentL2 >> chargingCurrentL3 >> cosinePhiL1 >> cosinePhiL2 >> cosinePhiL3 >> totalEnergyConsumed; + + qCDebug(dcEVBox()) << QString("Min current: %1, actual current L1: %2, L2: %3, L3: %4, Total energy: %5") + .arg(minChargingCurrent).arg(chargingCurrentL1).arg(chargingCurrentL2).arg(chargingCurrentL3).arg(totalEnergyConsumed); + + thing->setStateMinMaxValues(evboxMaxChargingCurrentStateTypeId, minChargingCurrent / 10, maxChargingCurrent / 10); + + double currentPower = (chargingCurrentL1 + chargingCurrentL2 + chargingCurrentL3) * 23; + thing->setStateValue(evboxCurrentPowerStateTypeId, currentPower); + + thing->setStateValue(evboxTotalEnergyConsumedStateTypeId, totalEnergyConsumed / 1000.0); + + thing->setStateValue(evboxChargingStateTypeId, currentPower > 0); + + int phaseCount = 0; + if (chargingCurrentL1 > 0) { + phaseCount++; + } + if (chargingCurrentL2 > 0) { + phaseCount++; + } + if (chargingCurrentL3 > 0) { + phaseCount++; + } + // If all phases are on 0, we aren't charging and don't know how may phases are used... + // so only updating the count if we actually do know that at least one is charging. + if (phaseCount > 0) { + thing->setStateValue(evboxPhaseCountStateTypeId, phaseCount); + } + } + + m_timers.value(thing)->start(); +} + diff --git a/evbox/integrationpluginevbox.h b/evbox/integrationpluginevbox.h new file mode 100644 index 00000000..fe663d55 --- /dev/null +++ b/evbox/integrationpluginevbox.h @@ -0,0 +1,83 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINEVBOX_H +#define INTEGRATIONPLUGINEVBOX_H + +#include "integrations/integrationplugin.h" + +#include "extern-plugininfo.h" + +#include + +class QSerialPort; + +class IntegrationPluginEVBox: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginevbox.json") + Q_INTERFACES(IntegrationPlugin) + +public: + enum Command { + Command68 = 68, + Command69 = 69 + }; + Q_ENUM(Command) + + explicit IntegrationPluginEVBox(); + ~IntegrationPluginEVBox(); + + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void thingRemoved(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; + +private: + bool sendCommand(Thing *thing, Command command, quint16 maxChargingCurrent); + + QByteArray createChecksum(const QByteArray &data) const; + + void processInputBuffer(Thing *thing); + void processDataPacket(Thing *thing, const QByteArray &packet); + +private: + QHash m_serialPorts; + QHash m_pendingSetups; + QHash> m_pendingActions; + + QHash m_inputBuffers; + + QHash m_timers; + QHash m_waitingForResponses; +}; + +#endif // INTEGRATIONPLUGINEVBOX_H diff --git a/evbox/integrationpluginevbox.json b/evbox/integrationpluginevbox.json new file mode 100644 index 00000000..ec9a1959 --- /dev/null +++ b/evbox/integrationpluginevbox.json @@ -0,0 +1,97 @@ +{ + "name": "EVBox", + "displayName": "EVBox", + "id": "3362ac5c-5e2f-43c0-b3fc-70a98773e119", + "vendors": [ + { + "name": "evbox", + "displayName": "EVBox", + "id": "435d8843-887a-4642-b2f5-cd27d18bdb95", + "thingClasses": [ + { + "id": "d73a14e3-10af-47bc-9bc7-a5ff6e52f72c", + "name": "evbox", + "displayName": "Elvi", + "createMethods": ["discovery"], + "setupMethod": "justadd", + "interfaces": [ "evcharger", "connectable" ], + "paramTypes": [ + { + "id": "bce7c412-c19a-4e60-a11f-fe8308408abf", + "name":"serialPort", + "displayName": "Serial port", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "5ef06038-9fa9-4d5d-8d9b-0375b8aa343a", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "e3ed9334-68bf-47eb-bd9a-d9a800529bce", + "name": "power", + "displayName": "Charging enabled", + "displayNameAction": "Enable/disable charging", + "type": "bool", + "defaultValue": false, + "writable": true, + "cached": false + }, + { + "id": "cc9ae86d-fc86-473f-ae90-d9eb20d7a011", + "name": "maxChargingCurrent", + "displayName": "Maximum charging current", + "displayNameAction": "Set maximum charging current", + "type": "uint", + "writable": true, + "unit": "Ampere", + "minValue": "6", + "maxValue": "22", + "defaultValue": 6, + "cached": false + }, + { + "id": "8d3c80b7-f1f1-48de-8b7a-f99b9bc688b7", + "name": "currentPower", + "displayName": "Current power consumption", + "type": "double", + "unit": "Watt", + "defaultValue": 0, + "cached": false + }, + { + "id": "9fd15d14-c228-4af6-85af-4cb171d6f9f0", + "name": "totalEnergyConsumed", + "displayName": "Total consumed energy", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "6439fdba-dc03-454f-bc33-0f6e2619d2ab", + "name": "charging", + "displayName": "Charging", + "type": "bool", + "defaultValue": false + }, + { + "id": "1120abe3-1878-4301-a701-014b24fd1e41", + "name": "phaseCount", + "displayName": "Used phases", + "type": "uint", + "minValue": 1, + "maxValue": 3, + "defaultValue": 1 + } + ] + } + ] + } + ] +} diff --git a/evbox/meta.json b/evbox/meta.json new file mode 100644 index 00000000..a0d966b3 --- /dev/null +++ b/evbox/meta.json @@ -0,0 +1,13 @@ +{ + "title": "EVBox", + "tagline": "Integrates EVBox wallboxes with nymea.", + "icon": "evbox-logo-blue.png", + "stability": "consumer", + "offline": true, + "technologies": [ + "serial-port" + ], + "categories": [ + "energy" + ] +} diff --git a/evbox/translations/3362ac5c-5e2f-43c0-b3fc-70a98773e119-en_US.ts b/evbox/translations/3362ac5c-5e2f-43c0-b3fc-70a98773e119-en_US.ts new file mode 100644 index 00000000..0d41216c --- /dev/null +++ b/evbox/translations/3362ac5c-5e2f-43c0-b3fc-70a98773e119-en_US.ts @@ -0,0 +1,101 @@ + + + + + EVBox + + + Charging + The name of the StateType ({6439fdba-dc03-454f-bc33-0f6e2619d2ab}) of ThingClass evbox + + + + + + Charging enabled + The name of the ParamType (ThingClass: evbox, ActionType: power, ID: {e3ed9334-68bf-47eb-bd9a-d9a800529bce}) +---------- +The name of the StateType ({e3ed9334-68bf-47eb-bd9a-d9a800529bce}) of ThingClass evbox + + + + + Connected + The name of the StateType ({5ef06038-9fa9-4d5d-8d9b-0375b8aa343a}) of ThingClass evbox + + + + + Current power consumption + The name of the StateType ({8d3c80b7-f1f1-48de-8b7a-f99b9bc688b7}) of ThingClass evbox + + + + + + EVBox + The name of the vendor ({435d8843-887a-4642-b2f5-cd27d18bdb95}) +---------- +The name of the plugin EVBox ({3362ac5c-5e2f-43c0-b3fc-70a98773e119}) + + + + + Elvi + The name of the ThingClass ({d73a14e3-10af-47bc-9bc7-a5ff6e52f72c}) + + + + + Enable/disable charging + The name of the ActionType ({e3ed9334-68bf-47eb-bd9a-d9a800529bce}) of ThingClass evbox + + + + + + Maximum charging current + The name of the ParamType (ThingClass: evbox, ActionType: maxChargingCurrent, ID: {cc9ae86d-fc86-473f-ae90-d9eb20d7a011}) +---------- +The name of the StateType ({cc9ae86d-fc86-473f-ae90-d9eb20d7a011}) of ThingClass evbox + + + + + Serial port + The name of the ParamType (ThingClass: evbox, Type: thing, ID: {bce7c412-c19a-4e60-a11f-fe8308408abf}) + + + + + Set maximum charging current + The name of the ActionType ({cc9ae86d-fc86-473f-ae90-d9eb20d7a011}) of ThingClass evbox + + + + + Total consumed energy + The name of the StateType ({9fd15d14-c228-4af6-85af-4cb171d6f9f0}) of ThingClass evbox + + + + + Used phases + The name of the StateType ({1120abe3-1878-4301-a701-014b24fd1e41}) of ThingClass evbox + + + + + IntegrationPluginEVBox + + + Unable to open the RS485 port. Please make sure the RS485 adapter is connected properly. + + + + + The EVBox is not responding. + + + + diff --git a/nymea-plugins.pro b/nymea-plugins.pro index f6d3babf..7c714647 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -22,6 +22,7 @@ PLUGIN_DIRS = \ elgato \ eq-3 \ espuino \ + evbox \ fastcom \ flowercare \ fronius \