From 5d15fc9e1685789daec7354fb1b4a841bc9c289b Mon Sep 17 00:00:00 2001 From: Martin Lukas Date: Tue, 6 Aug 2024 07:09:22 +0200 Subject: [PATCH] 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(); };