From 4443ff04f1bdf1d08d2276fb76ab31a827223ce4 Mon Sep 17 00:00:00 2001 From: Martin Lukas Date: Thu, 1 Aug 2024 08:55:09 +0200 Subject: [PATCH 01/12] Add new registers for * vendor name * product name * firmware version * absolute charged energy to regarding json files Signed-off-by: Martin Lukas --- inro/integrationplugininro.json | 28 ++++++++++++++++++++++ inro/pantabox-registers.json | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/inro/integrationplugininro.json b/inro/integrationplugininro.json index d5e1962..8f84598 100644 --- a/inro/integrationplugininro.json +++ b/inro/integrationplugininro.json @@ -146,6 +146,34 @@ "displayName": "Modbus TCP version", "type": "QString", "defaultValue": "" + }, + { + "id": "e247585c-18d2-454f-ac12-d625ce36cc52", + "name": "vendorName", + "displayName": "Vendor name", + "type": "QString", + "defaultValue": "" + }, + { + "id": "815db609-f739-4df6-aafc-6814ec12e102", + "name": "productName", + "displayName": "Product name", + "type": "QString", + "defaultValue": "" + }, + { + "id": "8c92890f-c9fb-44dd-8665-f1502fc90912", + "name": "firmwareVersion", + "displayName": "Firmware version", + "type": "QString", + "defaultValue": "" + }, + { + "id": "e772b753-ced9-41dc-8cbd-7fc58834eaa9", + "name": "absoluteEnergy", + "displayName": "Absolute charged energy", + "type": "double", + "defaultValue": 0 } ] } diff --git a/inro/pantabox-registers.json b/inro/pantabox-registers.json index 05ec69f..680ac60 100644 --- a/inro/pantabox-registers.json +++ b/inro/pantabox-registers.json @@ -59,6 +59,36 @@ "defaultValue": "0", "access": "RO" }, + { + "id": "vendorName", + "address": 260, + "size": 2, + "type": "string", + "registerType": "inputRegister", + "description": "Name of vendor", + "readSchedule": "init", + "access": "RO" + }, + { + "id": "productName", + "address": 262, + "size": 4, + "type": "string", + "registerType": "inputRegister", + "description": "Name of product", + "readSchedule": "init", + "access": "RO" + }, + { + "id": "firmwareVersion", + "address": 266, + "size": 16, + "type": "uint32", + "registerType": "inputRegister", + "description": "Firmware version", + "readSchedule": "init", + "access": "RO" + }, { "id": "chargingState", "address": 512, @@ -119,6 +149,18 @@ "defaultValue": "0", "access": "RO" }, + { + "id": "absoluteEnergy", + "address": 519, + "size": 2, + "type": "uint32", + "registerType": "inputRegister", + "description": "Absolute charged energy", + "unit": "Wh", + "readSchedule": "update", + "defaultValue": "0", + "access": "RO" + }, { "id": "chargingEnabled", "address": 768, From 5affe6614e43bf657f3ea7bba440811c50b44f1b Mon Sep 17 00:00:00 2001 From: Martin Lukas Date: Fri, 2 Aug 2024 08:12:34 +0200 Subject: [PATCH 02/12] Implementation of play pause depending on action issuer Signed-off-by: Martin Lukas --- inro/integrationplugininro.cpp | 67 +++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index 5eda2bc..5c54585 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -162,27 +162,58 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) if (info->action().actionTypeId() == pantaboxPowerActionTypeId) { bool power = info->action().paramValue(pantaboxPowerActionPowerParamTypeId).toBool(); - qCDebug(dcInro()) << "PANTABOX: Set power" << (power ? 1 : 0); - QModbusReply *reply = connection->setChargingEnabled(power ? 1 : 0); - if (!reply) { - qCWarning(dcInro()) << "Execute action failed because the reply could not be created."; - info->finish(Thing::ThingErrorHardwareFailure); + if (info->action().triggeredBy() == Action::TriggeredByUser) { + + // When power is set by user, charging is going to stop or start depending on setting + qCDebug(dcInro()) << "PANTABOX: Set power by user" << (power ? 1 : 0); + QModbusReply *reply = connection->setChargingEnabled(power ? 1 : 0); + + if (!reply) { + qCWarning(dcInro()) << "Execute action failed because the reply could not be created."; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, info, [info, reply, power](){ + if (reply->error() == QModbusDevice::NoError) { + info->thing()->setStateValue(pantaboxPowerStateTypeId, power); + qCDebug(dcInro()) << "PANTABOX: Set power by user finished successfully"; + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcInro()) << "Error setting power by user:" << reply->error() << reply->errorString(); + info->finish(Thing::ThingErrorHardwareFailure); + } + }); + return; + } else { + + // When power is set to 0 by energy manager, max charging current is set to 0 otherwise take the configured max charging current + qCDebug(dcInro()) << "PANTABOX: Pause session by energy manager"; + + quint16 chargingCurrent = power ? info->action().paramValue(pantaboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt() : 0; + QModbusReply *reply = connection->setMaxChargingCurrent(chargingCurrent); + + if (!reply) { + qCWarning(dcInro()) << "Execute action failed because the reply could not be created."; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, info, [info, reply, chargingCurrent](){ + if (reply->error() == QModbusDevice::NoError) { + info->thing()->setStateValue(pantaboxMaxChargingCurrentStateTypeId, chargingCurrent); + qCDebug(dcInro()) << "PANTABOX: Set max charging current finished successfully"; + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcInro()) << "Error setting charging current:" << reply->error() << reply->errorString(); + info->finish(Thing::ThingErrorHardwareFailure); + } + }); return; } - - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, info, [info, reply, power](){ - if (reply->error() == QModbusDevice::NoError) { - info->thing()->setStateValue(pantaboxPowerStateTypeId, power); - qCDebug(dcInro()) << "PANTABOX: Set power finished successfully"; - info->finish(Thing::ThingErrorNoError); - } else { - qCWarning(dcInro()) << "Error setting power:" << reply->error() << reply->errorString(); - info->finish(Thing::ThingErrorHardwareFailure); - } - }); - return; } if (info->action().actionTypeId() == pantaboxMaxChargingCurrentActionTypeId) { From 5d15fc9e1685789daec7354fb1b4a841bc9c289b Mon Sep 17 00:00:00 2001 From: Martin Lukas Date: Tue, 6 Aug 2024 07:09:22 +0200 Subject: [PATCH 03/12] Add backwards compatibility for modbus protocol Add verification for new registers Signed-off-by: Martin Lukas --- inro/inro.pro | 2 + inro/integrationplugininro.cpp | 30 +- inro/integrationplugininro.h | 4 +- inro/pantabox.cpp | 540 +++++++++++++++++++++++++++++++++ inro/pantabox.h | 21 ++ inro/pantaboxdiscovery.cpp | 52 ++-- inro/pantaboxdiscovery.h | 7 +- 7 files changed, 615 insertions(+), 41 deletions(-) create mode 100644 inro/pantabox.cpp create mode 100644 inro/pantabox.h diff --git a/inro/inro.pro b/inro/inro.pro index 8abdb2d..18de92a 100644 --- a/inro/inro.pro +++ b/inro/inro.pro @@ -8,8 +8,10 @@ include(../modbus.pri) HEADERS += \ integrationplugininro.h \ + pantabox.h \ pantaboxdiscovery.h SOURCES += \ integrationplugininro.cpp \ + pantabox.cpp \ pantaboxdiscovery.cpp diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index 5c54585..1abbba7 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -136,7 +136,7 @@ void IntegrationPluginInro::postSetupThing(Thing *thing) if (!m_refreshTimer) { m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(2); connect(m_refreshTimer, &PluginTimer::timeout, this, [this] { - foreach (PantaboxModbusTcpConnection *connection, m_connections) { + foreach (Pantabox *connection, m_connections) { if (connection->reachable()) { connection->update(); } @@ -152,7 +152,7 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) { if (info->thing()->thingClassId() == pantaboxThingClassId) { - PantaboxModbusTcpConnection *connection = m_connections.value(info->thing()); + Pantabox *connection = m_connections.value(info->thing()); if (!connection->reachable()) { qCWarning(dcInro()) << "Cannot execute action. The PANTABOX is not reachable"; @@ -248,7 +248,7 @@ void IntegrationPluginInro::thingRemoved(Thing *thing) qCDebug(dcInro()) << "Thing removed" << thing->name(); if (m_connections.contains(thing)) { - PantaboxModbusTcpConnection *connection = m_connections.take(thing); + Pantabox *connection = m_connections.take(thing); connection->disconnectDevice(); connection->deleteLater(); } @@ -269,8 +269,8 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) Thing *thing = info->thing(); NetworkDeviceMonitor *monitor = m_monitors.value(thing); - PantaboxModbusTcpConnection *connection = new PantaboxModbusTcpConnection(monitor->networkDeviceInfo().address(), 502, 1, this); - connect(info, &ThingSetupInfo::aborted, connection, &PantaboxModbusTcpConnection::deleteLater); + Pantabox *connection = new Pantabox(monitor->networkDeviceInfo().address(), 502, 1, this); + connect(info, &ThingSetupInfo::aborted, connection, &Pantabox::deleteLater); // Monitor reachability connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ @@ -289,7 +289,7 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) }); // Connection reachability - connect(connection, &PantaboxModbusTcpConnection::reachableChanged, thing, [thing, connection](bool reachable){ + connect(connection, &Pantabox::reachableChanged, thing, [thing, connection](bool reachable){ qCInfo(dcInro()) << "Reachable changed to" << reachable << "for" << thing; thing->setStateValue("connected", reachable); @@ -301,28 +301,28 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) } }); - connect(connection, &PantaboxModbusTcpConnection::updateFinished, thing, [thing, connection](){ + connect(connection, &Pantabox::updateFinished, thing, [thing, connection](){ qCDebug(dcInro()) << "Update finished for" << thing; qCDebug(dcInro()) << connection; QString chargingStateString; switch(connection->chargingState()) { - case PantaboxModbusTcpConnection::ChargingStateA: + case Pantabox::ChargingStateA: chargingStateString = "A"; break; - case PantaboxModbusTcpConnection::ChargingStateB: + case Pantabox::ChargingStateB: chargingStateString = "B"; break; - case PantaboxModbusTcpConnection::ChargingStateC: + case Pantabox::ChargingStateC: chargingStateString = "C"; break; - case PantaboxModbusTcpConnection::ChargingStateD: + case Pantabox::ChargingStateD: chargingStateString = "D"; break; - case PantaboxModbusTcpConnection::ChargingStateE: + case Pantabox::ChargingStateE: chargingStateString = "E"; break; - case PantaboxModbusTcpConnection::ChargingStateF: + case Pantabox::ChargingStateF: chargingStateString = "F"; break; } @@ -333,8 +333,8 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) // C: connected, charging // D: ventilation required // E: F: fault/error - thing->setStateValue(pantaboxPluggedInStateTypeId, connection->chargingState() >= PantaboxModbusTcpConnection::ChargingStateB); - thing->setStateValue(pantaboxChargingStateTypeId, connection->chargingState() >= PantaboxModbusTcpConnection::ChargingStateC); + thing->setStateValue(pantaboxPluggedInStateTypeId, connection->chargingState() >= Pantabox::ChargingStateB); + thing->setStateValue(pantaboxChargingStateTypeId, connection->chargingState() >= Pantabox::ChargingStateC); thing->setStateValue(pantaboxCurrentPowerStateTypeId, connection->currentPower()); // W thing->setStateValue(pantaboxTotalEnergyConsumedStateTypeId, connection->chargedEnergy() / 1000.0); // Wh thing->setStateMaxValue(pantaboxMaxChargingCurrentActionTypeId, connection->maxPossibleChargingCurrent()); diff --git a/inro/integrationplugininro.h b/inro/integrationplugininro.h index 3267bdb..58aa8e2 100644 --- a/inro/integrationplugininro.h +++ b/inro/integrationplugininro.h @@ -36,7 +36,7 @@ #include #include "extern-plugininfo.h" -#include "pantaboxmodbustcpconnection.h" +#include "pantabox.h" class IntegrationPluginInro: public IntegrationPlugin { @@ -56,7 +56,7 @@ public: private: PluginTimer *m_refreshTimer = nullptr; - QHash m_connections; + QHash m_connections; QHash m_monitors; void setupConnection(ThingSetupInfo *info); diff --git a/inro/pantabox.cpp b/inro/pantabox.cpp new file mode 100644 index 0000000..249c6c8 --- /dev/null +++ b/inro/pantabox.cpp @@ -0,0 +1,540 @@ +#include "pantabox.h" +#include "loggingcategories.h" + +NYMEA_LOGGING_CATEGORY(dcPantabox, "Pantabox") + +Pantabox::Pantabox(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) : + PantaboxModbusTcpConnection(hostAddress, port, slaveId, parent) +{ + // Note: sometimes right after the discovery / setup the check fails the first time due to server busy error, + // this is a very slow or busy device since it returns quiet often that error. Don't faile with the first busy error... + setCheckReachableRetries(3); +} + +bool Pantabox::initialize() { + + if (!m_reachable) { + qCWarning(dcPantabox()) << "Tried to initialize but the device is not to be reachable."; + return false; + } + + if (m_initObject) { + qCWarning(dcPantabox()) << "Tried to initialize but the init process is already running."; + return false; + } + + m_initializing = true; + + // Parent object for the init process + m_initObject = new QObject(this); + + QModbusReply *reply = nullptr; + + // Read Serial number (hex) + qCDebug(dcPantabox()) << "--> Read init \"Serial number (hex)\" register:" << 256 << "size:" << 2; + reply = readSerialNumber(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Serial number (hex)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + finishInitialization(false); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingInitReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ + handleModbusError(reply->error()); + m_pendingInitReplies.removeAll(reply); + if (reply->error() != QModbusDevice::NoError) { + finishInitialization(false); + return; + } + + const QModbusDataUnit unit = reply->result(); + processSerialNumberRegisterValues(unit.values()); + verifyInitFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Serial number (hex)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Serial number (hex)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read ModbusTCP version + qCDebug(dcPantabox()) << "--> Read init \"ModbusTCP version\" register:" << 258 << "size:" << 2; + reply = readModbusTcpVersion(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"ModbusTCP version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + finishInitialization(false); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingInitReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ + handleModbusError(reply->error()); + m_pendingInitReplies.removeAll(reply); + if (reply->error() != QModbusDevice::NoError) { + finishInitialization(false); + return; + } + + const QModbusDataUnit unit = reply->result(); + processModbusTcpVersionRegisterValues(unit.values()); + verifyInitFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"ModbusTCP version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"ModbusTCP version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Following Modbus registers are available since ModbusTcpVersion 1.1 + if (m_modbusTcpVersion < 257) + return true; + + // Read Name of vendor + qCDebug(dcPantabox()) << "--> Read init \"Name of vendor\" register:" << 260 << "size:" << 2; + reply = readVendorName(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Name of vendor\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + finishInitialization(false); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingInitReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ + handleModbusError(reply->error()); + m_pendingInitReplies.removeAll(reply); + if (reply->error() != QModbusDevice::NoError) { + finishInitialization(false); + return; + } + + const QModbusDataUnit unit = reply->result(); + processVendorNameRegisterValues(unit.values()); + verifyInitFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Name of vendor\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Name of vendor\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read Name of product + qCDebug(dcPantabox()) << "--> Read init \"Name of product\" register:" << 262 << "size:" << 4; + reply = readProductName(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Name of product\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + finishInitialization(false); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingInitReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ + handleModbusError(reply->error()); + m_pendingInitReplies.removeAll(reply); + if (reply->error() != QModbusDevice::NoError) { + finishInitialization(false); + return; + } + + const QModbusDataUnit unit = reply->result(); + processProductNameRegisterValues(unit.values()); + verifyInitFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Name of product\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Name of product\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read Firmware version + qCDebug(dcPantabox()) << "--> Read init \"Firmware version\" register:" << 266 << "size:" << 16; + reply = readFirmwareVersion(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + finishInitialization(false); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingInitReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ + handleModbusError(reply->error()); + m_pendingInitReplies.removeAll(reply); + if (reply->error() != QModbusDevice::NoError) { + finishInitialization(false); + return; + } + + const QModbusDataUnit unit = reply->result(); + processFirmwareVersionRegisterValues(unit.values()); + verifyInitFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + return true; +} + +bool Pantabox::update() { + if (!m_modbusTcpMaster->connected()) + return false; + + if (!m_pendingUpdateReplies.isEmpty()) { + qCDebug(dcPantabox()) << "Tried to update but there are still some update replies pending. Waiting for them to be finished..."; + return true; + } + + QModbusReply *reply = nullptr; + + // Read Charging state + qCDebug(dcPantabox()) << "--> Read \"Charging state\" register:" << 512 << "size:" << 1; + reply = readChargingState(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processChargingStateRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read Current charging power + qCDebug(dcPantabox()) << "--> Read \"Current charging power\" register:" << 513 << "size:" << 2; + reply = readCurrentPower(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processCurrentPowerRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read Charged energy + qCDebug(dcPantabox()) << "--> Read \"Charged energy\" register:" << 515 << "size:" << 2; + reply = readChargedEnergy(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processChargedEnergyRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read Maximal possible charging current (adapter) + qCDebug(dcPantabox()) << "--> Read \"Maximal possible charging current (adapter)\" register:" << 517 << "size:" << 1; + reply = readMaxPossibleChargingCurrent(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processMaxPossibleChargingCurrentRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read Actual charging current + qCDebug(dcPantabox()) << "--> Read \"Actual charging current\" register:" << 518 << "size:" << 1; + reply = readChargingCurrent(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processChargingCurrentRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // The Modbus register for absolute charged energy is available since Modbusversion 1.2 + if (m_modbusTcpVersion < 258) { + + // Read Absolute charged energy + qCDebug(dcPantabox()) << "--> Read \"Absolute charged energy\" register:" << 519 << "size:" << 2; + reply = readAbsoluteEnergy(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processAbsoluteEnergyRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + } + + // Read Charging enabled (1) / disabled (0) + qCDebug(dcPantabox()) << "--> Read \"Charging enabled (1) / disabled (0)\" register:" << 768 << "size:" << 1; + reply = readChargingEnabled(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processChargingEnabledRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read Max charging current + qCDebug(dcPantabox()) << "--> Read \"Max charging current\" register:" << 769 << "size:" << 1; + reply = readMaxChargingCurrent(); + if (!reply) { + qCWarning(dcPantabox()) << "Error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processMaxChargingCurrentRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + return true; +} diff --git a/inro/pantabox.h b/inro/pantabox.h new file mode 100644 index 0000000..f254e27 --- /dev/null +++ b/inro/pantabox.h @@ -0,0 +1,21 @@ +#ifndef PANTABOX_H +#define PANTABOX_H + +#include "pantaboxmodbustcpconnection.h" + +class Pantabox : public PantaboxModbusTcpConnection +{ + Q_OBJECT +public: + explicit Pantabox(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr); + explicit Pantabox(ModbusTcpMaster *modbusTcpMaster, quint16 slaveId, QObject *parent = nullptr); + ~Pantabox() = default; + + virtual bool update() override; + virtual bool initialize() override; + +signals: + +}; + +#endif // PANTABOX_H diff --git a/inro/pantaboxdiscovery.cpp b/inro/pantaboxdiscovery.cpp index 7c6ccad..9c06168 100644 --- a/inro/pantaboxdiscovery.cpp +++ b/inro/pantaboxdiscovery.cpp @@ -69,10 +69,10 @@ void PantaboxDiscovery::startDiscovery() void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) { - PantaboxModbusTcpConnection *connection = new PantaboxModbusTcpConnection(networkDeviceInfo.address(), m_port, m_modbusAddress, this); + Pantabox *connection = new Pantabox(networkDeviceInfo.address(), m_port, m_modbusAddress, this); m_connections.append(connection); - connect(connection, &PantaboxModbusTcpConnection::reachableChanged, this, [=](bool reachable){ + connect(connection, &Pantabox::reachableChanged, this, [=](bool reachable){ if (!reachable) { // Disconnected ... done with this connection cleanupConnection(connection); @@ -80,30 +80,42 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic } // Modbus TCP connected...ok, let's try to initialize it! - connect(connection, &PantaboxModbusTcpConnection::initializationFinished, this, [=](bool success){ + connect(connection, &Pantabox::initializationFinished, this, [=](bool success){ if (!success) { qCDebug(dcInro()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue..."; cleanupConnection(connection); return; } - // FIXME: find a better way to discover the device besides a valid init - qCDebug(dcInro()) << "Discovery: Connection initialized successfully" << connection->serialNumber(); + // Modbus registers for vendor and product name are available since Modbus version 1.1 + if (connection->modbusTcpVersion() > 257) { - Result result; - result.serialNumber = QString::number(connection->serialNumber(), 16).toUpper(); - result.modbusTcpVersion = modbusVersionToString(connection->modbusTcpVersion()); - result.networkDeviceInfo = networkDeviceInfo; - m_results.append(result); + // Only add device to result when correct device parameters were read + if (connection->vendorName() == "INRO" && connection->productName() == "PANTABOX") { + qCDebug(dcInro()) << "Discovery: Connection initialized successfully" << connection->serialNumber(); - qCInfo(dcInro()) << "Discovery: --> Found" - << "Serial number:" << result.serialNumber - << "(" << connection->serialNumber() << ")" - << "ModbusTCP version:" << result.modbusTcpVersion - << result.networkDeviceInfo; + Result result; + result.serialNumber = QString::number(connection->serialNumber(), 16).toUpper(); + result.modbusTcpVersion = modbusVersionToString(connection->modbusTcpVersion()); + result.networkDeviceInfo = networkDeviceInfo; + m_results.append(result); - // Done with this connection - cleanupConnection(connection); + qCInfo(dcInro()) << "Discovery: --> Found" + << "Serial number:" << result.serialNumber + << "(" << connection->serialNumber() << ")" + << "ModbusTCP version:" << result.modbusTcpVersion + << result.networkDeviceInfo; + + // Done with this connection + cleanupConnection(connection); + } else { + qCDebug(dcInro()) << "Discovery: Device not added to result because of wrong vendor or/and product name" + << connection->vendorName() + << connection->productName(); + } + } else { + qCDebug(dcInro()) << "Discovery: Device not added to result because of wrong ModbusTcpVersion" << modbusVersionToString(connection->modbusTcpVersion()); + } }); // Initializing... @@ -122,7 +134,7 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic }); // If check reachability failed...skip this host... - connect(connection, &PantaboxModbusTcpConnection::checkReachabilityFailed, this, [=](){ + connect(connection, &Pantabox::checkReachabilityFailed, this, [=](){ qCDebug(dcInro()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue..."; cleanupConnection(connection); }); @@ -131,7 +143,7 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic connection->connectDevice(); } -void PantaboxDiscovery::cleanupConnection(PantaboxModbusTcpConnection *connection) +void PantaboxDiscovery::cleanupConnection(Pantabox *connection) { m_connections.removeAll(connection); connection->disconnectDevice(); @@ -143,7 +155,7 @@ void PantaboxDiscovery::finishDiscovery() qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch(); // Cleanup any leftovers...we don't care any more - foreach (PantaboxModbusTcpConnection *connection, m_connections) + foreach (Pantabox *connection, m_connections) cleanupConnection(connection); qCInfo(dcInro()) << "Discovery: Finished the discovery process. Found" << m_results.count() diff --git a/inro/pantaboxdiscovery.h b/inro/pantaboxdiscovery.h index c0364ce..95c1796 100644 --- a/inro/pantaboxdiscovery.h +++ b/inro/pantaboxdiscovery.h @@ -34,8 +34,7 @@ #include #include - -#include "pantaboxmodbustcpconnection.h" +#include "pantabox.h" class PantaboxDiscovery : public QObject { @@ -66,12 +65,12 @@ private: QDateTime m_startDateTime; - QList m_connections; + QList m_connections; QList m_results; void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); - void cleanupConnection(PantaboxModbusTcpConnection *connection); + void cleanupConnection(Pantabox *connection); void finishDiscovery(); }; From 67668747241e64c1c20fbaa432703cd00bc2a702 Mon Sep 17 00:00:00 2001 From: Martin Lukas Date: Tue, 6 Aug 2024 08:43:58 +0200 Subject: [PATCH 04/12] Add copyright stuff to new class files Signed-off-by: Martin Lukas --- inro/pantabox.cpp | 30 ++++++++++++++++++++++++++++++ inro/pantabox.h | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/inro/pantabox.cpp b/inro/pantabox.cpp index 249c6c8..49b1912 100644 --- a/inro/pantabox.cpp +++ b/inro/pantabox.cpp @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 "pantabox.h" #include "loggingcategories.h" diff --git a/inro/pantabox.h b/inro/pantabox.h index f254e27..bc59507 100644 --- a/inro/pantabox.h +++ b/inro/pantabox.h @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 PANTABOX_H #define PANTABOX_H From 0488683cba9a5cbb3051d98d4e7cdb643e1e2bd3 Mon Sep 17 00:00:00 2001 From: Martin Lukas Date: Thu, 8 Aug 2024 13:14:26 +0200 Subject: [PATCH 05/12] Address review comments Signed-off-by: Martin Lukas --- inro/integrationplugininro.cpp | 72 ++++-- inro/integrationplugininro.json | 23 +- inro/pantabox-registers.json | 4 +- inro/pantabox.cpp | 399 +++++++++----------------------- inro/pantabox.h | 4 - inro/pantaboxdiscovery.cpp | 80 +++++-- inro/pantaboxdiscovery.h | 1 + 7 files changed, 217 insertions(+), 366 deletions(-) diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index 1abbba7..de06d88 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -163,10 +163,10 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) if (info->action().actionTypeId() == pantaboxPowerActionTypeId) { bool power = info->action().paramValue(pantaboxPowerActionPowerParamTypeId).toBool(); - if (info->action().triggeredBy() == Action::TriggeredByUser) { - + // Play/pause charging session feature is available from Modbus Tcp version 1.1 + if (connection->modbusTcpVersion() <= 65536) { // When power is set by user, charging is going to stop or start depending on setting - qCDebug(dcInro()) << "PANTABOX: Set power by user" << (power ? 1 : 0); + qCDebug(dcInro()) << "Set power by user" << (power ? 1 : 0); QModbusReply *reply = connection->setChargingEnabled(power ? 1 : 0); if (!reply) { @@ -179,7 +179,7 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) connect(reply, &QModbusReply::finished, info, [info, reply, power](){ if (reply->error() == QModbusDevice::NoError) { info->thing()->setStateValue(pantaboxPowerStateTypeId, power); - qCDebug(dcInro()) << "PANTABOX: Set power by user finished successfully"; + qCDebug(dcInro()) << "Set power by user finished successfully"; info->finish(Thing::ThingErrorNoError); } else { qCWarning(dcInro()) << "Error setting power by user:" << reply->error() << reply->errorString(); @@ -188,31 +188,55 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) }); return; } else { + if (info->action().triggeredBy() == Action::TriggeredByUser) { - // When power is set to 0 by energy manager, max charging current is set to 0 otherwise take the configured max charging current - qCDebug(dcInro()) << "PANTABOX: Pause session by energy manager"; + // When power is set by user, charging is going to stop or start depending on setting + qCDebug(dcInro()) << "Set power by user" << (power ? 1 : 0); + QModbusReply *reply = connection->setChargingEnabled(power ? 1 : 0); - quint16 chargingCurrent = power ? info->action().paramValue(pantaboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt() : 0; - QModbusReply *reply = connection->setMaxChargingCurrent(chargingCurrent); + if (!reply) { + qCWarning(dcInro()) << "Execute action failed because the reply could not be created."; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } - if (!reply) { - qCWarning(dcInro()) << "Execute action failed because the reply could not be created."; - info->finish(Thing::ThingErrorHardwareFailure); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, info, [info, reply, power](){ + if (reply->error() == QModbusDevice::NoError) { + info->thing()->setStateValue(pantaboxPowerStateTypeId, power); + qCDebug(dcInro()) << "Set power by user finished successfully"; + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcInro()) << "Error setting power by user:" << reply->error() << reply->errorString(); + info->finish(Thing::ThingErrorHardwareFailure); + } + }); + return; + } else { + // When power is set to 0 by automatisnm, max charging current is set to 0 otherwise take the configured max charging current + qCDebug(dcInro()) << "Going to play/pause charging session"; + + quint16 chargingCurrent = power ? info->thing()->stateValue(pantaboxMaxChargingCurrentStateTypeId).toUInt() : 0; + QModbusReply *reply = connection->setMaxChargingCurrent(chargingCurrent); + + if (!reply) { + qCWarning(dcInro()) << "Execute action failed because the reply could not be created."; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, info, [info, reply, power](){ + if (reply->error() == QModbusDevice::NoError) { + qCDebug(dcInro()) << (power ? "Play" : "Pause") << "session by energy manager"; + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcInro()) << "Error setting charging current:" << reply->error() << reply->errorString(); + info->finish(Thing::ThingErrorHardwareFailure); + } + }); return; } - - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, info, [info, reply, chargingCurrent](){ - if (reply->error() == QModbusDevice::NoError) { - info->thing()->setStateValue(pantaboxMaxChargingCurrentStateTypeId, chargingCurrent); - qCDebug(dcInro()) << "PANTABOX: Set max charging current finished successfully"; - info->finish(Thing::ThingErrorNoError); - } else { - qCWarning(dcInro()) << "Error setting charging current:" << reply->error() << reply->errorString(); - info->finish(Thing::ThingErrorHardwareFailure); - } - }); - return; } } diff --git a/inro/integrationplugininro.json b/inro/integrationplugininro.json index 8f84598..0176106 100644 --- a/inro/integrationplugininro.json +++ b/inro/integrationplugininro.json @@ -146,34 +146,13 @@ "displayName": "Modbus TCP version", "type": "QString", "defaultValue": "" - }, - { - "id": "e247585c-18d2-454f-ac12-d625ce36cc52", - "name": "vendorName", - "displayName": "Vendor name", - "type": "QString", - "defaultValue": "" - }, - { - "id": "815db609-f739-4df6-aafc-6814ec12e102", - "name": "productName", - "displayName": "Product name", - "type": "QString", - "defaultValue": "" - }, + }, { "id": "8c92890f-c9fb-44dd-8665-f1502fc90912", "name": "firmwareVersion", "displayName": "Firmware version", "type": "QString", "defaultValue": "" - }, - { - "id": "e772b753-ced9-41dc-8cbd-7fc58834eaa9", - "name": "absoluteEnergy", - "displayName": "Absolute charged energy", - "type": "double", - "defaultValue": 0 } ] } diff --git a/inro/pantabox-registers.json b/inro/pantabox-registers.json index 680ac60..edb4d13 100644 --- a/inro/pantabox-registers.json +++ b/inro/pantabox-registers.json @@ -66,7 +66,6 @@ "type": "string", "registerType": "inputRegister", "description": "Name of vendor", - "readSchedule": "init", "access": "RO" }, { @@ -76,7 +75,6 @@ "type": "string", "registerType": "inputRegister", "description": "Name of product", - "readSchedule": "init", "access": "RO" }, { @@ -86,7 +84,7 @@ "type": "uint32", "registerType": "inputRegister", "description": "Firmware version", - "readSchedule": "init", + "readSchedule": "update", "access": "RO" }, { diff --git a/inro/pantabox.cpp b/inro/pantabox.cpp index 49b1912..452c09f 100644 --- a/inro/pantabox.cpp +++ b/inro/pantabox.cpp @@ -30,248 +30,30 @@ #include "pantabox.h" #include "loggingcategories.h" - -NYMEA_LOGGING_CATEGORY(dcPantabox, "Pantabox") +#include "pantaboxmodbustcpconnection.cpp" Pantabox::Pantabox(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) : PantaboxModbusTcpConnection(hostAddress, port, slaveId, parent) { - // Note: sometimes right after the discovery / setup the check fails the first time due to server busy error, - // this is a very slow or busy device since it returns quiet often that error. Don't faile with the first busy error... - setCheckReachableRetries(3); -} -bool Pantabox::initialize() { - - if (!m_reachable) { - qCWarning(dcPantabox()) << "Tried to initialize but the device is not to be reachable."; - return false; - } - - if (m_initObject) { - qCWarning(dcPantabox()) << "Tried to initialize but the init process is already running."; - return false; - } - - m_initializing = true; - - // Parent object for the init process - m_initObject = new QObject(this); - - QModbusReply *reply = nullptr; - - // Read Serial number (hex) - qCDebug(dcPantabox()) << "--> Read init \"Serial number (hex)\" register:" << 256 << "size:" << 2; - reply = readSerialNumber(); - if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Serial number (hex)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); - finishInitialization(false); - return false; - } - - if (reply->isFinished()) { - reply->deleteLater(); // Broadcast reply returns immediatly - return false; - } - - m_pendingInitReplies.append(reply); - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ - handleModbusError(reply->error()); - m_pendingInitReplies.removeAll(reply); - if (reply->error() != QModbusDevice::NoError) { - finishInitialization(false); - return; - } - - const QModbusDataUnit unit = reply->result(); - processSerialNumberRegisterValues(unit.values()); - verifyInitFinished(); - }); - - connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ - QModbusResponse response = reply->rawResult(); - if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Serial number (hex)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); - } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Serial number (hex)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); - } - }); - - // Read ModbusTCP version - qCDebug(dcPantabox()) << "--> Read init \"ModbusTCP version\" register:" << 258 << "size:" << 2; - reply = readModbusTcpVersion(); - if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"ModbusTCP version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); - finishInitialization(false); - return false; - } - - if (reply->isFinished()) { - reply->deleteLater(); // Broadcast reply returns immediatly - return false; - } - - m_pendingInitReplies.append(reply); - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ - handleModbusError(reply->error()); - m_pendingInitReplies.removeAll(reply); - if (reply->error() != QModbusDevice::NoError) { - finishInitialization(false); - return; - } - - const QModbusDataUnit unit = reply->result(); - processModbusTcpVersionRegisterValues(unit.values()); - verifyInitFinished(); - }); - - connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ - QModbusResponse response = reply->rawResult(); - if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"ModbusTCP version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); - } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"ModbusTCP version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); - } - }); - - // Following Modbus registers are available since ModbusTcpVersion 1.1 - if (m_modbusTcpVersion < 257) - return true; - - // Read Name of vendor - qCDebug(dcPantabox()) << "--> Read init \"Name of vendor\" register:" << 260 << "size:" << 2; - reply = readVendorName(); - if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Name of vendor\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); - finishInitialization(false); - return false; - } - - if (reply->isFinished()) { - reply->deleteLater(); // Broadcast reply returns immediatly - return false; - } - - m_pendingInitReplies.append(reply); - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ - handleModbusError(reply->error()); - m_pendingInitReplies.removeAll(reply); - if (reply->error() != QModbusDevice::NoError) { - finishInitialization(false); - return; - } - - const QModbusDataUnit unit = reply->result(); - processVendorNameRegisterValues(unit.values()); - verifyInitFinished(); - }); - - connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ - QModbusResponse response = reply->rawResult(); - if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Name of vendor\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); - } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Name of vendor\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); - } - }); - - // Read Name of product - qCDebug(dcPantabox()) << "--> Read init \"Name of product\" register:" << 262 << "size:" << 4; - reply = readProductName(); - if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Name of product\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); - finishInitialization(false); - return false; - } - - if (reply->isFinished()) { - reply->deleteLater(); // Broadcast reply returns immediatly - return false; - } - - m_pendingInitReplies.append(reply); - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ - handleModbusError(reply->error()); - m_pendingInitReplies.removeAll(reply); - if (reply->error() != QModbusDevice::NoError) { - finishInitialization(false); - return; - } - - const QModbusDataUnit unit = reply->result(); - processProductNameRegisterValues(unit.values()); - verifyInitFinished(); - }); - - connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ - QModbusResponse response = reply->rawResult(); - if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Name of product\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); - } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Name of product\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); - } - }); - - // Read Firmware version - qCDebug(dcPantabox()) << "--> Read init \"Firmware version\" register:" << 266 << "size:" << 16; - reply = readFirmwareVersion(); - if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); - finishInitialization(false); - return false; - } - - if (reply->isFinished()) { - reply->deleteLater(); // Broadcast reply returns immediatly - return false; - } - - m_pendingInitReplies.append(reply); - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){ - handleModbusError(reply->error()); - m_pendingInitReplies.removeAll(reply); - if (reply->error() != QModbusDevice::NoError) { - finishInitialization(false); - return; - } - - const QModbusDataUnit unit = reply->result(); - processFirmwareVersionRegisterValues(unit.values()); - verifyInitFinished(); - }); - - connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){ - QModbusResponse response = reply->rawResult(); - if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); - } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); - } - }); - return true; } bool Pantabox::update() { if (!m_modbusTcpMaster->connected()) - return false; + return false; if (!m_pendingUpdateReplies.isEmpty()) { - qCDebug(dcPantabox()) << "Tried to update but there are still some update replies pending. Waiting for them to be finished..."; + qCDebug(dcPantaboxModbusTcpConnection()) << "Tried to update but there are still some update replies pending. Waiting for them to be finished..."; return true; } QModbusReply *reply = nullptr; // Read Charging state - qCDebug(dcPantabox()) << "--> Read \"Charging state\" register:" << 512 << "size:" << 1; + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Charging state\" register:" << 512 << "size:" << 1; reply = readChargingState(); if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); return false; } @@ -298,17 +80,17 @@ bool Pantabox::update() { connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ QModbusResponse response = reply->rawResult(); if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charging state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); } }); // Read Current charging power - qCDebug(dcPantabox()) << "--> Read \"Current charging power\" register:" << 513 << "size:" << 2; + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Current charging power\" register:" << 513 << "size:" << 2; reply = readCurrentPower(); if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); return false; } @@ -335,17 +117,17 @@ bool Pantabox::update() { connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ QModbusResponse response = reply->rawResult(); if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Current charging power\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); } }); // Read Charged energy - qCDebug(dcPantabox()) << "--> Read \"Charged energy\" register:" << 515 << "size:" << 2; + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Charged energy\" register:" << 515 << "size:" << 2; reply = readChargedEnergy(); if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); return false; } @@ -372,17 +154,17 @@ bool Pantabox::update() { connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ QModbusResponse response = reply->rawResult(); if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); } }); // Read Maximal possible charging current (adapter) - qCDebug(dcPantabox()) << "--> Read \"Maximal possible charging current (adapter)\" register:" << 517 << "size:" << 1; + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Maximal possible charging current (adapter)\" register:" << 517 << "size:" << 1; reply = readMaxPossibleChargingCurrent(); if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); return false; } @@ -409,17 +191,17 @@ bool Pantabox::update() { connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ QModbusResponse response = reply->rawResult(); if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Maximal possible charging current (adapter)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); } }); // Read Actual charging current - qCDebug(dcPantabox()) << "--> Read \"Actual charging current\" register:" << 518 << "size:" << 1; + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Actual charging current\" register:" << 518 << "size:" << 1; reply = readChargingCurrent(); if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); return false; } @@ -446,58 +228,17 @@ bool Pantabox::update() { connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ QModbusResponse response = reply->rawResult(); if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Actual charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); } }); - // The Modbus register for absolute charged energy is available since Modbusversion 1.2 - if (m_modbusTcpVersion < 258) { - - // Read Absolute charged energy - qCDebug(dcPantabox()) << "--> Read \"Absolute charged energy\" register:" << 519 << "size:" << 2; - reply = readAbsoluteEnergy(); - if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); - return false; - } - - if (reply->isFinished()) { - reply->deleteLater(); // Broadcast reply returns immediatly - return false; - } - - m_pendingUpdateReplies.append(reply); - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - m_pendingUpdateReplies.removeAll(reply); - handleModbusError(reply->error()); - if (reply->error() != QModbusDevice::NoError) { - verifyUpdateFinished(); - return; - } - - const QModbusDataUnit unit = reply->result(); - processAbsoluteEnergyRegisterValues(unit.values()); - verifyUpdateFinished(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - QModbusResponse response = reply->rawResult(); - if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); - } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); - } - }); - } - // Read Charging enabled (1) / disabled (0) - qCDebug(dcPantabox()) << "--> Read \"Charging enabled (1) / disabled (0)\" register:" << 768 << "size:" << 1; + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Charging enabled (1) / disabled (0)\" register:" << 768 << "size:" << 1; reply = readChargingEnabled(); if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); return false; } @@ -524,17 +265,17 @@ bool Pantabox::update() { connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ QModbusResponse response = reply->rawResult(); if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charging enabled (1) / disabled (0)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); } }); // Read Max charging current - qCDebug(dcPantabox()) << "--> Read \"Max charging current\" register:" << 769 << "size:" << 1; + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Max charging current\" register:" << 769 << "size:" << 1; reply = readMaxChargingCurrent(); if (!reply) { - qCWarning(dcPantabox()) << "Error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); return false; } @@ -561,10 +302,88 @@ bool Pantabox::update() { connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ QModbusResponse response = reply->rawResult(); if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); } else { - qCWarning(dcPantabox()) << "Modbus reply error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Max charging current\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); } }); + + // The Modbus register for absolute charged energy is available since Modbusversion 1.1 + if (m_modbusTcpVersion > 65536) { + + // Read Absolute charged energy + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Absolute charged energy\" register:" << 519 << "size:" << 2; + reply = readAbsoluteEnergy(); + if (!reply) { + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processAbsoluteEnergyRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + + // Read Firmware version + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Firmware version\" register:" << 266 << "size:" << 16; + reply = readFirmwareVersion(); + if (!reply) { + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processFirmwareVersionRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + } return true; } diff --git a/inro/pantabox.h b/inro/pantabox.h index bc59507..1e88d92 100644 --- a/inro/pantabox.h +++ b/inro/pantabox.h @@ -42,10 +42,6 @@ public: ~Pantabox() = default; virtual bool update() override; - virtual bool initialize() override; - -signals: - }; #endif // PANTABOX_H diff --git a/inro/pantaboxdiscovery.cpp b/inro/pantaboxdiscovery.cpp index 9c06168..0ccf067 100644 --- a/inro/pantaboxdiscovery.cpp +++ b/inro/pantaboxdiscovery.cpp @@ -33,7 +33,7 @@ PantaboxDiscovery::PantaboxDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) : QObject{parent}, - m_networkDeviceDiscovery{networkDeviceDiscovery} + m_networkDeviceDiscovery{networkDeviceDiscovery} { } @@ -88,33 +88,47 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic } // Modbus registers for vendor and product name are available since Modbus version 1.1 - if (connection->modbusTcpVersion() > 257) { + if (connection->modbusTcpVersion() > 65536) { - // Only add device to result when correct device parameters were read - if (connection->vendorName() == "INRO" && connection->productName() == "PANTABOX") { - qCDebug(dcInro()) << "Discovery: Connection initialized successfully" << connection->serialNumber(); - - Result result; - result.serialNumber = QString::number(connection->serialNumber(), 16).toUpper(); - result.modbusTcpVersion = modbusVersionToString(connection->modbusTcpVersion()); - result.networkDeviceInfo = networkDeviceInfo; - m_results.append(result); - - qCInfo(dcInro()) << "Discovery: --> Found" - << "Serial number:" << result.serialNumber - << "(" << connection->serialNumber() << ")" - << "ModbusTCP version:" << result.modbusTcpVersion - << result.networkDeviceInfo; - - // Done with this connection + QModbusReply *reply = connection->readProductName(); + if (!reply) { cleanupConnection(connection); - } else { - qCDebug(dcInro()) << "Discovery: Device not added to result because of wrong vendor or/and product name" - << connection->vendorName() - << connection->productName(); + return; } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + cleanupConnection(connection); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply, connection, networkDeviceInfo](){ + if (reply->error() != QModbusDevice::NoError) { + qCDebug(dcInro()) << "Discovery: Error reading product name error on" << networkDeviceInfo.address().toString() << "Continue..."; + cleanupConnection(connection); + return; + } + + const QModbusDataUnit unit = reply->result(); + qCDebug(dcInro()) << "<-- Response from \"Name of product\" register" << 262 << "size:" << 4 << unit.values(); + if (unit.values().size() == 4) { + QString receivedProductName = ModbusDataUtils::convertToString(unit.values(), connection->stringEndianness()); + if (receivedProductName.toUpper().contains("PANTABOX")) { + addResult(connection, networkDeviceInfo); + } else { + qCDebug(dcInro()) << "Discovery: Invalid product name " << receivedProductName + << "on" << networkDeviceInfo.address().toString() << "Continue..."; + cleanupConnection(connection); + } + } else { + qCDebug(dcInro()) << "Discovery: Reading from \"Name of product\" registers" << 262 << "size:" << 4 << "returned different size than requested. Ignoring incomplete data" << unit.values(); + cleanupConnection(connection); + } + }); } else { qCDebug(dcInro()) << "Discovery: Device not added to result because of wrong ModbusTcpVersion" << modbusVersionToString(connection->modbusTcpVersion()); + addResult(connection, networkDeviceInfo); } }); @@ -162,3 +176,23 @@ void PantaboxDiscovery::finishDiscovery() << "PANTABOXE wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); emit discoveryFinished(); } + +void PantaboxDiscovery::addResult(Pantabox *connection, const NetworkDeviceInfo &networkDeviceInfo) +{ + qCDebug(dcInro()) << "Discovery: Connection initialized successfully" << connection->serialNumber(); + + Result result; + result.serialNumber = QString::number(connection->serialNumber(), 16).toUpper(); + result.modbusTcpVersion = modbusVersionToString(connection->modbusTcpVersion()); + result.networkDeviceInfo = networkDeviceInfo; + m_results.append(result); + + qCInfo(dcInro()) << "Discovery: --> Found" + << "Serial number:" << result.serialNumber + << "(" << connection->serialNumber() << ")" + << "ModbusTCP version:" << result.modbusTcpVersion + << result.networkDeviceInfo; + + // Done with this connection + cleanupConnection(connection); +} diff --git a/inro/pantaboxdiscovery.h b/inro/pantaboxdiscovery.h index 95c1796..7732601 100644 --- a/inro/pantaboxdiscovery.h +++ b/inro/pantaboxdiscovery.h @@ -73,6 +73,7 @@ private: void cleanupConnection(Pantabox *connection); void finishDiscovery(); + void addResult(Pantabox *connection, const NetworkDeviceInfo &networkDeviceInfo); }; #endif // PANTABOXDISCOVERY_H From b52d1e99c15d3f644dab1241dcdb0d01f1698ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 8 Aug 2024 15:53:51 +0200 Subject: [PATCH 06/12] Update 1.2 verion handling and update states and debug information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Simon Stürz --- inro/integrationplugininro.cpp | 14 +++++- inro/pantabox-registers.json | 3 +- inro/pantabox.cpp | 90 ++++++++++++++++++++-------------- inro/pantabox.h | 6 ++- inro/pantaboxdiscovery.cpp | 17 ++----- inro/pantaboxdiscovery.h | 2 - 6 files changed, 75 insertions(+), 57 deletions(-) diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index de06d88..7dc2d11 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -321,7 +321,13 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) // Reset energy live values on disconnected thing->setStateValue(pantaboxCurrentPowerStateTypeId, 0); } else { - thing->setStateValue(pantaboxModbusTcpVersionStateTypeId, PantaboxDiscovery::modbusVersionToString(connection->modbusTcpVersion())); + connection->initialize(); + } + }); + + connect(connection, &Pantabox::initializationFinished, thing, [thing, connection](bool success){ + if (success) { + thing->setStateValue(pantaboxModbusTcpVersionStateTypeId, Pantabox::modbusVersionToString(connection->modbusTcpVersion())); } }); @@ -360,7 +366,7 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) thing->setStateValue(pantaboxPluggedInStateTypeId, connection->chargingState() >= Pantabox::ChargingStateB); thing->setStateValue(pantaboxChargingStateTypeId, connection->chargingState() >= Pantabox::ChargingStateC); thing->setStateValue(pantaboxCurrentPowerStateTypeId, connection->currentPower()); // W - thing->setStateValue(pantaboxTotalEnergyConsumedStateTypeId, connection->chargedEnergy() / 1000.0); // Wh + thing->setStateValue(pantaboxSessionEnergyStateTypeId, connection->chargedEnergy() / 1000.0); // Wh thing->setStateMaxValue(pantaboxMaxChargingCurrentActionTypeId, connection->maxPossibleChargingCurrent()); // Phase count is a setting, since we don't get the information from the device. @@ -371,6 +377,10 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) thing->setStateValue(pantaboxPhaseCountStateTypeId, Electricity::getPhaseCount(phases)); thing->setStateValue(pantaboxUsedPhasesStateTypeId, thing->setting(pantaboxSettingsPhasesParamTypeId).toString()); + // Following states depend on the modbus TCP version, default they will be reset. + thing->setStateValue(pantaboxFirmwareVersionStateTypeId, connection->firmwareVersion()); + thing->setStateValue(pantaboxTotalEnergyConsumedStateTypeId, connection->absoluteEnergy() / 1000.0); // Wh + }); m_connections.insert(thing, connection); diff --git a/inro/pantabox-registers.json b/inro/pantabox-registers.json index edb4d13..6e59fb9 100644 --- a/inro/pantabox-registers.json +++ b/inro/pantabox-registers.json @@ -2,6 +2,7 @@ "className": "Pantabox", "protocol": "TCP", "endianness": "LittleEndian", + "stringEndianness": "LittleEndian", "errorLimitUntilNotReachable": 2, "checkReachableRegister": "chargingState", "blocks": [ ], @@ -81,7 +82,7 @@ "id": "firmwareVersion", "address": 266, "size": 16, - "type": "uint32", + "type": "string", "registerType": "inputRegister", "description": "Firmware version", "readSchedule": "update", diff --git a/inro/pantabox.cpp b/inro/pantabox.cpp index 452c09f..9195c00 100644 --- a/inro/pantabox.cpp +++ b/inro/pantabox.cpp @@ -29,7 +29,6 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "pantabox.h" -#include "loggingcategories.h" #include "pantaboxmodbustcpconnection.cpp" Pantabox::Pantabox(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) : @@ -38,6 +37,13 @@ Pantabox::Pantabox(const QHostAddress &hostAddress, uint port, quint16 slaveId, } +QString Pantabox::modbusVersionToString(quint32 value) +{ + quint16 modbusVersionMinor = (value >> 16) & 0xffff; + quint16 modbusVersionMajor = value & 0xffff; + return QString("%1.%2").arg(modbusVersionMajor).arg(modbusVersionMinor); +} + bool Pantabox::update() { if (!m_modbusTcpMaster->connected()) return false; @@ -308,45 +314,11 @@ bool Pantabox::update() { } }); - // The Modbus register for absolute charged energy is available since Modbusversion 1.1 - if (m_modbusTcpVersion > 65536) { - // Read Absolute charged energy - qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Absolute charged energy\" register:" << 519 << "size:" << 2; - reply = readAbsoluteEnergy(); - if (!reply) { - qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); - return false; - } + // Following Modbus registers depend on the modbus TCP protocol version and require compatibility checks - if (reply->isFinished()) { - reply->deleteLater(); // Broadcast reply returns immediatly - return false; - } - - m_pendingUpdateReplies.append(reply); - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - m_pendingUpdateReplies.removeAll(reply); - handleModbusError(reply->error()); - if (reply->error() != QModbusDevice::NoError) { - verifyUpdateFinished(); - return; - } - - const QModbusDataUnit unit = reply->result(); - processAbsoluteEnergyRegisterValues(unit.values()); - verifyUpdateFinished(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - QModbusResponse response = reply->rawResult(); - if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { - qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); - } else { - qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); - } - }); + // Firmware version registers are available since modbus TCP version 1.1 (0x0001 0x0001) 0x10001 = 65537 + if (m_modbusTcpVersion >= 65537) { // Read Firmware version qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Firmware version\" register:" << 266 << "size:" << 16; @@ -385,5 +357,47 @@ bool Pantabox::update() { } }); } + + // Absolute charged energy is available since modbus TCP version 1.2 (0x0001 0x0002) 0x10002 = 65538 + if (m_modbusTcpVersion >= 65538) { + + // Read Absolute charged energy + qCDebug(dcPantaboxModbusTcpConnection()) << "--> Read \"Absolute charged energy\" register:" << 519 << "size:" << 2; + reply = readAbsoluteEnergy(); + if (!reply) { + qCWarning(dcPantaboxModbusTcpConnection()) << "Error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString(); + return false; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + return false; + } + + m_pendingUpdateReplies.append(reply); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + m_pendingUpdateReplies.removeAll(reply); + handleModbusError(reply->error()); + if (reply->error() != QModbusDevice::NoError) { + verifyUpdateFinished(); + return; + } + + const QModbusDataUnit unit = reply->result(); + processAbsoluteEnergyRegisterValues(unit.values()); + verifyUpdateFinished(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + QModbusResponse response = reply->rawResult(); + if (reply->error() == QModbusDevice::ProtocolError && response.isException()) { + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode()); + } else { + qCWarning(dcPantaboxModbusTcpConnection()) << "Modbus reply error occurred while reading \"Absolute charged energy\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString(); + } + }); + } + return true; } diff --git a/inro/pantabox.h b/inro/pantabox.h index 1e88d92..a9fe6ee 100644 --- a/inro/pantabox.h +++ b/inro/pantabox.h @@ -39,9 +39,11 @@ class Pantabox : public PantaboxModbusTcpConnection public: explicit Pantabox(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr); explicit Pantabox(ModbusTcpMaster *modbusTcpMaster, quint16 slaveId, QObject *parent = nullptr); - ~Pantabox() = default; + ~Pantabox() override = default ; - virtual bool update() override; + static QString modbusVersionToString(quint32 value); + + bool update() override; }; #endif // PANTABOX_H diff --git a/inro/pantaboxdiscovery.cpp b/inro/pantaboxdiscovery.cpp index 0ccf067..66ec346 100644 --- a/inro/pantaboxdiscovery.cpp +++ b/inro/pantaboxdiscovery.cpp @@ -43,13 +43,6 @@ QList PantaboxDiscovery::results() const return m_results; } -QString PantaboxDiscovery::modbusVersionToString(quint32 value) -{ - quint16 modbusVersionMinor = (value >> 8) & 0xffff; - quint16 modbusVersionMajor = value & 0xffff; - return QString("%1.%2").arg(modbusVersionMajor).arg(modbusVersionMinor); -} - void PantaboxDiscovery::startDiscovery() { qCInfo(dcInro()) << "Discovery: Start searching for PANTABOX wallboxes in the network..."; @@ -87,8 +80,8 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic return; } - // Modbus registers for vendor and product name are available since Modbus version 1.1 - if (connection->modbusTcpVersion() > 65536) { + // Vendor and product name registers are available since modbus TCP version 1.1 (0x0001 0x0001) 0x10001 = 65537 + if (connection->modbusTcpVersion() >= 65537) { QModbusReply *reply = connection->readProductName(); if (!reply) { @@ -111,7 +104,6 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic } const QModbusDataUnit unit = reply->result(); - qCDebug(dcInro()) << "<-- Response from \"Name of product\" register" << 262 << "size:" << 4 << unit.values(); if (unit.values().size() == 4) { QString receivedProductName = ModbusDataUtils::convertToString(unit.values(), connection->stringEndianness()); if (receivedProductName.toUpper().contains("PANTABOX")) { @@ -127,7 +119,8 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic } }); } else { - qCDebug(dcInro()) << "Discovery: Device not added to result because of wrong ModbusTcpVersion" << modbusVersionToString(connection->modbusTcpVersion()); + qCDebug(dcInro()) << "Discovery: Adding connection to results even tough the result is not precise due to modbus TCP protocol version:" + << connection->modbusTcpVersion() << Pantabox::modbusVersionToString(connection->modbusTcpVersion()); addResult(connection, networkDeviceInfo); } }); @@ -183,7 +176,7 @@ void PantaboxDiscovery::addResult(Pantabox *connection, const NetworkDeviceInfo Result result; result.serialNumber = QString::number(connection->serialNumber(), 16).toUpper(); - result.modbusTcpVersion = modbusVersionToString(connection->modbusTcpVersion()); + result.modbusTcpVersion = Pantabox::modbusVersionToString(connection->modbusTcpVersion()); result.networkDeviceInfo = networkDeviceInfo; m_results.append(result); diff --git a/inro/pantaboxdiscovery.h b/inro/pantaboxdiscovery.h index 7732601..6030c0a 100644 --- a/inro/pantaboxdiscovery.h +++ b/inro/pantaboxdiscovery.h @@ -50,8 +50,6 @@ public: QList results() const; - static QString modbusVersionToString(quint32 value); - public slots: void startDiscovery(); From 404124090247ee50fed62aa25278bbd3569463ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 26 Aug 2024 09:56:57 +0200 Subject: [PATCH 07/12] INRO: Fix intial read after connection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Simon Stürz --- inro/integrationplugininro.cpp | 16 ++++++++++++++-- inro/integrationplugininro.h | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index 7dc2d11..daa5c7d 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -277,6 +277,8 @@ void IntegrationPluginInro::thingRemoved(Thing *thing) connection->deleteLater(); } + m_initReadRequired.remove(thing); + // Unregister related hardware resources if (m_monitors.contains(thing)) hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); @@ -325,13 +327,14 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) } }); - connect(connection, &Pantabox::initializationFinished, thing, [thing, connection](bool success){ + connect(connection, &Pantabox::initializationFinished, thing, [this, thing, connection](bool success){ if (success) { thing->setStateValue(pantaboxModbusTcpVersionStateTypeId, Pantabox::modbusVersionToString(connection->modbusTcpVersion())); + m_initReadRequired[thing] = true; } }); - connect(connection, &Pantabox::updateFinished, thing, [thing, connection](){ + connect(connection, &Pantabox::updateFinished, thing, [this, thing, connection](){ qCDebug(dcInro()) << "Update finished for" << thing; qCDebug(dcInro()) << connection; @@ -381,6 +384,15 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) thing->setStateValue(pantaboxFirmwareVersionStateTypeId, connection->firmwareVersion()); thing->setStateValue(pantaboxTotalEnergyConsumedStateTypeId, connection->absoluteEnergy() / 1000.0); // Wh + // Sync states only right after the connection + if (m_initReadRequired.value(thing, false)) { + qCDebug(dcInro()) << "Set initial charging current and charging enabled values."; + m_initReadRequired.remove(thing); + if (connection->maxChargingCurrent() > 0) { + thing->setStateValue(pantaboxMaxChargingCurrentStateTypeId, connection->maxChargingCurrent()); + } + thing->setStateValue(pantaboxPowerStateTypeId, connection->chargingEnabled()); + } }); m_connections.insert(thing, connection); diff --git a/inro/integrationplugininro.h b/inro/integrationplugininro.h index 58aa8e2..17bb5a0 100644 --- a/inro/integrationplugininro.h +++ b/inro/integrationplugininro.h @@ -58,6 +58,8 @@ private: PluginTimer *m_refreshTimer = nullptr; QHash m_connections; QHash m_monitors; + QHash m_initReadRequired; + void setupConnection(ThingSetupInfo *info); }; From dbecb39e0bdfd48fed94e3906d43d611bd5159cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 26 Aug 2024 13:08:30 +0200 Subject: [PATCH 08/12] INRO: Introduce UDP discovery and update connection handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Simon Stürz Signed-off-by: Martin Lukas --- inro/crclookuptable.h | 42 +++++++++ inro/inro.pro | 7 +- inro/integrationplugininro.cpp | 67 ++++---------- inro/integrationplugininro.h | 2 + inro/pantaboxudpdiscovery.cpp | 163 +++++++++++++++++++++++++++++++++ inro/pantaboxudpdiscovery.h | 74 +++++++++++++++ 6 files changed, 303 insertions(+), 52 deletions(-) create mode 100644 inro/crclookuptable.h create mode 100644 inro/pantaboxudpdiscovery.cpp create mode 100644 inro/pantaboxudpdiscovery.h diff --git a/inro/crclookuptable.h b/inro/crclookuptable.h new file mode 100644 index 0000000..1fa34c3 --- /dev/null +++ b/inro/crclookuptable.h @@ -0,0 +1,42 @@ +#ifndef CRCLOOKUPTABLE_H +#define CRCLOOKUPTABLE_H +#include + +//CRC-8/NRSC-5 lookup table + +const QVector crc8LookupTable = { + 0x00, 0x31, 0x62, 0x53, 0xc4, 0xf5, 0xa6, 0x97, + 0xb9, 0x88, 0xdb, 0xea, 0x7d, 0x4c, 0x1f, 0x2e, + 0x43, 0x72, 0x21, 0x10, 0x87, 0xb6, 0xe5, 0xd4, + 0xfa, 0xcb, 0x98, 0xa9, 0x3e, 0x0f, 0x5c, 0x6d, + 0x86, 0xb7, 0xe4, 0xd5, 0x42, 0x73, 0x20, 0x11, + 0x3f, 0x0e, 0x5d, 0x6c, 0xfb, 0xca, 0x99, 0xa8, + 0xc5, 0xf4, 0xa7, 0x96, 0x01, 0x30, 0x63, 0x52, + 0x7c, 0x4d, 0x1e, 0x2f, 0xb8, 0x89, 0xda, 0xeb, + 0x3d, 0x0c, 0x5f, 0x6e, 0xf9, 0xc8, 0x9b, 0xaa, + 0x84, 0xb5, 0xe6, 0xd7, 0x40, 0x71, 0x22, 0x13, + 0x7e, 0x4f, 0x1c, 0x2d, 0xba, 0x8b, 0xd8, 0xe9, + 0xc7, 0xf6, 0xa5, 0x94, 0x03, 0x32, 0x61, 0x50, + 0xbb, 0x8a, 0xd9, 0xe8, 0x7f, 0x4e, 0x1d, 0x2c, + 0x02, 0x33, 0x60, 0x51, 0xc6, 0xf7, 0xa4, 0x95, + 0xf8, 0xc9, 0x9a, 0xab, 0x3c, 0x0d, 0x5e, 0x6f, + 0x41, 0x70, 0x23, 0x12, 0x85, 0xb4, 0xe7, 0xd6, + 0x7a, 0x4b, 0x18, 0x29, 0xbe, 0x8f, 0xdc, 0xed, + 0xc3, 0xf2, 0xa1, 0x90, 0x07, 0x36, 0x65, 0x54, + 0x39, 0x08, 0x5b, 0x6a, 0xfd, 0xcc, 0x9f, 0xae, + 0x80, 0xb1, 0xe2, 0xd3, 0x44, 0x75, 0x26, 0x17, + 0xfc, 0xcd, 0x9e, 0xaf, 0x38, 0x09, 0x5a, 0x6b, + 0x45, 0x74, 0x27, 0x16, 0x81, 0xb0, 0xe3, 0xd2, + 0xbf, 0x8e, 0xdd, 0xec, 0x7b, 0x4a, 0x19, 0x28, + 0x06, 0x37, 0x64, 0x55, 0xc2, 0xf3, 0xa0, 0x91, + 0x47, 0x76, 0x25, 0x14, 0x83, 0xb2, 0xe1, 0xd0, + 0xfe, 0xcf, 0x9c, 0xad, 0x3a, 0x0b, 0x58, 0x69, + 0x04, 0x35, 0x66, 0x57, 0xc0, 0xf1, 0xa2, 0x93, + 0xbd, 0x8c, 0xdf, 0xee, 0x79, 0x48, 0x1b, 0x2a, + 0xc1, 0xf0, 0xa3, 0x92, 0x05, 0x34, 0x67, 0x56, + 0x78, 0x49, 0x1a, 0x2b, 0xbc, 0x8d, 0xde, 0xef, + 0x82, 0xb3, 0xe0, 0xd1, 0x46, 0x77, 0x24, 0x15, + 0x3b, 0x0a, 0x59, 0x68, 0xff, 0xce, 0x9d, 0xac +}; + +#endif // CRCLOOKUPTABLE_H diff --git a/inro/inro.pro b/inro/inro.pro index 18de92a..2bc5f38 100644 --- a/inro/inro.pro +++ b/inro/inro.pro @@ -7,11 +7,14 @@ MODBUS_TOOLS_CONFIG += VERBOSE include(../modbus.pri) HEADERS += \ + crclookuptable.h \ integrationplugininro.h \ pantabox.h \ - pantaboxdiscovery.h + pantaboxdiscovery.h \ + pantaboxudpdiscovery.h SOURCES += \ integrationplugininro.cpp \ pantabox.cpp \ - pantaboxdiscovery.cpp + pantaboxdiscovery.cpp \ + pantaboxudpdiscovery.cpp diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index daa5c7d..44ac121 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -39,6 +39,7 @@ IntegrationPluginInro::IntegrationPluginInro() { + m_updDiscovery = new PantaboxUdpDiscovery(this); } @@ -93,41 +94,16 @@ void IntegrationPluginInro::setupThing(ThingSetupInfo *info) } } - MacAddress macAddress = MacAddress(thing->paramValue(pantaboxThingMacAddressParamTypeId).toString()); - if (!macAddress.isValid()) { - qCWarning(dcInro()) << "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.")); + + QString serialNumber = thing->paramValue(pantaboxThingSerialNumberParamTypeId).toString(); + + if (serialNumber.isEmpty()) { + qCWarning(dcInro()) << "Could not set up PANTABOX because the configured serial number is empty" << thing->params(); + info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The serial number 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(dcInro()) << "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(dcInro()) << "Network device" << thing->name() << "is not reachable yet. Continue with the setup once reachable."; - connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){ - if (reachable) { - qCDebug(dcInro()) << "Network device" << thing->name() << "is now reachable. Continue with the setup..."; - setupConnection(info); - } - }); - } - } else { - setupConnection(info); - } + setupConnection(info); } void IntegrationPluginInro::postSetupThing(Thing *thing) @@ -293,24 +269,21 @@ void IntegrationPluginInro::thingRemoved(Thing *thing) void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) { Thing *thing = info->thing(); - NetworkDeviceMonitor *monitor = m_monitors.value(thing); + // NetworkDeviceMonitor *monitor = m_monitors.value(thing); - Pantabox *connection = new Pantabox(monitor->networkDeviceInfo().address(), 502, 1, this); + Pantabox *connection = new Pantabox(QHostAddress(), 502, 1, this); connect(info, &ThingSetupInfo::aborted, connection, &Pantabox::deleteLater); - // Monitor reachability - connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ - if (!thing->setupComplete()) + connect(m_updDiscovery, &PantaboxUdpDiscovery::pantaboxDiscovered, connection, [connection, thing](const PantaboxUdpDiscovery::PantaboxUdp &pantabox){ + QString serialNumber = thing->paramValue(pantaboxThingSerialNumberParamTypeId).toString(); + if (pantabox.serialNumber != serialNumber) return; - qCDebug(dcInro()) << "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->modbusTcpMaster()->setHostAddress(pantabox.ipAddress); + + if (!thing->stateValue("connected").toBool()) { + qCDebug(dcInro()) << "Received discovery paket for" << thing << "Start connecting to the PANTABOX on" << pantabox.ipAddress.toString(); 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(); } }); @@ -397,10 +370,4 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) m_connections.insert(thing, connection); info->finish(Thing::ThingErrorNoError); - - qCDebug(dcInro()) << "Setting up PANTABOX finished successfully" << monitor->networkDeviceInfo().address().toString(); - - // Connect reight the way if the monitor indicates reachable, otherwise the connect will handle the connect later - if (monitor->reachable()) - connection->connectDevice(); } diff --git a/inro/integrationplugininro.h b/inro/integrationplugininro.h index 17bb5a0..cc49ac0 100644 --- a/inro/integrationplugininro.h +++ b/inro/integrationplugininro.h @@ -37,6 +37,7 @@ #include "extern-plugininfo.h" #include "pantabox.h" +#include "pantaboxudpdiscovery.h" class IntegrationPluginInro: public IntegrationPlugin { @@ -60,6 +61,7 @@ private: QHash m_monitors; QHash m_initReadRequired; + PantaboxUdpDiscovery *m_updDiscovery = nullptr; void setupConnection(ThingSetupInfo *info); }; diff --git a/inro/pantaboxudpdiscovery.cpp b/inro/pantaboxudpdiscovery.cpp new file mode 100644 index 0000000..2ea9415 --- /dev/null +++ b/inro/pantaboxudpdiscovery.cpp @@ -0,0 +1,163 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 "pantaboxudpdiscovery.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include "crclookuptable.h" + +#define PANTABOX_DISCOVERY_PORT 52001 + +PantaboxUdpDiscovery::PantaboxUdpDiscovery(QObject *parent) + : QObject{parent} +{ + m_socket = new QUdpSocket(this); + if (!m_socket->bind(QHostAddress::Broadcast, PANTABOX_DISCOVERY_PORT, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { + qCWarning(dcInro()) << "UdpDiscovery: Failed to bind to UDP broadcast on" << PANTABOX_DISCOVERY_PORT << m_socket->errorString(); + return; + } + + connect(m_socket, &QUdpSocket::readyRead, this, &PantaboxUdpDiscovery::readPendingDatagrams); + + m_available = true; + +} + +QHash PantaboxUdpDiscovery::results() const +{ + return m_results; +} + +void PantaboxUdpDiscovery::readPendingDatagrams() +{ + while(m_socket->hasPendingDatagrams()) { + + QNetworkDatagram datagram = m_socket->receiveDatagram(); + + for (int i = 0; i < datagram.data().length(); i++) { + + quint8 dataByte = static_cast(datagram.data().at(i)); + + if (!m_prefixStartDiscovered[datagram.senderAddress()] && dataByte == 0xe5) { + m_prefixStartDiscovered[datagram.senderAddress()] = true; + continue; + } + + if (m_prefixStartDiscovered[datagram.senderAddress()] && dataByte == 0x00) { + m_prefixStartDiscovered[datagram.senderAddress()] = false; + + // Paket prefix discovered (0xe5 0x00), process current buffer and start collecting data + processDataBuffer(datagram.senderAddress()); + m_buffers[datagram.senderAddress()].clear(); + continue; + } else { + m_prefixStartDiscovered[datagram.senderAddress()] = false; + } + + // Adding data byte + m_buffers[datagram.senderAddress()].append(dataByte); + + if (m_buffers[datagram.senderAddress()].length() >= 0xffff) { + qCWarning(dcInro()) << "UdpDiscovery: Buffer overflow. Wipe data buffer..."; + m_buffers[datagram.senderAddress()].clear(); + } + } + } +} + +quint8 PantaboxUdpDiscovery::calculateCrc8(const QByteArray &data) +{ + // CRC-8/NRSC-5 initial value + quint8 crc = 0xFF; + + for (quint8 byte : data) { + crc = crc8LookupTable[crc ^ byte]; + } + + return crc; +} + +void PantaboxUdpDiscovery::processDataBuffer(const QHostAddress &address) +{ + if (m_buffers[address].length() < 3) + return; + + quint8 receivedCrc = static_cast(m_buffers[address].at(m_buffers[address].length() - 1)); + quint8 calculatedCrc = calculateCrc8(QByteArray::fromHex("e500").append(m_buffers[address].left(m_buffers[address].length() - 1))); + + if (calculatedCrc != receivedCrc) { + qCDebug(dcInro()) << "UdpDiscovery: Crc checksum not correct. Received crc "<< receivedCrc <<", calculated crc " << calculatedCrc ; + return; + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(m_buffers[address].mid(2, m_buffers[address].length() - 3), &jsonError); + + if (jsonError.error != QJsonParseError::NoError) { + qCDebug(dcInro()) << "UdpDiscovery: Received invalud json data" << jsonError.errorString(); + return; + } + + //qCDebug(dcInro()) << "UdpDiscovery:" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); + /* + { + "deviceId": "e45749d4-8c05-44b2-9dbc-xxxxxxxxxxxx", + "encryptBLE": 1, + "env": "live", + "fwVersion": "V1.19.6", + "ipAddress": "10.10.10.111", + "macAddress": "8C:4B:14:88:05:00", + "name": "#1XXXXXXX", + "productKey": "inro-test-1", + "serialNumber": "#1XXXXXXX", + "useTLS": 1 + } + */ + + QVariantMap dataMap = jsonDoc.toVariant().toMap(); + if (dataMap.contains("serialNumber") && dataMap.contains("ipAddress") && dataMap.contains("macAddress")) { + PantaboxUdp pantabox; + pantabox.serialNumber = dataMap.value("serialNumber").toString().remove("#"); + pantabox.macAddress = MacAddress(dataMap.value("macAddress").toString()); + pantabox.ipAddress = QHostAddress(dataMap.value("ipAddress").toString()); + + if (address != pantabox.ipAddress) { + qCDebug(dcInro()) << "UdpDiscovery: Received UPD discovery paket from a different IP than communicated in the paket. Ignoring paket."; + return; + } + + qCDebug(dcInro()) << "UdpDiscovery: --> Received discovery paket from" << pantabox.serialNumber << pantabox.macAddress.toString() << pantabox.ipAddress.toString(); + m_results[pantabox.serialNumber] = pantabox; + emit pantaboxDiscovered(pantabox); + } +} diff --git a/inro/pantaboxudpdiscovery.h b/inro/pantaboxudpdiscovery.h new file mode 100644 index 0000000..fd61225 --- /dev/null +++ b/inro/pantaboxudpdiscovery.h @@ -0,0 +1,74 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 PANTABOXUDPDISCOVERY_H +#define PANTABOXUDPDISCOVERY_H + +#include +#include + +#include + +class PantaboxUdpDiscovery : public QObject +{ + Q_OBJECT +public: + explicit PantaboxUdpDiscovery(QObject *parent = nullptr); + + typedef struct PantaboxUdp { + QString serialNumber; + MacAddress macAddress; + QHostAddress ipAddress; + } PantaboxUdp; + + bool available() const; + + QHash results() const; + +signals: + void pantaboxDiscovered(const PantaboxUdp &pantabox); + +private slots: + void readPendingDatagrams(); + +private: + QUdpSocket *m_socket = nullptr; + bool m_available = false; + + QHash m_buffers; + QHash m_prefixStartDiscovered; + + quint8 calculateCrc8(const QByteArray &data); + void processDataBuffer(const QHostAddress &address); + + QHash m_results; +}; + +#endif // PANTABOXUDPDISCOVERY_H From 0b82b055663e324cc3efbddeea6dc0e06a95ba09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 27 Aug 2024 16:57:12 +0200 Subject: [PATCH 09/12] Fix CRC-8 lookup table data type --- inro/crclookuptable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inro/crclookuptable.h b/inro/crclookuptable.h index 1fa34c3..019597f 100644 --- a/inro/crclookuptable.h +++ b/inro/crclookuptable.h @@ -4,7 +4,7 @@ //CRC-8/NRSC-5 lookup table -const QVector crc8LookupTable = { +static const quint8 crc8LookupTable[256] = { 0x00, 0x31, 0x62, 0x53, 0xc4, 0xf5, 0xa6, 0x97, 0xb9, 0x88, 0xdb, 0xea, 0x7d, 0x4c, 0x1f, 0x2e, 0x43, 0x72, 0x21, 0x10, 0x87, 0xb6, 0xe5, 0xd4, From c61d0fc6cd5a4cbd7f1608c4caf41972e2309ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 29 Aug 2024 10:56:42 +0200 Subject: [PATCH 10/12] INRO: Replace network device discovery with UDP based discovery mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Simon Stürz --- inro/integrationplugininro.cpp | 55 ++++++++++++-------------- inro/integrationplugininro.h | 3 +- inro/pantaboxdiscovery.cpp | 72 ++++++++++++++++++++-------------- inro/pantaboxdiscovery.h | 17 ++++---- inro/pantaboxudpdiscovery.cpp | 12 ++++-- inro/pantaboxudpdiscovery.h | 10 ++--- 6 files changed, 89 insertions(+), 80 deletions(-) diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index 44ac121..8d8ad6f 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -31,7 +31,6 @@ #include "integrationplugininro.h" #include "plugininfo.h" -#include #include #include @@ -39,37 +38,29 @@ IntegrationPluginInro::IntegrationPluginInro() { - m_updDiscovery = new PantaboxUdpDiscovery(this); } void IntegrationPluginInro::discoverThings(ThingDiscoveryInfo *info) { - - if (!hardwareManager()->networkDeviceDiscovery()->available()) { - qCWarning(dcInro()) << "The network discovery is not available on this platform."; - info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available.")); - return; - } - - PantaboxDiscovery *discovery = new PantaboxDiscovery(hardwareManager()->networkDeviceDiscovery(), info); + PantaboxDiscovery *discovery = new PantaboxDiscovery(info); connect(discovery, &PantaboxDiscovery::discoveryFinished, info, [this, info, discovery](){ foreach (const PantaboxDiscovery::Result &result, discovery->results()) { - QString title = QString("PANTABOX - %1").arg(result.serialNumber); - QString description = QString("%1 (%2)").arg(result.networkDeviceInfo.macAddress(), result.networkDeviceInfo.address().toString()); + QString title = QString("PANTABOX - %1").arg(result.deviceInfo.serialNumber); + QString description = QString("%1 (%2)").arg(result.deviceInfo.macAddress.toString(), result.deviceInfo.ipAddress.toString()); ThingDescriptor descriptor(pantaboxThingClassId, title, description); // Check if we already have set up this device - Things existingThings = myThings().filterByParam(pantaboxThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress()); + Things existingThings = myThings().filterByParam(pantaboxThingSerialNumberParamTypeId, result.deviceInfo.serialNumber); if (existingThings.count() == 1) { - qCDebug(dcInro()) << "This PANTABOX already exists in the system:" << result.networkDeviceInfo; + qCDebug(dcInro()) << "This PANTABOX already exists in the system:" << result.deviceInfo.serialNumber << result.deviceInfo.ipAddress.toString(); descriptor.setThingId(existingThings.first()->id()); } ParamList params; - params << Param(pantaboxThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress()); - params << Param(pantaboxThingSerialNumberParamTypeId, result.serialNumber); + params << Param(pantaboxThingMacAddressParamTypeId, result.deviceInfo.macAddress.toString()); + params << Param(pantaboxThingSerialNumberParamTypeId, result.deviceInfo.serialNumber); descriptor.setParams(params); info->addThingDescriptor(descriptor); } @@ -87,14 +78,12 @@ void IntegrationPluginInro::setupThing(ThingSetupInfo *info) if (m_connections.contains(thing)) { qCDebug(dcInro()) << "Reconfiguring existing thing" << thing->name(); - m_connections.take(thing)->deleteLater(); - - if (m_monitors.contains(thing)) { - hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - } + Pantabox *connection = m_connections.take(thing); + connection->modbusTcpMaster()->disconnectDevice(); + connection->deleteLater(); + thing->setStateValue(pantaboxConnectedStateTypeId, false); } - QString serialNumber = thing->paramValue(pantaboxThingSerialNumberParamTypeId).toString(); if (serialNumber.isEmpty()) { @@ -255,34 +244,38 @@ void IntegrationPluginInro::thingRemoved(Thing *thing) m_initReadRequired.remove(thing); - // Unregister related hardware resources - if (m_monitors.contains(thing)) - hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - if (myThings().isEmpty() && m_refreshTimer) { qCDebug(dcInro()) << "Stopping reconnect timer"; hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); m_refreshTimer = nullptr; } + + if (myThings().isEmpty() && m_udpDiscovery) { + qCDebug(dcInro()) << "Destroy UDP discovery since not needed any more"; + m_udpDiscovery->deleteLater(); + m_udpDiscovery = nullptr; + } } void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) { + if (!m_udpDiscovery) + m_udpDiscovery = new PantaboxUdpDiscovery(this); + Thing *thing = info->thing(); - // NetworkDeviceMonitor *monitor = m_monitors.value(thing); Pantabox *connection = new Pantabox(QHostAddress(), 502, 1, this); connect(info, &ThingSetupInfo::aborted, connection, &Pantabox::deleteLater); - connect(m_updDiscovery, &PantaboxUdpDiscovery::pantaboxDiscovered, connection, [connection, thing](const PantaboxUdpDiscovery::PantaboxUdp &pantabox){ + connect(m_udpDiscovery, &PantaboxUdpDiscovery::pantaboxDiscovered, connection, [connection, thing](const PantaboxUdpDiscovery::DeviceInfo &deviceInfo){ QString serialNumber = thing->paramValue(pantaboxThingSerialNumberParamTypeId).toString(); - if (pantabox.serialNumber != serialNumber) + if (deviceInfo.serialNumber != serialNumber) return; - connection->modbusTcpMaster()->setHostAddress(pantabox.ipAddress); + connection->modbusTcpMaster()->setHostAddress(deviceInfo.ipAddress); if (!thing->stateValue("connected").toBool()) { - qCDebug(dcInro()) << "Received discovery paket for" << thing << "Start connecting to the PANTABOX on" << pantabox.ipAddress.toString(); + qCDebug(dcInro()) << "Received discovery paket for" << thing << "Start connecting to the PANTABOX on" << deviceInfo.ipAddress.toString(); connection->connectDevice(); } }); diff --git a/inro/integrationplugininro.h b/inro/integrationplugininro.h index cc49ac0..36e5ac0 100644 --- a/inro/integrationplugininro.h +++ b/inro/integrationplugininro.h @@ -58,10 +58,9 @@ public: private: PluginTimer *m_refreshTimer = nullptr; QHash m_connections; - QHash m_monitors; QHash m_initReadRequired; - PantaboxUdpDiscovery *m_updDiscovery = nullptr; + PantaboxUdpDiscovery *m_udpDiscovery = nullptr; void setupConnection(ThingSetupInfo *info); }; diff --git a/inro/pantaboxdiscovery.cpp b/inro/pantaboxdiscovery.cpp index 66ec346..0ccbf50 100644 --- a/inro/pantaboxdiscovery.cpp +++ b/inro/pantaboxdiscovery.cpp @@ -31,9 +31,8 @@ #include "pantaboxdiscovery.h" #include "extern-plugininfo.h" -PantaboxDiscovery::PantaboxDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) - : QObject{parent}, - m_networkDeviceDiscovery{networkDeviceDiscovery} +PantaboxDiscovery::PantaboxDiscovery(QObject *parent) + : QObject{parent} { } @@ -48,21 +47,22 @@ void PantaboxDiscovery::startDiscovery() qCInfo(dcInro()) << "Discovery: Start searching for PANTABOX wallboxes in the network..."; m_startDateTime = QDateTime::currentDateTime(); - NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover(); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &PantaboxDiscovery::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(dcInro()) << "Discovery: Grace period timer triggered."; - finishDiscovery(); - }); - }); + m_discovery = new PantaboxUdpDiscovery(this); + connect(m_discovery, &PantaboxUdpDiscovery::pantaboxDiscovered, this, &PantaboxDiscovery::checkNetworkDevice); + + connect(&m_discoveryTimer, &QTimer::timeout, this, &PantaboxDiscovery::finishDiscovery); + m_discoveryTimer.setSingleShot(true); + m_discoveryTimer.start(10000); } -void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) +void PantaboxDiscovery::checkNetworkDevice(const PantaboxUdpDiscovery::DeviceInfo &deviceInfo) { - Pantabox *connection = new Pantabox(networkDeviceInfo.address(), m_port, m_modbusAddress, this); + if (m_alreadyCheckedHosts.contains(deviceInfo.ipAddress)) + return; + + m_alreadyCheckedHosts.append(deviceInfo.ipAddress); + + Pantabox *connection = new Pantabox(deviceInfo.ipAddress, m_port, m_modbusAddress, this); m_connections.append(connection); connect(connection, &Pantabox::reachableChanged, this, [=](bool reachable){ @@ -75,7 +75,7 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic // Modbus TCP connected...ok, let's try to initialize it! connect(connection, &Pantabox::initializationFinished, this, [=](bool success){ if (!success) { - qCDebug(dcInro()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue..."; + qCDebug(dcInro()) << "Discovery: Initialization failed on" << deviceInfo.ipAddress.toString() << "Continue..."; cleanupConnection(connection); return; } @@ -96,9 +96,9 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic } connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply, connection, networkDeviceInfo](){ + connect(reply, &QModbusReply::finished, this, [this, reply, connection, deviceInfo](){ if (reply->error() != QModbusDevice::NoError) { - qCDebug(dcInro()) << "Discovery: Error reading product name error on" << networkDeviceInfo.address().toString() << "Continue..."; + qCDebug(dcInro()) << "Discovery: Error reading product name error on" << deviceInfo.ipAddress.toString() << "Continue..."; cleanupConnection(connection); return; } @@ -107,10 +107,10 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic if (unit.values().size() == 4) { QString receivedProductName = ModbusDataUtils::convertToString(unit.values(), connection->stringEndianness()); if (receivedProductName.toUpper().contains("PANTABOX")) { - addResult(connection, networkDeviceInfo); + addResult(connection, deviceInfo); } else { qCDebug(dcInro()) << "Discovery: Invalid product name " << receivedProductName - << "on" << networkDeviceInfo.address().toString() << "Continue..."; + << "on" << deviceInfo.ipAddress.toString() << "Continue..."; cleanupConnection(connection); } } else { @@ -121,13 +121,13 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic } else { qCDebug(dcInro()) << "Discovery: Adding connection to results even tough the result is not precise due to modbus TCP protocol version:" << connection->modbusTcpVersion() << Pantabox::modbusVersionToString(connection->modbusTcpVersion()); - addResult(connection, networkDeviceInfo); + addResult(connection, deviceInfo); } }); // Initializing... if (!connection->initialize()) { - qCDebug(dcInro()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue..."; + qCDebug(dcInro()) << "Discovery: Unable to initialize connection on" << deviceInfo.ipAddress.toString() << "Continue..."; cleanupConnection(connection); } }); @@ -135,14 +135,14 @@ void PantaboxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevic // If we get any error...skip this host... connect(connection->modbusTcpMaster(), &ModbusTcpMaster::connectionErrorOccurred, this, [=](QModbusDevice::Error error){ if (error != QModbusDevice::NoError) { - qCDebug(dcInro()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue..."; + qCDebug(dcInro()) << "Discovery: Connection error on" << deviceInfo.ipAddress.toString() << "Continue..."; cleanupConnection(connection); } }); // If check reachability failed...skip this host... connect(connection, &Pantabox::checkReachabilityFailed, this, [=](){ - qCDebug(dcInro()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue..."; + qCDebug(dcInro()) << "Discovery: Check reachability failed on" << deviceInfo.ipAddress.toString() << "Continue..."; cleanupConnection(connection); }); @@ -161,30 +161,42 @@ void PantaboxDiscovery::finishDiscovery() { qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch(); + m_discovery->deleteLater(); + m_discovery = nullptr; + + m_alreadyCheckedHosts.clear(); + // Cleanup any leftovers...we don't care any more foreach (Pantabox *connection, m_connections) cleanupConnection(connection); qCInfo(dcInro()) << "Discovery: Finished the discovery process. Found" << m_results.count() << "PANTABOXE wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); + emit discoveryFinished(); } -void PantaboxDiscovery::addResult(Pantabox *connection, const NetworkDeviceInfo &networkDeviceInfo) +void PantaboxDiscovery::addResult(Pantabox *connection, const PantaboxUdpDiscovery::DeviceInfo &deviceInfo) { - qCDebug(dcInro()) << "Discovery: Connection initialized successfully" << connection->serialNumber(); + QString modbusSerialNumber = QString::number(connection->serialNumber(), 16).toUpper(); + if (deviceInfo.serialNumber != modbusSerialNumber) { + qCWarning(dcInro()) << "Discovery: Successfully discovered PANTABOX, but the UPD serial number does not match the fetched modbus serial number. Ignoring result..."; + cleanupConnection(connection); + return; + } + + qCDebug(dcInro()) << "Discovery: Connection initialized successfully" << modbusSerialNumber; Result result; - result.serialNumber = QString::number(connection->serialNumber(), 16).toUpper(); result.modbusTcpVersion = Pantabox::modbusVersionToString(connection->modbusTcpVersion()); - result.networkDeviceInfo = networkDeviceInfo; + result.deviceInfo = deviceInfo; m_results.append(result); qCInfo(dcInro()) << "Discovery: --> Found" - << "Serial number:" << result.serialNumber + << "Serial number:" << result.deviceInfo.serialNumber << "(" << connection->serialNumber() << ")" << "ModbusTCP version:" << result.modbusTcpVersion - << result.networkDeviceInfo; + << "on" << result.deviceInfo.ipAddress.toString() << result.deviceInfo.macAddress.toString(); // Done with this connection cleanupConnection(connection); diff --git a/inro/pantaboxdiscovery.h b/inro/pantaboxdiscovery.h index 6030c0a..9210fdd 100644 --- a/inro/pantaboxdiscovery.h +++ b/inro/pantaboxdiscovery.h @@ -31,21 +31,21 @@ #ifndef PANTABOXDISCOVERY_H #define PANTABOXDISCOVERY_H +#include #include -#include #include "pantabox.h" +#include "pantaboxudpdiscovery.h" class PantaboxDiscovery : public QObject { Q_OBJECT public: - explicit PantaboxDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr); + explicit PantaboxDiscovery(QObject *parent = nullptr); typedef struct Result { - QString serialNumber; + PantaboxUdpDiscovery::DeviceInfo deviceInfo; QString modbusTcpVersion; - NetworkDeviceInfo networkDeviceInfo; } Result; QList results() const; @@ -57,21 +57,22 @@ signals: void discoveryFinished(); private: - NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; + PantaboxUdpDiscovery *m_discovery = nullptr; quint16 m_port = 502; quint16 m_modbusAddress = 1; - QDateTime m_startDateTime; + QTimer m_discoveryTimer; QList m_connections; + QList m_alreadyCheckedHosts; QList m_results; - void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); + void checkNetworkDevice(const PantaboxUdpDiscovery::DeviceInfo &deviceInfo); void cleanupConnection(Pantabox *connection); void finishDiscovery(); - void addResult(Pantabox *connection, const NetworkDeviceInfo &networkDeviceInfo); + void addResult(Pantabox *connection, const PantaboxUdpDiscovery::DeviceInfo &deviceInfo); }; #endif // PANTABOXDISCOVERY_H diff --git a/inro/pantaboxudpdiscovery.cpp b/inro/pantaboxudpdiscovery.cpp index 2ea9415..f1a5973 100644 --- a/inro/pantaboxudpdiscovery.cpp +++ b/inro/pantaboxudpdiscovery.cpp @@ -48,12 +48,15 @@ PantaboxUdpDiscovery::PantaboxUdpDiscovery(QObject *parent) } connect(m_socket, &QUdpSocket::readyRead, this, &PantaboxUdpDiscovery::readPendingDatagrams); - m_available = true; - } -QHash PantaboxUdpDiscovery::results() const +bool PantaboxUdpDiscovery::available() const +{ + return m_available; +} + +QHash PantaboxUdpDiscovery::results() const { return m_results; } @@ -129,6 +132,7 @@ void PantaboxUdpDiscovery::processDataBuffer(const QHostAddress &address) } //qCDebug(dcInro()) << "UdpDiscovery:" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); + /* { "deviceId": "e45749d4-8c05-44b2-9dbc-xxxxxxxxxxxx", @@ -146,7 +150,7 @@ void PantaboxUdpDiscovery::processDataBuffer(const QHostAddress &address) QVariantMap dataMap = jsonDoc.toVariant().toMap(); if (dataMap.contains("serialNumber") && dataMap.contains("ipAddress") && dataMap.contains("macAddress")) { - PantaboxUdp pantabox; + DeviceInfo pantabox; pantabox.serialNumber = dataMap.value("serialNumber").toString().remove("#"); pantabox.macAddress = MacAddress(dataMap.value("macAddress").toString()); pantabox.ipAddress = QHostAddress(dataMap.value("ipAddress").toString()); diff --git a/inro/pantaboxudpdiscovery.h b/inro/pantaboxudpdiscovery.h index fd61225..a7c979f 100644 --- a/inro/pantaboxudpdiscovery.h +++ b/inro/pantaboxudpdiscovery.h @@ -42,18 +42,18 @@ class PantaboxUdpDiscovery : public QObject public: explicit PantaboxUdpDiscovery(QObject *parent = nullptr); - typedef struct PantaboxUdp { + typedef struct DeviceInfo { QString serialNumber; MacAddress macAddress; QHostAddress ipAddress; - } PantaboxUdp; + } DeviceInfo; bool available() const; - QHash results() const; + QHash results() const; signals: - void pantaboxDiscovered(const PantaboxUdp &pantabox); + void pantaboxDiscovered(const PantaboxUdpDiscovery::DeviceInfo &deviceInfo); private slots: void readPendingDatagrams(); @@ -68,7 +68,7 @@ private: quint8 calculateCrc8(const QByteArray &data); void processDataBuffer(const QHostAddress &address); - QHash m_results; + QHash m_results; }; #endif // PANTABOXUDPDISCOVERY_H From e5b48560727eef1b85713fef3eb003b1ae64b9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 10 Sep 2024 10:47:48 +0200 Subject: [PATCH 11/12] INRO: Update reconnection logic if modbus timeouts but TCP is still connected --- inro/integrationplugininro.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index 8d8ad6f..15f677d 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -274,9 +274,19 @@ void IntegrationPluginInro::setupConnection(ThingSetupInfo *info) connection->modbusTcpMaster()->setHostAddress(deviceInfo.ipAddress); - if (!thing->stateValue("connected").toBool()) { - qCDebug(dcInro()) << "Received discovery paket for" << thing << "Start connecting to the PANTABOX on" << deviceInfo.ipAddress.toString(); - connection->connectDevice(); + if (!connection->reachable()) { + + if (connection->modbusTcpMaster()->connected()) { + qCDebug(dcInro()) << "Received discovery paket for" << thing->name() << + "which is not reachable but the TCP socket is still connected. Reconnecting the TCP socket on" << + deviceInfo.ipAddress.toString(); + connection->modbusTcpMaster()->reconnectDevice(); + } else { + qCDebug(dcInro()) << "Received discovery paket for" << thing->name() << + "which is not reachable and not connected. Start connecting to the PANTABOX on" << + deviceInfo.ipAddress.toString(); + connection->connectDevice(); + } } }); From 0a114e47ee1bd90d073449d5ecd59ac0ef093999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 25 Sep 2024 16:00:27 +0200 Subject: [PATCH 12/12] INRO: Make sure charging is enabled if session continues with play --- inro/integrationplugininro.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/inro/integrationplugininro.cpp b/inro/integrationplugininro.cpp index 15f677d..64fe27f 100644 --- a/inro/integrationplugininro.cpp +++ b/inro/integrationplugininro.cpp @@ -128,8 +128,8 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) if (info->action().actionTypeId() == pantaboxPowerActionTypeId) { bool power = info->action().paramValue(pantaboxPowerActionPowerParamTypeId).toBool(); - // Play/pause charging session feature is available from Modbus Tcp version 1.1 - if (connection->modbusTcpVersion() <= 65536) { + // Play/pause charging session feature is available since Modbus Tcp version 1.2 (0x0001 0x0002) 0x10002 = 65538 + if (connection->modbusTcpVersion() < 65538) { // When power is set by user, charging is going to stop or start depending on setting qCDebug(dcInro()) << "Set power by user" << (power ? 1 : 0); QModbusReply *reply = connection->setChargingEnabled(power ? 1 : 0); @@ -152,7 +152,11 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) } }); return; + } else { + + // Modbus version >= 1.2 + if (info->action().triggeredBy() == Action::TriggeredByUser) { // When power is set by user, charging is going to stop or start depending on setting @@ -177,6 +181,7 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) } }); return; + } else { // When power is set to 0 by automatisnm, max charging current is set to 0 otherwise take the configured max charging current qCDebug(dcInro()) << "Going to play/pause charging session"; @@ -191,15 +196,27 @@ void IntegrationPluginInro::executeAction(ThingActionInfo *info) } connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, info, [info, reply, power](){ + connect(reply, &QModbusReply::finished, info, [info, reply, power, connection](){ if (reply->error() == QModbusDevice::NoError) { qCDebug(dcInro()) << (power ? "Play" : "Pause") << "session by energy manager"; info->finish(Thing::ThingErrorNoError); + if (power) { + // Make sure the charging is enabled, just in case + QModbusReply *reply = connection->setChargingEnabled(1); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, info, [reply](){ + if (reply->error() != QModbusDevice::NoError) { + qCWarning(dcInro()) << "Error setting charging enabled:" << reply->error() << reply->errorString(); + } + }); + } } else { qCWarning(dcInro()) << "Error setting charging current:" << reply->error() << reply->errorString(); info->finish(Thing::ThingErrorHardwareFailure); } + }); + return; } }