diff --git a/phoenixconnect/README.md b/phoenixconnect/README.md index 402cec1..9772ebf 100644 --- a/phoenixconnect/README.md +++ b/phoenixconnect/README.md @@ -22,8 +22,7 @@ manual. Once the wallbox is connected to the network, it can be added to nymea using the regular thing setup wizard. -Depending on the usage, the DIP switches may be configured to all off, which puts the -wallbox in the plug-and-charge mode, allowing the easiest integration within nymea. If -there are security concerns, DIP switch 7 can be turned on to enable the lock mode. In -this mode, the key lock needs to be turned for the wallbox to actually start charging. -If the model is capable of RFID, DIP switch 10 can be used to enable/diable that feature. +Depending on the usage, DIP switches may be configured: DIP switch 10 is required to +be set for nymea to be able to operate the wallbox. DIP switch 7 may be enabled in +addition to enable the key lock. In this mode, the key lock needs to be unlocked before +the wallbox will charge. diff --git a/phoenixconnect/integrationpluginphoenixconnect.cpp b/phoenixconnect/integrationpluginphoenixconnect.cpp index c0e28f7..63cae37 100644 --- a/phoenixconnect/integrationpluginphoenixconnect.cpp +++ b/phoenixconnect/integrationpluginphoenixconnect.cpp @@ -32,6 +32,7 @@ #include "plugininfo.h" #include "phoenixmodbustcpconnection.h" +#include "phoenixdiscovery.h" #include #include @@ -51,42 +52,36 @@ IntegrationPluginPhoenixConnect::IntegrationPluginPhoenixConnect() void IntegrationPluginPhoenixConnect::discoverThings(ThingDiscoveryInfo *info) { if (!hardwareManager()->networkDeviceDiscovery()->available()) { - qCWarning(dcPhoenixContact()) << "Failed to discover network devices. The network device discovery is not available."; + qCWarning(dcPhoenixConnect()) << "Failed to discover network devices. The network device discovery is not available."; info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The network cannot be searched.")); return; } - qCDebug(dcPhoenixContact()) << "Starting network discovery..."; - NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, info, [=](){ - ThingDescriptors descriptors; - qCDebug(dcPhoenixContact()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices"; - foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { - qCDebug(dcPhoenixContact()) << networkDeviceInfo; + PhoenixDiscovery *discovery = new PhoenixDiscovery(hardwareManager()->networkDeviceDiscovery(), info); + connect(discovery, &PhoenixDiscovery::discoveryFinished, info, [=](){ + foreach (const PhoenixDiscovery::Result &result, discovery->discoveryResults()) { - if (networkDeviceInfo.macAddressManufacturer() != "wallbe GmbH" && !networkDeviceInfo.macAddressManufacturer().contains("Phoenix", Qt::CaseSensitivity::CaseInsensitive)) { - continue; - } - - ThingClass thingClass = supportedThings().findById(info->thingClassId()); - ParamTypeId macAddressParamType = thingClass.paramTypes().findByName("mac").id(); - - ThingDescriptor descriptor(info->thingClassId(), thingClass.displayName(), networkDeviceInfo.address().toString()); - descriptor.setParams({Param(macAddressParamType, networkDeviceInfo.macAddress())}); - - // Check if we already have set up this device - Thing *existingThing = myThings().findByParams(descriptor.params()); - if (existingThing) { - qCDebug(dcPhoenixContact()) << "Found already existing" << thingClass.name() << "wallbox:" << existingThing->name() << networkDeviceInfo; - descriptor.setThingId(existingThing->id()); - } else { - qCDebug(dcPhoenixContact()) << "Found new" << thingClass.name() << "wallbox"; + QString name = supportedThings().findById(info->thingClassId()).displayName(); + QString description = result.serialNumber; + ThingDescriptor descriptor(info->thingClassId(), name, description); + qCDebug(dcPhoenixConnect()) << "Discovered:" << descriptor.title() << descriptor.description(); + + ParamTypeId macParamTypeId = supportedThings().findById(info->thingClassId()).paramTypes().findByName("mac").id(); + Things existingThings = myThings().filterByParam(macParamTypeId, result.networkDeviceInfo.macAddress()); + if (existingThings.count() == 1) { + qCDebug(dcPhoenixConnect()) << "This wallbox already exists in the system:" << result.networkDeviceInfo; + descriptor.setThingId(existingThings.first()->id()); } + ParamList params; + params << Param(macParamTypeId, result.networkDeviceInfo.macAddress()); + descriptor.setParams(params); info->addThingDescriptor(descriptor); } + info->finish(Thing::ThingErrorNoError); }); + discovery->startDiscovery(); } @@ -95,10 +90,10 @@ void IntegrationPluginPhoenixConnect::setupThing(ThingSetupInfo *info) Thing *thing = info->thing(); if (m_connections.contains(thing)) { - qCDebug(dcPhoenixContact()) << "Reconfiguring existing thing" << thing->name(); + qCDebug(dcPhoenixConnect()) << "Reconfiguring existing thing" << thing->name(); m_connections.take(thing)->deleteLater(); } else { - qCDebug(dcPhoenixContact()) << "Setting up a new device:" << thing->params(); + qCDebug(dcPhoenixConnect()) << "Setting up a new device:" << thing->params(); } @@ -119,8 +114,12 @@ void IntegrationPluginPhoenixConnect::setupThing(ThingSetupInfo *info) } }); + connect(monitor, &NetworkDeviceMonitor::networkDeviceInfoChanged, this, [=](const NetworkDeviceInfo &networkDeviceInfo){ + connection->setHostAddress(networkDeviceInfo.address()); + }); + connect(connection, &PhoenixModbusTcpConnection::reachableChanged, thing, [connection, thing](bool reachable){ - qCDebug(dcPhoenixContact()) << "Reachable state changed" << reachable; + qCDebug(dcPhoenixConnect()) << "Reachable state changed" << reachable; if (reachable) { connection->initialize(); } else { @@ -130,20 +129,24 @@ void IntegrationPluginPhoenixConnect::setupThing(ThingSetupInfo *info) // Only during setup connect(connection, &PhoenixModbusTcpConnection::initializationFinished, info, [this, thing, connection, monitor, info](bool success){ - if (success) { - qCDebug(dcPhoenixContact()) << "Phoenix wallbox initialized. Firmware version:" << connection->firmwareVersion(); - m_connections.insert(thing, connection); - m_monitors.insert(thing, monitor); - info->finish(Thing::ThingErrorNoError); - } else { + if (!success) { + qCDebug(dcPhoenixConnect()) << "Failed to init modbus connection to" << thing->name(); hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor); connection->deleteLater(); info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Could not initialize the communication with the wallbox.")); + return; } + + m_connections.insert(thing, connection); + m_monitors.insert(thing, monitor); + info->finish(Thing::ThingErrorNoError); }); - connect(connection, &PhoenixModbusTcpConnection::updateFinished, thing, [connection, thing](){ - qCDebug(dcPhoenixContact()) << "Update finished:" << thing->name() << connection; + connect(connection, &PhoenixModbusTcpConnection::updateFinished, thing, [this, connection, thing](){ + qCDebug(dcPhoenixConnect()) << "Update finished:" << thing->name() << connection; + if (thing->thingClassId() == scapoVisionThingClassId || thing->thingClassId() == wallbeProThingClassId || thing->thingClassId() == compleoProThingClassId) { + updatePhaseCount(thing); + } }); connect(connection, &PhoenixModbusTcpConnection::initializationFinished, thing, [thing, connection](bool success){ @@ -154,66 +157,72 @@ void IntegrationPluginPhoenixConnect::setupThing(ThingSetupInfo *info) }); // Handle property changed signals - connect(connection, &PhoenixModbusTcpConnection::cpStatusChanged, thing, [thing, connection](quint16 cpStatus){ - qCDebug(dcPhoenixContact()) << "CP Signal state changed:" << (char)cpStatus; + connect(connection, &PhoenixModbusTcpConnection::cpStatusChanged, thing, [thing, this](quint16 cpStatus){ + qCDebug(dcPhoenixConnect()) << "CP Signal state changed:" << (char)cpStatus; thing->setStateValue("pluggedIn", cpStatus >= 66); - thing->setStateValue("charging", cpStatus >= 67 && connection->chargingEnabled() > 0); + evaluateChargingState(thing); }); - connect(connection, &PhoenixModbusTcpConnection::chargingEnabledChanged, this, [thing, connection](quint16 chargingEnabled){ - qCDebug(dcPhoenixContact()) << "Charging enabled changed:" << chargingEnabled; - thing->setStateValue("power", chargingEnabled > 0); - thing->setStateValue("charging", chargingEnabled > 0 && connection->cpStatus() >= 67); + connect(connection, &PhoenixModbusTcpConnection::chargingEnabledChanged, this, [thing, this](quint16 chargingEnabled){ + qCDebug(dcPhoenixConnect()) << "Charging enabled changed:" << chargingEnabled; + evaluateChargingState(thing); + }); + + connect(connection, &PhoenixModbusTcpConnection::chargingPausedChanged, this, [thing, this](quint16 chargingPaused){ + qCDebug(dcPhoenixConnect()) << "Charging paused changed:" << chargingPaused; + thing->setStateValue("power", chargingPaused == 0); + evaluateChargingState(thing); + }); + + connect(connection, &PhoenixModbusTcpConnection::chargingAllowedChanged, this, [thing, this](quint16 chargingEnabled){ + qCDebug(dcPhoenixConnect()) << "Charging enabled changed:" << chargingEnabled; + evaluateChargingState(thing); }); connect(connection, &PhoenixModbusTcpConnection::chargingCurrentChanged, thing, [/*thing*/](quint16 chargingCurrent) { - qCDebug(dcPhoenixContact()) << "Charging current changed" << chargingCurrent / 10; + qCDebug(dcPhoenixConnect()) << "Charging current changed" << chargingCurrent / 10; }); connect(connection, &PhoenixModbusTcpConnection::maximumChargingCurrentChanged, thing, [thing](quint16 maxChargingCurrent) { - qCDebug(dcPhoenixContact()) << "Max charging current changed" << maxChargingCurrent; + qCDebug(dcPhoenixConnect()) << "Max charging current changed" << maxChargingCurrent; thing->setStateValue("maxChargingCurrent", 1.0 * maxChargingCurrent / 10); // 100mA -> 1A }); connect(connection, &PhoenixModbusTcpConnection::activePowerChanged, thing, [thing](quint32 activePower) { - qCDebug(dcPhoenixContact()) << "Active power consumption changed" << activePower; + qCDebug(dcPhoenixConnect()) << "Active power consumption changed" << activePower; if (thing->hasState("currentPower")) { thing->setStateValue("currentPower", activePower); } }); connect(connection, &PhoenixModbusTcpConnection::totalEnergyChanged, thing, [thing](quint32 totalEnergy) { - qCDebug(dcPhoenixContact()) << "Total energy consumption changed" << totalEnergy; + qCDebug(dcPhoenixConnect()) << "Total energy consumption changed" << totalEnergy; if (thing->hasState("totalEnergyConsumed")) { thing->setStateValue("totalEnergyConsumed", 1.0 * totalEnergy / 1000); } }); connect(connection, &PhoenixModbusTcpConnection::errorCodeChanged, thing, [](PhoenixModbusTcpConnection::ErrorCode errorCode){ - qCDebug(dcPhoenixContact()) << "Error code changed:" << errorCode; + qCDebug(dcPhoenixConnect()) << "Error code changed:" << errorCode; }); - connect(connection, &PhoenixModbusTcpConnection::voltageI1Changed, thing, [this, thing](){ updatePhaseCount(thing); }); - connect(connection, &PhoenixModbusTcpConnection::voltageI2Changed, thing, [this, thing](){ updatePhaseCount(thing); }); - connect(connection, &PhoenixModbusTcpConnection::voltageI3Changed, thing, [this, thing](){ updatePhaseCount(thing); }); - connection->connectDevice(); } void IntegrationPluginPhoenixConnect::postSetupThing(Thing *thing) { - qCDebug(dcPhoenixContact()) << "Post setup thing" << thing->name(); + qCDebug(dcPhoenixConnect()) << "Post setup thing" << thing->name(); if (!m_pluginTimer) { - qCDebug(dcPhoenixContact()) << "Starting plugin timer"; + qCDebug(dcPhoenixConnect()) << "Starting plugin timer"; m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { foreach (Thing *thing, myThings()) { - if (m_monitors.value(thing)->reachable()) { - qCDebug(dcPhoenixContact()) << "Updating" << thing->name(); + if (thing->setupStatus() == Thing::ThingSetupStatusComplete && m_monitors.value(thing)->reachable()) { + qCDebug(dcPhoenixConnect()) << "Updating" << thing->name() << m_monitors.value(thing)->macAddress() << m_monitors.value(thing)->networkDeviceInfo().address().toString(); m_connections.value(thing)->update(); } else { - qCDebug(dcPhoenixContact()) << thing->name() << "isn't reachable. Not updating."; + qCDebug(dcPhoenixConnect()) << thing->name() << "isn't reachable. Not updating."; } } }); @@ -227,7 +236,7 @@ void IntegrationPluginPhoenixConnect::executeAction(ThingActionInfo *info) PhoenixModbusTcpConnection *connection = m_connections.value(thing); if (!connection) { - qCWarning(dcPhoenixContact()) << "Modbus connection not available"; + qCWarning(dcPhoenixConnect()) << "Modbus connection not available"; info->finish(Thing::ThingErrorHardwareFailure); return; } @@ -235,13 +244,14 @@ void IntegrationPluginPhoenixConnect::executeAction(ThingActionInfo *info) ActionType actionType = thing->thingClass().actionTypes().findById(info->action().actionTypeId()); if (actionType.name() == "power") { bool enabled = info->action().paramValue(actionType.id()).toBool(); - QModbusReply *reply = connection->setChargingEnabled(enabled); + + QModbusReply *reply = connection->setChargingPaused(!enabled); connect(reply, &QModbusReply::finished, info, [info, thing, reply, enabled](){ if (reply->error() != QModbusDevice::NoError) { - qCWarning(dcPhoenixContact()) << "Error setting charging enabled" << reply->error() << reply->errorString(); + qCWarning(dcPhoenixConnect()) << "Error" << (enabled ? "starting" : "stopping") << "charging:" << reply->error() << reply->errorString(); info->finish(Thing::ThingErrorHardwareFailure); } else { - qCDebug(dcPhoenixContact()) << "Charging enabled set with success"; + qCDebug(dcPhoenixConnect()) << "Charging" << (enabled ? "started" : "stopped") << "with success"; thing->setStateValue("power", enabled); info->finish(Thing::ThingErrorNoError); } @@ -249,14 +259,14 @@ void IntegrationPluginPhoenixConnect::executeAction(ThingActionInfo *info) } else if (actionType.name() == "maxChargingCurrent") { uint16_t current = action.param(actionType.id()).value().toUInt(); - qCDebug(dcPhoenixContact()) << "Charging power set to" << current; + qCDebug(dcPhoenixConnect()) << "Charging power set to" << current; QModbusReply *reply = connection->setMaximumChargingCurrent(current * 10); connect(reply, &QModbusReply::finished, info, [info, thing, reply, current](){ if (reply->error() != QModbusDevice::NoError) { - qCWarning(dcPhoenixContact()) << "Error setting charging current" << reply->error() << reply->errorString(); + qCWarning(dcPhoenixConnect()) << "Error setting charging current" << reply->error() << reply->errorString(); info->finish(Thing::ThingErrorHardwareFailure); } else { - qCDebug(dcPhoenixContact()) << "Max charging current set to" << current; + qCDebug(dcPhoenixConnect()) << "Max charging current set to" << current; thing->setStateValue("maxChargingCurrent", current); info->finish(Thing::ThingErrorNoError); } @@ -269,7 +279,7 @@ void IntegrationPluginPhoenixConnect::executeAction(ThingActionInfo *info) void IntegrationPluginPhoenixConnect::thingRemoved(Thing *thing) { - qCDebug(dcPhoenixContact()) << "Removing device" << thing->name(); + qCDebug(dcPhoenixConnect()) << "Removing device" << thing->name(); if (m_connections.contains(thing)) { m_connections.take(thing)->deleteLater(); hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); @@ -294,5 +304,12 @@ void IntegrationPluginPhoenixConnect::updatePhaseCount(Thing *thing) if (connection->voltageI3() > 100) { phaseCount++; } - thing->setStateValue("phaseCount", phaseCount); + thing->setStateValue("phaseCount", qMax(1, phaseCount)); +} + +void IntegrationPluginPhoenixConnect::evaluateChargingState(Thing *thing) +{ + PhoenixModbusTcpConnection *connection = m_connections.value(thing); + bool charging = connection->cpStatus() >= 67 && connection->chargingPaused() == 0 && connection->chargingAllowed() == 1; + thing->setStateValue("charging", charging); } diff --git a/phoenixconnect/integrationpluginphoenixconnect.h b/phoenixconnect/integrationpluginphoenixconnect.h index 61d7b68..e44bf31 100644 --- a/phoenixconnect/integrationpluginphoenixconnect.h +++ b/phoenixconnect/integrationpluginphoenixconnect.h @@ -59,6 +59,9 @@ public: private slots: void updatePhaseCount(Thing *thing); +private: + void evaluateChargingState(Thing *thing); + private: QHash m_connections; QHash m_monitors; diff --git a/phoenixconnect/integrationpluginphoenixconnect.json b/phoenixconnect/integrationpluginphoenixconnect.json index 6560795..770a923 100644 --- a/phoenixconnect/integrationpluginphoenixconnect.json +++ b/phoenixconnect/integrationpluginphoenixconnect.json @@ -1,6 +1,6 @@ { - "displayName": "phoenixContact", - "name": "PhoenixContact", + "displayName": "phoenixConnect", + "name": "PhoenixConnect", "id": "0de5bbd2-0dad-4727-9a17-3ee149106048", "vendors": [ { diff --git a/phoenixconnect/phoenixconnect-registers.json b/phoenixconnect/phoenixconnect-registers.json index 5a5a49f..68ff216 100644 --- a/phoenixconnect/phoenixconnect-registers.json +++ b/phoenixconnect/phoenixconnect-registers.json @@ -164,6 +164,30 @@ "access": "R" } ] + }, + { + "id": "ids", + "readSchedule": "init", + "registers": [ + { + "id": "serial", + "address": 304, + "size": 6, + "type": "string", + "registerType": "holdingRegister", + "description": "Serial number", + "access": "R" + }, + { + "id": "deviceName", + "address": 310, + "size": 5, + "type": "string", + "registerType": "holdingRegister", + "description": "Device name", + "access": "R" + } + ] } ], "registers": [ @@ -189,6 +213,17 @@ "defaultValue": "0", "access": "R" }, + { + "id": "dipSwitches", + "address": 104, + "size": 1, + "type": "uint16", + "note": "The controller may report wrong DIP switches during init, so polling on update", + "readSchedule": "update", + "registerType": "inputRegister", + "description": "DIP switch configuration", + "access": "R" + }, { "id": "firmwareVersion", "address": 105, @@ -234,6 +269,39 @@ "defaultValue": "0", "access": "RW" }, + { + "id": "rfidEnabled", + "address": 419, + "size": 1, + "type": "uint16", + "readSchedule": "init", + "registerType": "coils", + "description": "RFID enabled", + "defaultValue": "0", + "access": "RW" + }, + { + "id": "chargingAllowed", + "address": 436, + "size": 1, + "type": "uint16", + "readSchedule": "update", + "registerType": "coils", + "description": "Charging allowed", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "chargingPaused", + "address": 468, + "size": 1, + "type": "uint16", + "readSchedule": "update", + "registerType": "coils", + "description": "Charging paused", + "defaultValue": "1", + "access": "RW" + }, { "id": "maximumChargingCurrent", "address": 528, diff --git a/phoenixconnect/phoenixconnect.pro b/phoenixconnect/phoenixconnect.pro index fa9f39c..116cba2 100644 --- a/phoenixconnect/phoenixconnect.pro +++ b/phoenixconnect/phoenixconnect.pro @@ -4,7 +4,9 @@ MODBUS_CONNECTIONS=phoenixconnect-registers.json include(../modbus.pri) SOURCES += \ - integrationpluginphoenixconnect.cpp + integrationpluginphoenixconnect.cpp \ + phoenixdiscovery.cpp HEADERS += \ - integrationpluginphoenixconnect.h + integrationpluginphoenixconnect.h \ + phoenixdiscovery.h diff --git a/phoenixconnect/phoenixdiscovery.cpp b/phoenixconnect/phoenixdiscovery.cpp new file mode 100644 index 0000000..c549397 --- /dev/null +++ b/phoenixconnect/phoenixdiscovery.cpp @@ -0,0 +1,136 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "phoenixdiscovery.h" +#include "extern-plugininfo.h" + +PhoenixDiscovery::PhoenixDiscovery(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(dcPhoenixConnect()) << "Discovery: Grace period timer triggered."; + finishDiscovery(); + }); +} + +void PhoenixDiscovery::startDiscovery() +{ + qCInfo(dcPhoenixConnect()) << "Discovery: Searching for PhoenixConnect wallboxes in the network..."; + NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover(); + + connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &PhoenixDiscovery::checkNetworkDevice); + + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + qCDebug(dcPhoenixConnect()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices"; + m_gracePeriodTimer.start(); + discoveryReply->deleteLater(); + }); +} + +QList PhoenixDiscovery::discoveryResults() const +{ + return m_discoveryResults; +} + +void PhoenixDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) +{ + if (networkDeviceInfo.macAddressManufacturer() != "wallbe GmbH" && networkDeviceInfo.macAddressManufacturer() != "Phoenix") { + return; + } + + int port = 502; + int slaveId = 0xff; + qCDebug(dcPhoenixConnect()) << "Checking network device:" << networkDeviceInfo << "Port:" << port << "Slave ID:" << slaveId; + + PhoenixModbusTcpConnection *connection = new PhoenixModbusTcpConnection(networkDeviceInfo.address(), port, slaveId, this); + m_connections.append(connection); + + connect(connection, &PhoenixModbusTcpConnection::reachableChanged, this, [=](bool reachable){ + if (!reachable) { + cleanupConnection(connection); + return; + } + + connect(connection, &PhoenixModbusTcpConnection::initializationFinished, this, [=](bool success){ + if (!success) { + qCDebug(dcPhoenixConnect()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString(); + cleanupConnection(connection); + return; + } + Result result; + result.firmwareVersion = connection->firmwareVersion(); + result.model = connection->deviceName(); + result.serialNumber = connection->serial(); + result.networkDeviceInfo = networkDeviceInfo; + m_discoveryResults.append(result); + + qCDebug(dcPhoenixConnect()) << "Discovery: Found wallbox with firmware version:" << result.firmwareVersion << result.networkDeviceInfo; + + cleanupConnection(connection); + }); + + if (!connection->initialize()) { + qCDebug(dcPhoenixConnect()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString(); + cleanupConnection(connection); + } + }); + + connect(connection, &PhoenixModbusTcpConnection::checkReachabilityFailed, this, [=](){ + qCDebug(dcPhoenixConnect()) << "Discovery: Checking reachability failed on" << networkDeviceInfo.address().toString(); + cleanupConnection(connection); + }); + + connection->connectDevice(); +} + +void PhoenixDiscovery::cleanupConnection(PhoenixModbusTcpConnection *connection) +{ + m_connections.removeAll(connection); + connection->disconnectDevice(); + connection->deleteLater(); +} + +void PhoenixDiscovery::finishDiscovery() +{ + qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch(); + + // Cleanup any leftovers...we don't care any more + foreach (PhoenixModbusTcpConnection *connection, m_connections) + cleanupConnection(connection); + + qCInfo(dcPhoenixConnect()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() + << "Phoenix connect wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); + m_gracePeriodTimer.stop(); + + emit discoveryFinished(); +} diff --git a/phoenixconnect/phoenixdiscovery.h b/phoenixconnect/phoenixdiscovery.h new file mode 100644 index 0000000..e5665f0 --- /dev/null +++ b/phoenixconnect/phoenixdiscovery.h @@ -0,0 +1,76 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef AMTRONECUDISCOVERY_H +#define AMTRONECUDISCOVERY_H + +#include +#include + +#include + +#include "phoenixmodbustcpconnection.h" + +class PhoenixDiscovery : public QObject +{ + Q_OBJECT +public: + explicit PhoenixDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr); + struct Result { + QString firmwareVersion; + QString model; + QString serialNumber; + 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(PhoenixModbusTcpConnection *connection); + + void finishDiscovery(); +}; + +#endif // AMTRONECUDISCOVERY_H diff --git a/phoenixconnect/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-de.ts b/phoenixconnect/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-de.ts new file mode 100644 index 0000000..52fd6e4 --- /dev/null +++ b/phoenixconnect/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-de.ts @@ -0,0 +1,27 @@ + + + + + IntegrationPluginPhoenixConnect + + + The network cannot be searched. + Das Netzwerk kann nicht durchsucht werden. + + + + The given MAC address is not valid. + Die eingegebene MAC Adresse ist ungültig. + + + + Could not initialize the communication with the wallbox. + Fehler beim Kommunizieren mit der Wallbox. + + + + DIP switch 10 is not enabled in the wallbox. Please enable it, restart the wallbox and try again. + DIP Schalter 10 der Wallbox ist nicht aktiv. Bitte aktiviere ihn, starte die Wallbox neu und versuche es erneut. + + + diff --git a/phoenixconnect/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts b/phoenixconnect/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts index 4f1d459..c20e6ec 100644 --- a/phoenixconnect/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts +++ b/phoenixconnect/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts @@ -4,373 +4,23 @@ IntegrationPluginPhoenixConnect - + The network cannot be searched. - + The given MAC address is not valid. - - - PhoenixContact - - - - - - - Charging - The name of the StateType ({7be9853f-ed14-461a-ac84-e425da2bce17}) of ThingClass scapoVision ----------- -The name of the StateType ({f3dbb5e9-a762-4ec4-bc4e-f2f1129924ee}) of ThingClass scapoEco ----------- -The name of the StateType ({72332b31-d162-4201-b357-12858ffc256a}) of ThingClass compleoPro ----------- -The name of the StateType ({1665d3dd-1d9a-4c9d-af48-910134304827}) of ThingClass compleoEcoS ----------- -The name of the StateType ({e0854ef1-609b-45c0-a71c-8035a335fd0c}) of ThingClass wallbePro ----------- -The name of the StateType ({e0f58841-2e34-4671-8696-c262f048f74a}) of ThingClass wallbeEco2 + + Could not initialize the communication with the wallbox. - - - - - - Charging Time - The name of the StateType ({5ca99dcc-745e-48a9-9201-69fbdce181f0}) of ThingClass scapoEco ----------- -The name of the StateType ({7f52adda-24a7-4c81-894c-bc08efe92f73}) of ThingClass compleoPro ----------- -The name of the StateType ({88fca3df-6cf0-4643-8e38-297167361f3a}) of ThingClass compleoEcoS ----------- -The name of the StateType ({95fd2098-f4b1-49b4-9776-68354c1db099}) of ThingClass wallbePro ----------- -The name of the StateType ({8dc2fef8-d16e-422a-8498-456b818f5752}) of ThingClass wallbeEco2 - - - - - - - - - - - - - - - - Charging current - The name of the ParamType (ThingClass: scapoVision, ActionType: maxChargingCurrent, ID: {82dff595-cf54-4e1f-bf2a-0223f2da4ede}) ----------- -The name of the StateType ({82dff595-cf54-4e1f-bf2a-0223f2da4ede}) of ThingClass scapoVision ----------- -The name of the ParamType (ThingClass: scapoEco, ActionType: maxChargingCurrent, ID: {ca5548f9-7604-49f0-8641-6bfc4401c444}) ----------- -The name of the StateType ({ca5548f9-7604-49f0-8641-6bfc4401c444}) of ThingClass scapoEco ----------- -The name of the ParamType (ThingClass: compleoPro, ActionType: maxChargingCurrent, ID: {16e6d989-c215-4f18-83bb-135ba47e73a7}) ----------- -The name of the StateType ({16e6d989-c215-4f18-83bb-135ba47e73a7}) of ThingClass compleoPro ----------- -The name of the ParamType (ThingClass: compleoEcoS, ActionType: maxChargingCurrent, ID: {fe4d27ba-d7ef-4068-b7c7-1b3d6b580624}) ----------- -The name of the StateType ({fe4d27ba-d7ef-4068-b7c7-1b3d6b580624}) of ThingClass compleoEcoS ----------- -The name of the ParamType (ThingClass: wallbePro, ActionType: maxChargingCurrent, ID: {180707c7-46bc-4b80-9054-5d14915f5bbe}) ----------- -The name of the StateType ({180707c7-46bc-4b80-9054-5d14915f5bbe}) of ThingClass wallbePro ----------- -The name of the ParamType (ThingClass: wallbeEco2, ActionType: maxChargingCurrent, ID: {60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1}) ----------- -The name of the StateType ({60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1}) of ThingClass wallbeEco2 - - - - - - - - - - - - - - - - Charging enabled - The name of the ParamType (ThingClass: scapoVision, ActionType: power, ID: {b31d0d6a-d340-45ba-bad5-1df377d0064f}) ----------- -The name of the StateType ({b31d0d6a-d340-45ba-bad5-1df377d0064f}) of ThingClass scapoVision ----------- -The name of the ParamType (ThingClass: scapoEco, ActionType: power, ID: {eea21ea1-131f-4661-93fc-36ba385f17cf}) ----------- -The name of the StateType ({eea21ea1-131f-4661-93fc-36ba385f17cf}) of ThingClass scapoEco ----------- -The name of the ParamType (ThingClass: compleoPro, ActionType: power, ID: {c449f2cc-a49b-4580-8ac6-20e1b7fae243}) ----------- -The name of the StateType ({c449f2cc-a49b-4580-8ac6-20e1b7fae243}) of ThingClass compleoPro ----------- -The name of the ParamType (ThingClass: compleoEcoS, ActionType: power, ID: {4d7ad54f-dc91-41eb-95e9-de85b2f599e7}) ----------- -The name of the StateType ({4d7ad54f-dc91-41eb-95e9-de85b2f599e7}) of ThingClass compleoEcoS ----------- -The name of the ParamType (ThingClass: wallbePro, ActionType: power, ID: {6e630b2d-2fba-4cd6-b5eb-d1d9679e8229}) ----------- -The name of the StateType ({6e630b2d-2fba-4cd6-b5eb-d1d9679e8229}) of ThingClass wallbePro ----------- -The name of the ParamType (ThingClass: wallbeEco2, ActionType: power, ID: {26793adc-de10-426f-bb17-170c227891b2}) ----------- -The name of the StateType ({26793adc-de10-426f-bb17-170c227891b2}) of ThingClass wallbeEco2 - - - - - Compleo ECO s - The name of the ThingClass ({b8add30e-d83e-441c-a707-08d61b55cf69}) - - - - - Compleo PRO - The name of the ThingClass ({be881b17-2a2a-43af-8331-39e3b4a8885d}) - - - - - - - - - - Connected - The name of the StateType ({3b1defc1-57df-4615-a24d-5208b94c978f}) of ThingClass scapoVision ----------- -The name of the StateType ({5a9bb6a9-d45f-44db-94d4-d86e03deb48c}) of ThingClass scapoEco ----------- -The name of the StateType ({5eaf3ac5-4759-4c08-b77a-3b044644c770}) of ThingClass compleoPro ----------- -The name of the StateType ({372b08b6-2bfb-4b9e-81a6-d1a6dac0b2c9}) of ThingClass compleoEcoS ----------- -The name of the StateType ({60abb255-d6b8-4ffb-8ee6-7946576b35b6}) of ThingClass wallbePro ----------- -The name of the StateType ({39a8e92b-40e5-4648-b5a8-2ffcb5598081}) of ThingClass wallbeEco2 - - - - - - - Current power usage - The name of the StateType ({1aee372c-84cf-4ffd-adbe-5c56653124fd}) of ThingClass scapoVision ----------- -The name of the StateType ({421bed00-ca55-4276-a610-86a9a97279fa}) of ThingClass compleoPro ----------- -The name of the StateType ({2462e691-9457-4772-88ff-224ee2982cad}) of ThingClass wallbePro - - - - - - - - - - Enable/disable charging - The name of the ActionType ({b31d0d6a-d340-45ba-bad5-1df377d0064f}) of ThingClass scapoVision ----------- -The name of the ActionType ({eea21ea1-131f-4661-93fc-36ba385f17cf}) of ThingClass scapoEco ----------- -The name of the ActionType ({c449f2cc-a49b-4580-8ac6-20e1b7fae243}) of ThingClass compleoPro ----------- -The name of the ActionType ({4d7ad54f-dc91-41eb-95e9-de85b2f599e7}) of ThingClass compleoEcoS ----------- -The name of the ActionType ({6e630b2d-2fba-4cd6-b5eb-d1d9679e8229}) of ThingClass wallbePro ----------- -The name of the ActionType ({26793adc-de10-426f-bb17-170c227891b2}) of ThingClass wallbeEco2 - - - - - - - - - - Firmware version - The name of the StateType ({c275e21d-6ecb-432e-924e-96ebf804abe1}) of ThingClass scapoVision ----------- -The name of the StateType ({3b7200b0-852e-44ba-b3c6-08d454ab6137}) of ThingClass scapoEco ----------- -The name of the StateType ({62f8294e-f35d-4b90-a8fe-1cd41106fc2b}) of ThingClass compleoPro ----------- -The name of the StateType ({9d4900eb-b16b-4881-ae68-f198cfc8b768}) of ThingClass compleoEcoS ----------- -The name of the StateType ({6449f171-2152-435f-854a-58d07f99b6f9}) of ThingClass wallbePro ----------- -The name of the StateType ({f4c822a0-454b-4782-85d2-8c60bacb4fe8}) of ThingClass wallbeEco2 - - - - - - - - - - MAC address - The name of the ParamType (ThingClass: scapoVision, Type: thing, ID: {8a0a3c7c-1197-4c55-8a7d-ee87587235bd}) ----------- -The name of the ParamType (ThingClass: scapoEco, Type: thing, ID: {2b544329-4d59-4974-8c3d-8b6aadf26c2c}) ----------- -The name of the ParamType (ThingClass: compleoPro, Type: thing, ID: {e7ca8712-4a4a-44e8-a36b-233486acd687}) ----------- -The name of the ParamType (ThingClass: compleoEcoS, Type: thing, ID: {d65aa536-e60d-4e0d-986c-80c1023e0e81}) ----------- -The name of the ParamType (ThingClass: wallbePro, Type: thing, ID: {71a147c7-a87c-45e0-9e91-657d5c7fd0cd}) ----------- -The name of the ParamType (ThingClass: wallbeEco2, Type: thing, ID: {551b03f0-dd70-4463-929b-3668dbd3290f}) - - - - - - - - - - - - - Phase count - The name of the StateType ({a082fe9a-004b-4a0c-83da-715dcd58297d}) of ThingClass scapoVision ----------- -The name of the StateType ({554aca26-85d4-4b8f-aa08-f3a8beb0a9dc}) of ThingClass scapoEco ----------- -The name of the ParamType (ThingClass: scapoEco, Type: settings, ID: {1882ec8e-5f3b-43cd-a099-07efead9b809}) ----------- -The name of the StateType ({b5a70e5e-afe8-453e-8a1e-911a4c12aa32}) of ThingClass compleoPro ----------- -The name of the StateType ({b25c3ef6-b76b-4290-9b8a-3602f833a96e}) of ThingClass compleoEcoS ----------- -The name of the ParamType (ThingClass: compleoEcoS, Type: settings, ID: {ab4de76e-bb8d-4292-a9d7-c8ec406e9203}) ----------- -The name of the StateType ({d203ec5b-08b1-4cc3-9cee-6c259832c262}) of ThingClass wallbePro ----------- -The name of the StateType ({75e75e12-bd22-444a-804c-c589731b206e}) of ThingClass wallbeEco2 ----------- -The name of the ParamType (ThingClass: wallbeEco2, Type: settings, ID: {a41ef0c3-873c-48c4-8647-5cfe2ce1b974}) - - - - - Scapo Economy - The name of the ThingClass ({3ed98b8c-7a6b-4c2a-9d47-30b0dbb04420}) - - - - - - - - - - Plugged in - The name of the StateType ({a30b07f5-b1cc-4613-977c-9e8fccee4484}) of ThingClass scapoVision ----------- -The name of the StateType ({3da9af10-f922-42dc-92d1-5e94ae8d5bf0}) of ThingClass scapoEco ----------- -The name of the StateType ({f9262e60-261e-4412-a160-ed6c96dca0c4}) of ThingClass compleoPro ----------- -The name of the StateType ({0db3c46d-cb0e-499a-9422-c0723cbd392e}) of ThingClass compleoEcoS ----------- -The name of the StateType ({6e276c0f-fe7b-4a26-81f0-01f116d14093}) of ThingClass wallbePro ----------- -The name of the StateType ({40fa9559-8758-4a65-8a0d-d60e96e0ddf0}) of ThingClass wallbeEco2 - - - - - Scapo Vision - The name of the ThingClass ({ca775df3-2452-4426-a9e7-b7cf2fffbfd3}) - - - - - - - - - - Set charging current - The name of the ActionType ({82dff595-cf54-4e1f-bf2a-0223f2da4ede}) of ThingClass scapoVision ----------- -The name of the ActionType ({ca5548f9-7604-49f0-8641-6bfc4401c444}) of ThingClass scapoEco ----------- -The name of the ActionType ({16e6d989-c215-4f18-83bb-135ba47e73a7}) of ThingClass compleoPro ----------- -The name of the ActionType ({fe4d27ba-d7ef-4068-b7c7-1b3d6b580624}) of ThingClass compleoEcoS ----------- -The name of the ActionType ({180707c7-46bc-4b80-9054-5d14915f5bbe}) of ThingClass wallbePro ----------- -The name of the ActionType ({60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1}) of ThingClass wallbeEco2 - - - - - - - Total consumed energy - The name of the StateType ({0475aae1-a0c9-4290-8827-b7fa66e5cbb7}) of ThingClass scapoVision ----------- -The name of the StateType ({1a4c1114-3250-482e-a065-366c0b2789d4}) of ThingClass compleoPro ----------- -The name of the StateType ({1f7d3486-fc4a-44d9-959a-b54e1e9e5883}) of ThingClass wallbePro - - - - - Wallbe - The name of the vendor ({831b4b87-0a6c-4d51-b055-967bb6e5fab5}) - - - - - Wallbe ECO 2.0 - The name of the ThingClass ({e66c84f6-b398-47e9-8aeb-33840e7b4492}) - - - - - Wallbe Pro - The name of the ThingClass ({dbf4d7ff-8caf-48a8-85d3-e8ab2aabf17c}) - - - - - compleo - The name of the vendor ({4757ce8a-6a24-4c60-9226-672f8fb47568}) - - - - - phoenixContact - The name of the plugin PhoenixContact ({0de5bbd2-0dad-4727-9a17-3ee149106048}) - - - - - scapo - The name of the vendor ({7f07e19c-e225-492c-9937-97c527eb24f5}) + + DIP switch 10 is not enabled in the wallbox. Please enable it, restart the wallbox and try again.