Add PCE EV11.3 support

This commit is contained in:
Simon Stürz 2024-03-25 15:14:00 +01:00
parent cc92df2d74
commit 22ef68ffa0
17 changed files with 1817 additions and 0 deletions

9
debian/control vendored
View File

@ -246,6 +246,15 @@ Description: nymea integration plugin for UniPi devices
This package contains the nymea integration plugin for UniPi devices.
Package: nymea-plugin-pcelectric
Architecture: any
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
Description: nymea integration plugin for PCE wallboxes
This package contains the nymea integration plugin for wallboxes made by PC Electric.
Package: nymea-plugin-phoenixconnect
Architecture: any
Section: libs

View File

@ -0,0 +1,2 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginpcelectric.so
pcelectric/translations/*qm usr/share/nymea/translations/

View File

@ -17,6 +17,7 @@ PLUGIN_DIRS = \
modbuscommander \
mtec \
mypv \
pcelectric \
phoenixconnect \
schrack \
senseair \

View File

@ -0,0 +1,334 @@
{
"className": "EV11",
"protocol": "TCP",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 10,
"checkReachableRegister": "chargingState",
"enums": [
{
"name": "ChargingState",
"values": [
{
"key": "Initializing",
"value": 0
},
{
"key": "A1",
"value": 1
},
{
"key": "A2",
"value": 2
},
{
"key": "B1",
"value": 3
},
{
"key": "B2",
"value": 4
},
{
"key": "C1",
"value": 5
},
{
"key": "C2",
"value": 6
},
{
"key": "Error",
"value": 7
}
]
},
{
"name": "ChargingRelayState",
"values": [
{
"key": "NoCharging",
"value": 0
},
{
"key": "SinglePhase",
"value": 1
},
{
"key": "TheePhase",
"value": 2
}
]
},
{
"name": "Error",
"values": [
{
"key": "NoError",
"value": 0
},
{
"key": "Overheating",
"value": 1
},
{
"key": "DCFaultCurrent",
"value": 2
},
{
"key": "ChargingWithVentilation",
"value": 3
},
{
"key": "CPErrorEF",
"value": 4
},
{
"key": "CPErrorBypass",
"value": 5
},
{
"key": "CPErrorDiodFault",
"value": 6
},
{
"key": "DCFaultCurrentCalibrating",
"value": 7
},
{
"key": "DCFaultCurrentCommunication",
"value": 8
},
{
"key": "DCFaultCurrentError",
"value": 9
}
]
}
],
"blocks": [
{
"id": "status",
"readSchedule": "update",
"registers": [
{
"id": "chargingState",
"address": 100,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"enum": "ChargingState",
"description": "Current charging state",
"defaultValue": "ChargingStateInitializing",
"access": "R"
},
{
"id": "chargingRelayState",
"address": 101,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"enum": "ChargingRelayState",
"description": "Charging relay state",
"defaultValue": "ChargingRelayStateNoCharging",
"access": "R"
},
{
"id": "maxChargingCurrentDip",
"address": 102,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Maximum charging current (DIP)",
"unit": "mA",
"defaultValue": "6000",
"access": "R"
},
{
"id": "phaseAutoSwitch",
"address": 103,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Automatic phase switching",
"defaultValue": "0",
"access": "R"
},
{
"id": "activeChargingCurrent",
"address": 104,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Active charging current",
"unit": "mA",
"defaultValue": "0",
"access": "R"
},
{
"id": "sessionDuration",
"address": 105,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Session durration",
"unit": "10 seconds",
"defaultValue": "0",
"access": "R"
},
{
"id": "powerMeter0",
"address": 106,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Current session energy",
"unit": "kWh",
"staticScaleFactor": -2,
"defaultValue": "0",
"access": "R"
},
{
"id": "powerMeter1",
"address": 107,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Last session energy",
"unit": "kWh",
"staticScaleFactor": -2,
"defaultValue": "0",
"access": "R"
},
{
"id": "powerMeter3",
"address": 108,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Penultimate session energy",
"unit": "kWh",
"staticScaleFactor": -2,
"defaultValue": "0",
"access": "R"
},
{
"id": "temperature",
"address": 109,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Onboard temperature",
"unit": "°C",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "R"
},
{
"id": "error",
"address": 110,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"enum": "Error",
"description": "Error",
"defaultValue": "ErrorNoError",
"access": "R"
}
]
},
{
"id": "initInfos",
"readSchedule": "init",
"registers": [
{
"id": "firmwareRevision",
"address": 135,
"size": 2,
"type": "string",
"registerType": "holdingRegister",
"description": "Firmware revision (ASCII)",
"access": "R"
},
{
"id": "hardwareRevision",
"address": 137,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Hardware revision",
"defaultValue": "0",
"access": "R"
},
{
"id": "serialNumber",
"address": 138,
"size": 3,
"type": "raw",
"registerType": "holdingRegister",
"description": "Serial number",
"access": "R"
},
{
"id": "macAddress",
"address": 141,
"size": 3,
"type": "raw",
"registerType": "holdingRegister",
"description": "MAC address",
"access": "R"
}
]
}
],
"registers": [
{
"id": "chargingCurrent",
"address": 200,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Write charging current",
"unit": "mA",
"access": "WO"
},
{
"id": "chargingCurrentOffline",
"address": 201,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Write charging current",
"unit": "mA",
"access": "WO"
},
{
"id": "maxChargingTime",
"address": 202,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Max charging time",
"unit": "Minutes",
"access": "WO"
},
{
"id": "heartbeat",
"address": 203,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Heartbeat (write < 60s to keep alive)",
"access": "WO"
},
{
"id": "ledBrightness",
"address": 204,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "LED brightness",
"unit": "%",
"access": "WO"
}
]
}

3
pcelectric/README.md Normal file
View File

@ -0,0 +1,3 @@
# PC Electric

View File

@ -0,0 +1,398 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 <https://www.gnu.org/licenses/>.
*
* 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 "integrationpluginpcelectric.h"
#include "pcelectricdiscovery.h"
#include "plugininfo.h"
#include <hardwaremanager.h>
#include <hardware/electricity.h>
IntegrationPluginPcElectric::IntegrationPluginPcElectric()
{
}
void IntegrationPluginPcElectric::init()
{
}
void IntegrationPluginPcElectric::discoverThings(ThingDiscoveryInfo *info)
{
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
qCWarning(dcPcElectric()) << "The network discovery is not available on this platform.";
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
return;
}
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
PcElectricDiscovery *discovery = new PcElectricDiscovery(hardwareManager()->networkDeviceDiscovery(), 502, 1, info);
connect(discovery, &PcElectricDiscovery::discoveryFinished, info, [=](){
foreach (const PcElectricDiscovery::Result &result, discovery->results()) {
ThingDescriptor descriptor(ev11ThingClassId, "PCE EV11.3 (" + result.serialNumber + ")", "Version: " + result.firmwareRevision + " - " + result.networkDeviceInfo.address().toString());
qCDebug(dcPcElectric()) << "Discovered:" << descriptor.title() << descriptor.description();
// Check if we already have set up this device
Things existingThings = myThings().filterByParam(ev11ThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
if (existingThings.count() == 1) {
qCDebug(dcPcElectric()) << "This PCE wallbox already exists in the system:" << result.networkDeviceInfo;
descriptor.setThingId(existingThings.first()->id());
}
ParamList params;
params << Param(ev11ThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
// Note: if we discover also the port and modbusaddress, we must fill them in from the discovery here, for now everywhere the defaults...
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
// Start the discovery process
discovery->startDiscovery();
}
void IntegrationPluginPcElectric::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
qCDebug(dcPcElectric()) << "Setup thing" << thing << thing->params();
if (m_connections.contains(thing)) {
qCDebug(dcPcElectric()) << "Reconfiguring existing thing" << thing->name();
m_connections.take(thing)->deleteLater();
if (m_monitors.contains(thing)) {
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
}
}
MacAddress macAddress = MacAddress(thing->paramValue(ev11ThingMacAddressParamTypeId).toString());
if (!macAddress.isValid()) {
qCWarning(dcPcElectric()) << "The configured mac address is not valid" << thing->params();
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing."));
return;
}
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
m_monitors.insert(thing, monitor);
connect(info, &ThingSetupInfo::aborted, monitor, [=](){
if (m_monitors.contains(thing)) {
qCDebug(dcPcElectric()) << "Unregistering monitor because setup has been aborted.";
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
}
});
// Only make sure the connection is working in the initial setup, otherwise we let the monitor do the work
if (info->isInitialSetup()) {
// Continue with setup only if we know that the network device is reachable
if (monitor->reachable()) {
setupConnection(info);
} else {
// otherwise wait until we reach the networkdevice before setting up the device
qCDebug(dcPcElectric()) << "Network device" << thing->name() << "is not reachable yet. Continue with the setup once reachable.";
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
if (reachable) {
qCDebug(dcPcElectric()) << "Network device" << thing->name() << "is now reachable. Continue with the setup...";
setupConnection(info);
}
});
}
} else {
setupConnection(info);
}
return;
}
void IntegrationPluginPcElectric::postSetupThing(Thing *thing)
{
qCDebug(dcPcElectric()) << "Post setup thing" << thing->name();
if (!m_refreshTimer) {
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
connect(m_refreshTimer, &PluginTimer::timeout, this, [this] {
foreach (PceWallbox *connection, m_connections) {
if (connection->reachable()) {
connection->update();
}
}
});
qCDebug(dcPcElectric()) << "Starting refresh timer...";
m_refreshTimer->start();
}
}
void IntegrationPluginPcElectric::thingRemoved(Thing *thing)
{
qCDebug(dcPcElectric()) << "Thing removed" << thing->name();
if (m_connections.contains(thing)) {
PceWallbox *connection = m_connections.take(thing);
connection->disconnectDevice();
connection->deleteLater();
}
// Unregister related hardware resources
if (m_monitors.contains(thing))
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
if (myThings().isEmpty() && m_refreshTimer) {
qCDebug(dcPcElectric()) << "Stopping reconnect timer";
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
m_refreshTimer = nullptr;
}
}
void IntegrationPluginPcElectric::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
PceWallbox *connection = m_connections.value(thing);
if (!connection->reachable()) {
qCWarning(dcPcElectric()) << "Could not execute action because the connection is not available.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (info->action().actionTypeId() == ev11PowerActionTypeId) {
bool power = info->action().paramValue(ev11PowerActionPowerParamTypeId).toBool();
quint16 chargingCurrent = 0;
if (power) {
chargingCurrent = thing->stateValue(ev11MaxChargingCurrentStateTypeId).toUInt() * 1000;
if (thing->stateValue(ev11DesiredPhaseCountStateTypeId).toUInt() == 3) {
// If 3 phase charging is enabled, we set the first bit
chargingCurrent |= static_cast<quint16>(1) << 15;
}
}
qCDebug(dcPcElectric()) << "Writing charging current register" << chargingCurrent << "mA";
QueuedModbusReply *reply = connection->setChargingCurrent(chargingCurrent);
connect(reply, &QueuedModbusReply::finished, info, [reply, info, thing, power, chargingCurrent](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set power state to" << power << "(" << chargingCurrent << "mA)" << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcPcElectric()) << "Successfully set power state to" << power << "(" << chargingCurrent << "mA)";
thing->setStateValue(ev11PowerStateTypeId, power);
info->finish(Thing::ThingErrorNoError);
});
return;
} else if (info->action().actionTypeId() == ev11MaxChargingCurrentActionTypeId) {
uint desiredChargingCurrent = info->action().paramValue(ev11MaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
qCDebug(dcPcElectric()) << "Set max charging current to" << desiredChargingCurrent << "A";
if (thing->stateValue(ev11PowerStateTypeId).toBool()) {
// The charging is enabled, let's write the value to the wallbox
quint16 finalChargingCurrent = static_cast<quint16>(desiredChargingCurrent * 1000);
if (thing->stateValue(ev11DesiredPhaseCountStateTypeId).toUInt() == 3) {
// If 3 phase charging is enabled, we set the first bit
finalChargingCurrent |= static_cast<quint16>(1) << 15;
}
qCDebug(dcPcElectric()) << "Writing charging current register" << finalChargingCurrent << "mA";
QueuedModbusReply *reply = connection->setChargingCurrent(finalChargingCurrent);
connect(reply, &QueuedModbusReply::finished, info, [reply, info, thing, desiredChargingCurrent](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set charging current to" << desiredChargingCurrent << "mA" << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcPcElectric()) << "Successfully set charging current to" << desiredChargingCurrent << "mA";
thing->setStateValue(ev11MaxChargingCurrentStateTypeId, desiredChargingCurrent);
info->finish(Thing::ThingErrorNoError);
});
} else {
// Save the value in the state, but do not send the value to the wallbox since the power state is reflected using the charging current...
qCDebug(dcPcElectric()) << "Setting charging current to" << desiredChargingCurrent << "without synching to wallbox since the power state is false";
thing->setStateValue(ev11MaxChargingCurrentStateTypeId, desiredChargingCurrent);
info->finish(Thing::ThingErrorNoError);
}
return;
} else if (info->action().actionTypeId() == ev11DesiredPhaseCountActionTypeId) {
uint desiredPhaseCount = info->action().paramValue(ev11DesiredPhaseCountActionDesiredPhaseCountParamTypeId).toUInt();
qCDebug(dcPcElectric()) << "Desried phase count changed" << desiredPhaseCount;
thing->setStateValue(ev11DesiredPhaseCountStateTypeId, desiredPhaseCount);
info->finish(Thing::ThingErrorNoError);
// Update the max charging current according to the new desired phase count
if (thing->stateValue(ev11PowerStateTypeId).toBool()) {
uint chargingCurrent = thing->stateValue(ev11MaxChargingCurrentStateTypeId).toUInt();
quint16 finalChargingCurrent = static_cast<quint16>(chargingCurrent * 1000);
if (thing->stateValue(ev11DesiredPhaseCountStateTypeId).toUInt() == 3) {
// If 3 phase charging is enabled, we set the first bit
finalChargingCurrent |= static_cast<quint16>(1) << 15;
}
qCDebug(dcPcElectric()) << "Writing charging current register" << finalChargingCurrent << "mA";
QueuedModbusReply *reply = connection->setChargingCurrent(finalChargingCurrent);
connect(reply, &QueuedModbusReply::finished, info, [reply, finalChargingCurrent](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set charging current to" << finalChargingCurrent << "mA" << reply->errorString();
return;
}
qCDebug(dcPcElectric()) << "Successfully set charging current to" << finalChargingCurrent << "mA";
});
}
return;
}
Q_ASSERT_X(false, "IntegrationPluginPcElectric::executeAction", QString("Unhandled action: %1").arg(info->action().actionTypeId().toString()).toLocal8Bit());
}
void IntegrationPluginPcElectric::setupConnection(ThingSetupInfo *info)
{
Thing *thing = info->thing();
NetworkDeviceMonitor *monitor = m_monitors.value(thing);
qCDebug(dcPcElectric()) << "Setting up PCE wallbox finished successfully" << monitor->networkDeviceInfo().address().toString();
PceWallbox *connection = new PceWallbox(monitor->networkDeviceInfo().address(), 502, 1, this);
connect(info, &ThingSetupInfo::aborted, connection, &PceWallbox::deleteLater);
// Monitor reachability
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
if (!thing->setupComplete())
return;
qCDebug(dcPcElectric()) << "Network device monitor for" << thing->name() << (reachable ? "is now reachable" : "is not reachable any more" );
if (reachable && !thing->stateValue("connected").toBool()) {
connection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address());
connection->connectDevice();
} else if (!reachable) {
// Note: We disable autoreconnect explicitly and we will
// connect the device once the monitor says it is reachable again
connection->disconnectDevice();
}
});
// Connection reachability
connect(connection, &PceWallbox::reachableChanged, thing, [thing](bool reachable){
qCInfo(dcPcElectric()) << "Reachable changed to" << reachable << "for" << thing;
thing->setStateValue("connected", reachable);
});
connect(connection, &PceWallbox::updateFinished, thing, [thing, connection](){
qCDebug(dcPcElectric()) << "Update finished for" << thing;
qCDebug(dcPcElectric()) << connection;
if (!connection->phaseAutoSwitch()) {
// Note: if auto phase switching is disabled, the wallbox forces 3 phase charging
thing->setStatePossibleValues(ev11DesiredPhaseCountStateTypeId, { 3 }); // Disable phase switching (default 3)
thing->setStateValue(ev11DesiredPhaseCountStateTypeId, 3);
thing->setStateValue(ev11PhaseCountStateTypeId, 3);
} else {
thing->setStatePossibleValues(ev11DesiredPhaseCountStateTypeId, { 1, 3 }); // Enable phase switching
}
if (connection->chargingRelayState() != EV11ModbusTcpConnection::ChargingRelayStateNoCharging) {
if (connection->chargingRelayState() == EV11ModbusTcpConnection::ChargingRelayStateSinglePhase) {
thing->setStateValue(ev11PhaseCountStateTypeId, 1);
} else if (connection->chargingRelayState() == EV11ModbusTcpConnection::ChargingRelayStateTheePhase) {
thing->setStateValue(ev11PhaseCountStateTypeId, 3);
}
}
thing->setStateMaxValue(ev11MaxChargingCurrentStateTypeId, connection->maxChargingCurrentDip() / 1000);
thing->setStateValue(ev11PluggedInStateTypeId, connection->chargingState() >= PceWallbox::ChargingStateB1 &&
connection->chargingState() < PceWallbox::ChargingStateError);
thing->setStateValue(ev11ChargingStateTypeId, connection->chargingState() == PceWallbox::ChargingStateC2);
if (connection->chargingRelayState() != EV11ModbusTcpConnection::ChargingRelayStateNoCharging) {
thing->setStateValue(ev11PhaseCountStateTypeId, connection->chargingRelayState() == EV11ModbusTcpConnection::ChargingRelayStateSinglePhase ? 1 : 3);
}
thing->setStateValue(ev11CurrentVersionStateTypeId, connection->firmwareRevision());
thing->setStateValue(ev11SessionEnergyStateTypeId, connection->powerMeter0());
thing->setStateValue(ev11TemperatureStateTypeId, connection->temperature());
switch (connection->error()) {
case EV11ModbusTcpConnection::ErrorNoError:
thing->setStateValue(ev11ErrorStateTypeId, "Kein Fehler aktiv");
break;
case EV11ModbusTcpConnection::ErrorOverheating:
thing->setStateValue(ev11ErrorStateTypeId, "Übertemperatur. Ladevorgang wird automatisch fortgesetzt.");
break;
case EV11ModbusTcpConnection::ErrorDCFaultCurrent:
thing->setStateValue(ev11ErrorStateTypeId, "DC Fehlerstromsensor ausgelöst.");
break;
case EV11ModbusTcpConnection::ErrorChargingWithVentilation:
thing->setStateValue(ev11ErrorStateTypeId, "Ladeanforderung mit Belüftung.");
break;
case EV11ModbusTcpConnection::ErrorCPErrorEF:
thing->setStateValue(ev11ErrorStateTypeId, "CP Signal, Fehlercode E oder F.");
break;
case EV11ModbusTcpConnection::ErrorCPErrorBypass:
thing->setStateValue(ev11ErrorStateTypeId, "CP Signal, bypass.");
break;
case EV11ModbusTcpConnection::ErrorCPErrorDiodFault:
thing->setStateValue(ev11ErrorStateTypeId, "CP Signal, Diode defekt.");
break;
case EV11ModbusTcpConnection::ErrorDCFaultCurrentCalibrating:
thing->setStateValue(ev11ErrorStateTypeId, "DC Fehlerstromsensor, Kalibrirung.");
break;
case EV11ModbusTcpConnection::ErrorDCFaultCurrentCommunication:
thing->setStateValue(ev11ErrorStateTypeId, "DC Fehlerstromsensor, Kommunikationsfehler.");
break;
case EV11ModbusTcpConnection::ErrorDCFaultCurrentError:
thing->setStateValue(ev11ErrorStateTypeId, "DC Fehlerstromsensor, Fehler.");
break;
}
});
connect(thing, &Thing::settingChanged, connection, [thing, connection](const ParamTypeId &paramTypeId, const QVariant &value){
if (paramTypeId == ev11SettingsLedBrightnessParamTypeId) {
quint16 percentage = value.toUInt();
qCDebug(dcPcElectric()) << "Set LED brightness" << percentage << "%";
QueuedModbusReply *reply = connection->setLedBrightness(percentage);
connect(reply, &QueuedModbusReply::finished, thing, [reply, percentage](){
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Could not set led brightness to" << percentage << "%" << reply->errorString();
return;
}
qCDebug(dcPcElectric()) << "Successfully set led brightness to" << percentage << "%";
});
}
});
m_connections.insert(thing, connection);
info->finish(Thing::ThingErrorNoError);
// Connect reight the way if the monitor indicates reachable, otherwise the connect will handle the connect later
if (monitor->reachable())
connection->connectDevice();
}

View File

@ -0,0 +1,69 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 <https://www.gnu.org/licenses/>.
*
* 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 INTEGRATIONPLUGINPCELECTRIC_H
#define INTEGRATIONPLUGINPCELECTRIC_H
#include <QObject>
#include <integrations/integrationplugin.h>
#include <network/networkdevicediscovery.h>
#include <plugintimer.h>
#include "pcewallbox.h"
#include "extern-plugininfo.h"
class IntegrationPluginPcElectric : public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginpcelectric.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginPcElectric();
void init() override;
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, PceWallbox *> m_connections;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
void setupConnection(ThingSetupInfo *info);
};
#endif // INTEGRATIONPLUGINPCELECTRIC_H

View File

@ -0,0 +1,148 @@
{
"name": "PcElectric",
"displayName": "PC Electric",
"id": "aa7ff833-a8e0-45cc-a1ef-65f05871f272",
"paramTypes":[ ],
"vendors": [
{
"name": "PcElectric",
"displayName": "PC Electric GmbH",
"id": "b365937b-f1d6-46bf-9ff1-e787373b8aa6",
"thingClasses": [
{
"name": "ev11",
"displayName": "PCE EV11.3",
"id": "88d96940-a940-4a07-8176-5e6aba7ca832",
"createMethods": ["discovery", "user"],
"interfaces": ["evcharger", "connectable"],
"paramTypes": [
{
"id": "0a3f8d12-9d33-4ae2-b763-9568f32e8da1",
"name":"macAddress",
"displayName": "MAC address",
"type": "QString",
"inputType": "MacAddress",
"defaultValue": ""
}
],
"settingsTypes": [
{
"id": "3a1329a2-84cc-47b9-a6c2-e96fdfd0c454",
"name": "ledBrightness",
"displayName": "LED brightness",
"type": "uint",
"minValue": 0,
"maxValue": 100,
"unit": "Percentage",
"defaultValue": 50
}
],
"stateTypes": [
{
"id": "ca8d680c-c2f8-456a-a246-9c6cd64e25a7",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"cached": false,
"defaultValue": false
},
{
"id": "c12a7a27-fa56-450c-a1ec-717c868554f2",
"name": "power",
"displayName": "Charging enabled",
"displayNameEvent": "Charging enabled or disabled",
"displayNameAction": "Enable or disable charging",
"type": "bool",
"defaultValue": false,
"writable": true
},
{
"id": "b5bbf23c-06db-463b-bb5c-3aea38e18818",
"name": "maxChargingCurrent",
"displayName": "Maximum charging current",
"displayNameEvent": "Maximum charging current changed",
"displayNameAction": "Set maximum charging current",
"type": "uint",
"unit": "Ampere",
"defaultValue": 6,
"minValue": 6,
"maxValue": 16,
"writable": true
},
{
"id": "50164bbd-9802-4cf6-82de-626b74293a1b",
"name": "pluggedIn",
"displayName": "Plugged in",
"displayNameEvent": "Plugged or unplugged",
"type": "bool",
"defaultValue": false
},
{
"id": "b7972cd7-471a-46bd-ab99-f49997f12309",
"name": "charging",
"displayName": "Charging",
"displayNameEvent": "Charging started or stopped",
"type": "bool",
"defaultValue": false
},
{
"id": "bca88c23-e940-40c1-afca-eb511fd17aab",
"name": "phaseCount",
"displayName": "Active phases",
"type": "uint",
"minValue": 1,
"maxValue": 3,
"defaultValue": 3
},
{
"id": "d91f7d96-2599-400a-91da-d164477098b7",
"name": "desiredPhaseCount",
"displayName": "Desired phase count",
"displayNameAction": "Set desired phase count",
"type": "uint",
"minValue": 1,
"maxValue": 3,
"possibleValues": [1, 3],
"defaultValue": 3,
"writable": true
},
{
"id": "3da3ee80-e9e7-4237-85a6-b4adcb2f483b",
"name": "sessionEnergy",
"displayName": "Session energy",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0
},
{
"id": "bb092562-377e-458e-bb8a-735af9036652",
"name": "temperature",
"displayName": "Onboard temperature",
"displayNameEvent": "Onboard temperature changed",
"unit": "DegreeCelsius",
"type": "double",
"defaultValue": 0,
"suggestLogging": true
},
{
"id": "2ea1a53f-b2b0-452d-8060-cdb114db05a7",
"name": "error",
"displayName": "Error",
"type": "QString",
"defaultValue": "Kein Fehler",
"suggestLogging": true
},
{
"id": "142b4276-e2e9-4149-adc4-89d9d3e31117",
"name": "currentVersion",
"displayName": "Firmware version",
"type": "QString",
"defaultValue": ""
}
]
}
]
}
]
}

13
pcelectric/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "PC Electric",
"tagline": "Integrate the PCE EV11.3 wallbox with nymea.",
"icon": "pce.png",
"stability": "consumer",
"offline": true,
"technologies": [
"modbus"
],
"categories": [
"energy"
]
}

BIN
pcelectric/pce.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

17
pcelectric/pcelectric.pro Normal file
View File

@ -0,0 +1,17 @@
include(../plugins.pri)
# Generate modbus connection
MODBUS_CONNECTIONS += EV11.3-registers.json
#MODBUS_TOOLS_CONFIG += VERBOSE
include(../modbus.pri)
HEADERS += \
integrationpluginpcelectric.h \
pcelectricdiscovery.h \
pcewallbox.h
SOURCES += \
integrationpluginpcelectric.cpp \
pcelectricdiscovery.cpp \
pcewallbox.cpp

View File

@ -0,0 +1,169 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 <https://www.gnu.org/licenses/>.
*
* 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 "pcelectricdiscovery.h"
#include "extern-plugininfo.h"
PcElectricDiscovery::PcElectricDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress, QObject *parent)
: QObject{parent},
m_networkDeviceDiscovery{networkDeviceDiscovery},
m_port{port},
m_modbusAddress{modbusAddress}
{
}
QList<PcElectricDiscovery::Result> PcElectricDiscovery::results() const
{
return m_results;
}
void PcElectricDiscovery::startDiscovery()
{
qCInfo(dcPcElectric()) << "Discovery: Start searching for PCE wallboxes in the network...";
m_startDateTime = QDateTime::currentDateTime();
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &PcElectricDiscovery::checkNetworkDevice);
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
// Finish with some delay so the last added network device information objects still can be checked.
QTimer::singleShot(3000, this, [this](){
qCDebug(dcPcElectric()) << "Discovery: Grace period timer triggered.";
finishDiscovery();
});
});
}
void PcElectricDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
{
EV11ModbusTcpConnection *connection = new EV11ModbusTcpConnection(networkDeviceInfo.address(), m_port, m_modbusAddress, this);
m_connections.append(connection);
connect(connection, &EV11ModbusTcpConnection::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, &EV11ModbusTcpConnection::initializationFinished, this, [=](bool success){
if (!success) {
qCDebug(dcPcElectric()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue...";;
cleanupConnection(connection);
return;
}
// Parse the mac address from the registers and compair with the network device info mac address.
// If they match, we most likly found a PCE wallbox
QByteArray macRawData;
QDataStream stream(&macRawData, QIODevice::WriteOnly);
for (int i = 0; i < connection->macAddress().count(); i++)
stream << connection->macAddress().at(i);
MacAddress registerMacAddress(macRawData);
qCDebug(dcPcElectric()) << "Fetched mac address" << macRawData.toHex() << registerMacAddress;
// According to PCE the HW revision must be 0
if (registerMacAddress == MacAddress(networkDeviceInfo.macAddress()) && connection->hardwareRevision() == 0) {
// Parse the serial number
QByteArray serialRawData;
QDataStream stream(&serialRawData, QIODevice::ReadWrite);
stream << static_cast<quint16>(0);
for (int i = 0; i < connection->serialNumber().count(); i++)
stream << connection->serialNumber().at(i);
quint64 serialNumber = serialRawData.toHex().toULongLong(nullptr, 16);
qCDebug(dcPcElectric()) << "Serial number" << serialRawData.toHex() << serialNumber;
Result result;
result.serialNumber = QString::number(serialNumber);
result.firmwareRevision = connection->firmwareRevision();
result.networkDeviceInfo = networkDeviceInfo;
m_results.append(result);
qCInfo(dcPcElectric()) << "Discovery: --> Found"
<< "Serial number:" << result.serialNumber
<< "Firmware revision:" << result.firmwareRevision
<< result.networkDeviceInfo;
}
// Done with this connection
cleanupConnection(connection);
});
// Initializing...
if (!connection->initialize()) {
qCDebug(dcPcElectric()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue...";;
cleanupConnection(connection);
}
});
// If we get any error...skip this host...
connect(connection->modbusTcpMaster(), &ModbusTcpMaster::connectionErrorOccurred, this, [=](QModbusDevice::Error error){
if (error != QModbusDevice::NoError) {
qCDebug(dcPcElectric()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";;
cleanupConnection(connection);
}
});
// If check reachability failed...skip this host...
connect(connection, &EV11ModbusTcpConnection::checkReachabilityFailed, this, [=](){
qCDebug(dcPcElectric()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue...";;
cleanupConnection(connection);
});
// Try to connect, maybe it works, maybe not...
connection->connectDevice();
}
void PcElectricDiscovery::cleanupConnection(EV11ModbusTcpConnection *connection)
{
m_connections.removeAll(connection);
connection->disconnectDevice();
connection->deleteLater();
}
void PcElectricDiscovery::finishDiscovery()
{
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
// Cleanup any leftovers...we don't care any more
foreach (EV11ModbusTcpConnection *connection, m_connections)
cleanupConnection(connection);
qCInfo(dcPcElectric()) << "Discovery: Finished the discovery process. Found" << m_results.count()
<< "PCE wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
emit discoveryFinished();
}

View File

@ -0,0 +1,76 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 <https://www.gnu.org/licenses/>.
*
* 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 PCELECTRICDISCOVERY_H
#define PCELECTRICDISCOVERY_H
#include <QObject>
#include <network/networkdevicediscovery.h>
#include "ev11modbustcpconnection.h"
class PcElectricDiscovery : public QObject
{
Q_OBJECT
public:
explicit PcElectricDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress, QObject *parent = nullptr);
typedef struct Result {
QString serialNumber;
QString firmwareRevision;
NetworkDeviceInfo networkDeviceInfo;
} Result;
QList<PcElectricDiscovery::Result> results() const;
public slots:
void startDiscovery();
signals:
void discoveryFinished();
private:
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
quint16 m_port;
quint16 m_modbusAddress;
QDateTime m_startDateTime;
QList<EV11ModbusTcpConnection *> m_connections;
QList<Result> m_results;
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
void cleanupConnection(EV11ModbusTcpConnection *connection);
void finishDiscovery();
};
#endif // PCELECTRICDISCOVERY_H

267
pcelectric/pcewallbox.cpp Normal file
View File

@ -0,0 +1,267 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 <https://www.gnu.org/licenses/>.
*
* 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 "pcewallbox.h"
#include "extern-plugininfo.h"
#include <modbusdatautils.h>
PceWallbox::PceWallbox(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent)
: EV11ModbusTcpConnection{hostAddress, port, slaveId, parent}
{
// Timer for resetting the heartbeat register (watchdog)
m_timer.setInterval(30000);
m_timer.setSingleShot(false);
connect(&m_timer, &QTimer::timeout, this, &PceWallbox::sendHeartbeat);
connect(this, &EV11ModbusTcpConnection::reachableChanged, this, [this](bool reachable){
if (!reachable) {
m_timer.stop();
qDeleteAll(m_queue);
m_queue.clear();
if (m_currentReply) {
m_currentReply = nullptr;
}
} else {
initialize();
}
});
connect(this, &EV11ModbusTcpConnection::initializationFinished, this, [this](bool success){
if (success) {
qCDebug(dcPcElectric()) << "Connection initialized successfully" << m_modbusTcpMaster->hostAddress().toString();
m_timer.start();
sendHeartbeat();
update();
} else {
qCWarning(dcPcElectric()) << "Connection initialization failed for" << m_modbusTcpMaster->hostAddress().toString();
}
});
}
bool PceWallbox::update()
{
if (m_aboutToDelete)
return false;
if (!reachable())
return false;
// Make sure we only have one update call in the queue
foreach (QueuedModbusReply *r, m_queue) {
if (r->dataUnit().startAddress() == readBlockInitInfosDataUnit().startAddress()) {
return true;
}
}
QueuedModbusReply *reply = new QueuedModbusReply(QueuedModbusReply::RequestTypeRead, readBlockStatusDataUnit(), this);
connect(reply, &QueuedModbusReply::finished, reply, &QueuedModbusReply::deleteLater);
connect(reply, &QueuedModbusReply::finished, this, [this, reply](){
if (m_currentReply == reply)
m_currentReply = nullptr;
if (reply->error() != QModbusDevice::NoError) {
emit updateFinished();
sendNextRequest();
return;
}
const QModbusDataUnit unit = reply->reply()->result();
const QVector<quint16> blockValues = unit.values();
processBlockStatusRegisterValues(blockValues);
emit updateFinished();
sendNextRequest();
});
enqueueRequest(reply);
return true;
}
QueuedModbusReply *PceWallbox::setChargingCurrent(quint16 chargingCurrent)
{
if (m_aboutToDelete)
return nullptr;
QueuedModbusReply *reply = new QueuedModbusReply(QueuedModbusReply::RequestTypeWrite, setChargingCurrentDataUnit(chargingCurrent), this);
connect(reply, &QueuedModbusReply::finished, reply, &QueuedModbusReply::deleteLater);
connect(reply, &QueuedModbusReply::finished, this, [this, reply](){
if (m_currentReply == reply)
m_currentReply = nullptr;
sendNextRequest();
return;
});
enqueueRequest(reply, true);
return reply;
}
QueuedModbusReply *PceWallbox::setLedBrightness(quint16 percentage)
{
if (m_aboutToDelete)
return nullptr;
QueuedModbusReply *reply = new QueuedModbusReply(QueuedModbusReply::RequestTypeWrite, setLedBrightnessDataUnit(percentage), this);
connect(reply, &QueuedModbusReply::finished, reply, &QueuedModbusReply::deleteLater);
connect(reply, &QueuedModbusReply::finished, this, [this, reply](){
if (m_currentReply == reply)
m_currentReply = nullptr;
sendNextRequest();
return;
});
enqueueRequest(reply, true);
return reply;
}
void PceWallbox::gracefullDeleteLater()
{
// Clean up the queue
m_aboutToDelete = true;
cleanupQueue();
m_timer.stop();
if (!m_currentReply) {
qCDebug(dcPcElectric()) << "Deleting object without pending request...";
// No pending request, we can close the connection and delete the object
disconnect(this, nullptr, nullptr, nullptr);
disconnectDevice();
deleteLater();
} else {
qCDebug(dcPcElectric()) << "Pending request, deleting object once the request is finished...";
}
}
void PceWallbox::sendHeartbeat()
{
if (m_aboutToDelete)
return;
QueuedModbusReply *reply = new QueuedModbusReply(QueuedModbusReply::RequestTypeWrite, setHeartbeatDataUnit(m_heartbeat++), this);
connect(reply, &QueuedModbusReply::finished, reply, &QueuedModbusReply::deleteLater);
connect(reply, &QueuedModbusReply::finished, this, [this, reply](){
if (m_currentReply == reply)
m_currentReply = nullptr;
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcPcElectric()) << "Failed to send heartbeat to" << m_modbusTcpMaster->hostAddress().toString() << reply->errorString();
} else {
qCDebug(dcPcElectric()) << "Successfully sent heartbeat to" << m_modbusTcpMaster->hostAddress().toString();
}
sendNextRequest();
return;
});
enqueueRequest(reply, true);
}
void PceWallbox::sendNextRequest()
{
if (m_queue.isEmpty())
return;
if (m_currentReply)
return;
if (m_aboutToDelete) {
disconnect(this, nullptr, nullptr, nullptr);
disconnectDevice();
deleteLater();
return;
}
m_currentReply = m_queue.dequeue();
switch(m_currentReply->requestType()) {
case QueuedModbusReply::RequestTypeRead:
qCDebug(dcPcElectric()) << "--> Reading" << ModbusDataUtils::registerTypeToString(m_currentReply->dataUnit().registerType())
<< "register:" << m_currentReply->dataUnit().startAddress()
<< "length" << m_currentReply->dataUnit().valueCount();
m_currentReply->setReply(m_modbusTcpMaster->sendReadRequest(m_currentReply->dataUnit(), m_slaveId));
break;
case QueuedModbusReply::RequestTypeWrite:
qCDebug(dcPcElectric()) << "--> Writing" << ModbusDataUtils::registerTypeToString(m_currentReply->dataUnit().registerType())
<< "register:" << m_currentReply->dataUnit().startAddress()
<< "length:" << m_currentReply->dataUnit().valueCount()
<< "values:" << m_currentReply->dataUnit().values();
m_currentReply->setReply(m_modbusTcpMaster->sendWriteRequest(m_currentReply->dataUnit(), m_slaveId));
break;
}
if (!m_currentReply->reply()) {
qCWarning(dcPcElectric()) << "Error occurred while sending" << m_currentReply->requestType()
<< ModbusDataUtils::registerTypeToString(m_currentReply->dataUnit().registerType())
<< "register:" << m_currentReply->dataUnit().startAddress()
<< "length:" << m_currentReply->dataUnit().valueCount()
<< "to" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
m_currentReply->deleteLater();
m_currentReply = nullptr;
sendNextRequest();
return;
}
if (m_currentReply->reply()->isFinished()) {
qCWarning(dcPcElectric()) << "Reply immediatly finished";
m_currentReply->deleteLater();
m_currentReply = nullptr;
sendNextRequest();
return;
}
}
void PceWallbox::enqueueRequest(QueuedModbusReply *reply, bool prepend)
{
if (prepend) {
m_queue.prepend(reply);
} else {
m_queue.enqueue(reply);
}
sendNextRequest();
}
void PceWallbox::cleanupQueue()
{
qDeleteAll(m_queue);
m_queue.clear();
}

77
pcelectric/pcewallbox.h Normal file
View File

@ -0,0 +1,77 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 <https://www.gnu.org/licenses/>.
*
* 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 PCEWALLBOX_H
#define PCEWALLBOX_H
#include <QTimer>
#include <QQueue>
#include <QObject>
#include <queuedmodbusreply.h>
#include "ev11modbustcpconnection.h"
class PceWallbox : public EV11ModbusTcpConnection
{
Q_OBJECT
public:
explicit PceWallbox(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr);
bool update() override;
QueuedModbusReply *setChargingCurrent(quint16 chargingCurrent); // mA
QueuedModbusReply *setLedBrightness(quint16 percentage);
// Note: the modbus implementation of the wallbox gets stuck if a Modbus request has been sent
// and we disconnect the socket before the response has arrived. Only a reboot of the wallbox
// fixes the broken communication afterwards. This method waits for the current request before closing the
// socket and deletes it self.
// IMPORTNAT: do not use the object after this call, this is a temporary workaround
void gracefullDeleteLater();
private slots:
void sendHeartbeat();
private:
QTimer m_timer;
quint16 m_heartbeat = 1;
QueuedModbusReply *m_currentReply = nullptr;
QQueue<QueuedModbusReply *> m_queue;
bool m_aboutToDelete = false;
void sendNextRequest();
void enqueueRequest(QueuedModbusReply *reply, bool prepend = false);
void cleanupQueue();
};
#endif // PCEWALLBOX_H

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de" sourcelanguage="en">
<context>
<name>IntegrationPluginPcElectric</name>
<message>
<location filename="../integrationpluginpcelectric.cpp" line="53"/>
<source>The network device discovery is not available.</source>
<translation>Die Netzwerk Suche ist nicht verfügbar.</translation>
</message>
</context>
<context>
<name>PcElectric</name>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="43"/>
<source>Active phases</source>
<extracomment>The name of the StateType ({bca88c23-e940-40c1-afca-eb511fd17aab}) of ThingClass ev11</extracomment>
<translation>Aktive Phasen</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="46"/>
<source>Charging</source>
<extracomment>The name of the StateType ({b7972cd7-471a-46bd-ab99-f49997f12309}) of ThingClass ev11</extracomment>
<translation>Lädt</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="49"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="52"/>
<source>Charging enabled</source>
<extracomment>The name of the ParamType (ThingClass: ev11, ActionType: power, ID: {c12a7a27-fa56-450c-a1ec-717c868554f2})
----------
The name of the StateType ({c12a7a27-fa56-450c-a1ec-717c868554f2}) of ThingClass ev11</extracomment>
<translation>Laden eingeschalten</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="55"/>
<source>Connected</source>
<extracomment>The name of the StateType ({ca8d680c-c2f8-456a-a246-9c6cd64e25a7}) of ThingClass ev11</extracomment>
<translation>Verbunden</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="58"/>
<source>Enable or disable charging</source>
<extracomment>The name of the ActionType ({c12a7a27-fa56-450c-a1ec-717c868554f2}) of ThingClass ev11</extracomment>
<translation>Laden starten/stoppen</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="61"/>
<source>Firmware version</source>
<extracomment>The name of the StateType ({142b4276-e2e9-4149-adc4-89d9d3e31117}) of ThingClass ev11</extracomment>
<translation>Firmware Version</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="64"/>
<source>Hardware version</source>
<extracomment>The name of the StateType ({b6e65baf-6dcd-4db1-a3dc-962a4c33d157}) of ThingClass ev11</extracomment>
<translation>Hardware Version</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="67"/>
<source>LED brightness</source>
<extracomment>The name of the ParamType (ThingClass: ev11, Type: settings, ID: {3a1329a2-84cc-47b9-a6c2-e96fdfd0c454})</extracomment>
<translation>LED Helligkeit</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="70"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: ev11, Type: thing, ID: {0a3f8d12-9d33-4ae2-b763-9568f32e8da1})</extracomment>
<translation>MAC Adresse</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="73"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="76"/>
<source>Maximum charging current</source>
<extracomment>The name of the ParamType (ThingClass: ev11, ActionType: maxChargingCurrent, ID: {b5bbf23c-06db-463b-bb5c-3aea38e18818})
----------
The name of the StateType ({b5bbf23c-06db-463b-bb5c-3aea38e18818}) of ThingClass ev11</extracomment>
<translation>Maximaler Ladestrom</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="79"/>
<source>Maximum offline charging current</source>
<extracomment>The name of the ParamType (ThingClass: ev11, Type: settings, ID: {93654273-c4d3-4389-a81e-c0f065d9cd92})</extracomment>
<translation>Maximaler Ladestrom offline</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="82"/>
<source>PC Electric</source>
<extracomment>The name of the plugin PcElectric ({aa7ff833-a8e0-45cc-a1ef-65f05871f272})</extracomment>
<translation>PC Electric</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="85"/>
<source>PC Electric GmbH</source>
<extracomment>The name of the vendor ({b365937b-f1d6-46bf-9ff1-e787373b8aa6})</extracomment>
<translation>PC Electric GmbH</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="88"/>
<source>PCE EV11.X</source>
<extracomment>The name of the ThingClass ({88d96940-a940-4a07-8176-5e6aba7ca832})</extracomment>
<translation>PCE EV11.X</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="91"/>
<source>Plugged in</source>
<extracomment>The name of the StateType ({50164bbd-9802-4cf6-82de-626b74293a1b}) of ThingClass ev11</extracomment>
<translation>Angesteckt</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="94"/>
<source>Set maximum charging current</source>
<extracomment>The name of the ActionType ({b5bbf23c-06db-463b-bb5c-3aea38e18818}) of ThingClass ev11</extracomment>
<translation>Setze maximalen Ladestrom</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginPcElectric</name>
<message>
<location filename="../integrationpluginpcelectric.cpp" line="53"/>
<source>The network device discovery is not available.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PcElectric</name>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="43"/>
<source>Active phases</source>
<extracomment>The name of the StateType ({bca88c23-e940-40c1-afca-eb511fd17aab}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="46"/>
<source>Charging</source>
<extracomment>The name of the StateType ({b7972cd7-471a-46bd-ab99-f49997f12309}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="49"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="52"/>
<source>Charging enabled</source>
<extracomment>The name of the ParamType (ThingClass: ev11, ActionType: power, ID: {c12a7a27-fa56-450c-a1ec-717c868554f2})
----------
The name of the StateType ({c12a7a27-fa56-450c-a1ec-717c868554f2}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="55"/>
<source>Connected</source>
<extracomment>The name of the StateType ({ca8d680c-c2f8-456a-a246-9c6cd64e25a7}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="58"/>
<source>Enable or disable charging</source>
<extracomment>The name of the ActionType ({c12a7a27-fa56-450c-a1ec-717c868554f2}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="61"/>
<source>Firmware version</source>
<extracomment>The name of the StateType ({142b4276-e2e9-4149-adc4-89d9d3e31117}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="64"/>
<source>Hardware version</source>
<extracomment>The name of the StateType ({b6e65baf-6dcd-4db1-a3dc-962a4c33d157}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="67"/>
<source>LED brightness</source>
<extracomment>The name of the ParamType (ThingClass: ev11, Type: settings, ID: {3a1329a2-84cc-47b9-a6c2-e96fdfd0c454})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="70"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: ev11, Type: thing, ID: {0a3f8d12-9d33-4ae2-b763-9568f32e8da1})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="73"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="76"/>
<source>Maximum charging current</source>
<extracomment>The name of the ParamType (ThingClass: ev11, ActionType: maxChargingCurrent, ID: {b5bbf23c-06db-463b-bb5c-3aea38e18818})
----------
The name of the StateType ({b5bbf23c-06db-463b-bb5c-3aea38e18818}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="79"/>
<source>Maximum offline charging current</source>
<extracomment>The name of the ParamType (ThingClass: ev11, Type: settings, ID: {93654273-c4d3-4389-a81e-c0f065d9cd92})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="82"/>
<source>PC Electric</source>
<extracomment>The name of the plugin PcElectric ({aa7ff833-a8e0-45cc-a1ef-65f05871f272})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="85"/>
<source>PC Electric GmbH</source>
<extracomment>The name of the vendor ({b365937b-f1d6-46bf-9ff1-e787373b8aa6})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="88"/>
<source>PCE EV11.X</source>
<extracomment>The name of the ThingClass ({88d96940-a940-4a07-8176-5e6aba7ca832})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="91"/>
<source>Plugged in</source>
<extracomment>The name of the StateType ({50164bbd-9802-4cf6-82de-626b74293a1b}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/pcelectric/plugininfo.h" line="94"/>
<source>Set maximum charging current</source>
<extracomment>The name of the ActionType ({b5bbf23c-06db-463b-bb5c-3aea38e18818}) of ThingClass ev11</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>