From 87ed98b72f62040f02d6c0b859eaf93f789ce9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 26 Jul 2024 15:32:37 +0200 Subject: [PATCH] modbus RTU: add platform configuration --- .../hardware/modbus/modbusrtumanager.cpp | 151 +++++++++++++++--- .../hardware/modbus/modbusrtumanager.h | 29 +++- .../hardware/serialport/serialportmonitor.h | 18 ++- libnymea-core/jsonrpc/modbusrtuhandler.cpp | 8 +- 4 files changed, 176 insertions(+), 30 deletions(-) diff --git a/libnymea-core/hardware/modbus/modbusrtumanager.cpp b/libnymea-core/hardware/modbus/modbusrtumanager.cpp index d2cdafa4..d031e687 100644 --- a/libnymea-core/hardware/modbus/modbusrtumanager.cpp +++ b/libnymea-core/hardware/modbus/modbusrtumanager.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -34,6 +34,12 @@ #include "modbusrtumasterimpl.h" #include "hardware/serialport/serialportmonitor.h" +#include "qglobal.h" + +#include +#include +#include +#include NYMEA_LOGGING_CATEGORY(dcModbusRtu, "ModbusRtu") @@ -43,40 +49,91 @@ ModbusRtuManager::ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject QObject(parent), m_serialPortMonitor(serialPortMonitor) { + if (!supported()) + return; + + // Load the platform config + loadPlatformConfiguration(); + // Load uart configurations loadRtuMasters(); - connect(m_serialPortMonitor, &SerialPortMonitor::serialPortAdded, this, [=](const QSerialPortInfo &serialPortInfo){ - qCDebug(dcModbusRtu()) << "Serial port added. Verify modbus RTU masters..."; + // Initialize the list of serial ports already available in the system... + foreach (const SerialPort &serialPort, m_serialPortMonitor->serialPorts()) { + onSerialPortAdded(serialPort); + } - // Check if we have to reconnect any modbus RTU masters - foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) { - ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast(modbusMaster); + connect(m_serialPortMonitor, &SerialPortMonitor::serialPortAdded, this, &ModbusRtuManager::onSerialPortAdded); + connect(m_serialPortMonitor, &SerialPortMonitor::serialPortRemoved, this, &ModbusRtuManager::onSerialPortRemoved); - // Try only to reconnect if the added serial port matches a disconnected modbus RTU master - if (!modbusMasterImpl->connected() && modbusMasterImpl->serialPort() == serialPortInfo.systemLocation()) { - if (!modbusMasterImpl->connectDevice()) { - qCDebug(dcModbusRtu()) << "Reconnect" << modbusMaster << "failed."; - } else { - qCDebug(dcModbusRtu()) << "Reconnected" << modbusMaster << "successfully."; - } - } - } - }); - - // Try to connect the modbus rtu masters + // Try to connect the modbus RTU masters foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) { ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast(modbusMaster); if (!modbusMasterImpl->connectDevice()) { qCWarning(dcModbusRtu()) << "Failed to connect modbus RTU master. Could not connect to" << modbusMaster; } } - } -SerialPortMonitor *ModbusRtuManager::serialPortMonitor() const +SerialPorts ModbusRtuManager::serialPorts() const { - return m_serialPortMonitor; + return m_serialPorts.values(); +} + +bool ModbusRtuManager::serialPortAvailable(const QString &systemLocation) const +{ + return m_serialPorts.keys().contains(systemLocation); +} + +void ModbusRtuManager::onSerialPortAdded(const SerialPort &serialPort) +{ + if (m_serialPorts.contains(serialPort.systemLocation())) + return; + + // Depending on the platform configuration we have to filter out those serial ports which are not suitable for modbus RTU communication. + foreach (const ModbusRtuPlatformConfiguration &platformConfig, m_platformConfigurations) { + if (platformConfig.serialPort == serialPort.systemLocation()) { + if (platformConfig.usable) { + SerialPort internalPort = serialPort; + internalPort.setCustomDescription(platformConfig.description); + m_serialPorts.insert(internalPort.systemLocation(), internalPort); + emit serialPortAdded(internalPort); + } else { + qCDebug(dcModbusRtu()) << "Serial port" << serialPort.systemLocation() << "added but not usable for modbus RTU according to the platorm configuration."; + return; + } + } + } + + if (!m_serialPorts.contains(serialPort.systemLocation())) { + m_serialPorts.insert(serialPort.systemLocation(), serialPort); + emit serialPortAdded(serialPort); + } + + qCDebug(dcModbusRtu()) << "Serial port" << serialPort.systemLocation() << "added. Evaluate modbus RTU masters..."; + + // Check if we have to reconnect any modbus RTU masters + foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) { + ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast(modbusMaster); + + // Try only to reconnect if the added serial port matches a disconnected modbus RTU master + if (!modbusMasterImpl->connected() && modbusMasterImpl->serialPort() == serialPort.systemLocation()) { + if (!modbusMasterImpl->connectDevice()) { + qCDebug(dcModbusRtu()) << "Reconnect" << modbusMaster << "failed."; + } else { + qCDebug(dcModbusRtu()) << "Reconnected" << modbusMaster << "successfully."; + } + } + } +} + +void ModbusRtuManager::onSerialPortRemoved(const SerialPort &serialPort) +{ + if (m_serialPorts.contains(serialPort.systemLocation())) { + qCDebug(dcModbusRtu()) << "Serial port removed" << serialPort.systemLocation(); + m_serialPorts.remove(serialPort.systemLocation()); + emit serialPortRemoved(serialPort); + } } bool ModbusRtuManager::supported() const @@ -211,6 +268,58 @@ ModbusRtuManager::ModbusRtuError ModbusRtuManager::removeModbusRtuMaster(const Q return ModbusRtuErrorNoError; } +void ModbusRtuManager::loadPlatformConfiguration() +{ + QFileInfo platformConfigurationFileInfo(NymeaSettings::settingsPath() + QDir::separator() + "modbus-rtu-platform.conf"); + + if (platformConfigurationFileInfo.exists()) { + qCDebug(dcModbusRtu()) << "Loading modbus RTU platform configuration from" << platformConfigurationFileInfo.absoluteFilePath(); + QFile platformConfigurationFile(platformConfigurationFileInfo.absoluteFilePath()); + if (!platformConfigurationFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCWarning(dcModbusRtu()) << "Failed to open modbus RTU platform configuration file" + << platformConfigurationFileInfo.absoluteFilePath() << ":" + << platformConfigurationFile.errorString(); + return; + } + + QByteArray data = platformConfigurationFile.readAll(); + platformConfigurationFile.close(); + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) { + qCWarning(dcModbusRtu()) << "Failed to parse JSON data from modbus RTU platform configuration file" + << platformConfigurationFileInfo.absoluteFilePath() << ":" + << jsonError.errorString(); + return; + } + + // Make sure we have a clean start + m_platformConfigurations.clear(); + + QVariantMap platformConfigMap = jsonDoc.toVariant().toMap(); + foreach (const QVariant &configVariant, platformConfigMap.value("interfaces").toList()) { + QVariantMap configMap = configVariant.toMap(); + ModbusRtuPlatformConfiguration config; + config.name = configMap.value("name").toString(); + config.description = configMap.value("description").toString(); + config.serialPort = configMap.value("serialPort").toString(); + config.usable = configMap.value("usable").toBool(); + m_platformConfigurations.append(config); + } + + if (m_platformConfigurations.isEmpty()) { + qCDebug(dcModbusRtu()) << "No platform configurations available"; + } else { + qCDebug(dcModbusRtu()) << "Loaded successfully" << m_platformConfigurations.count() << "platform configurations"; + foreach(const ModbusRtuPlatformConfiguration &config, m_platformConfigurations) { + qCDebug(dcModbusRtu()).nospace() << "- Platform configuration: " << config.description << " (" << config.name << ") " + << "Serial port: " << config.serialPort << " usable: " << config.usable; + } + } + } +} + void ModbusRtuManager::loadRtuMasters() { if (!supported()) diff --git a/libnymea-core/hardware/modbus/modbusrtumanager.h b/libnymea-core/hardware/modbus/modbusrtumanager.h index 837188c6..e33d47db 100644 --- a/libnymea-core/hardware/modbus/modbusrtumanager.h +++ b/libnymea-core/hardware/modbus/modbusrtumanager.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -37,10 +37,10 @@ #include #include "hardware/modbus/modbusrtumaster.h" +#include "hardware/serialport/serialportmonitor.h" namespace nymeaserver { -class SerialPortMonitor; class ModbusRtuMasterImpl; class ModbusRtuManager : public QObject @@ -62,8 +62,6 @@ public: explicit ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject *parent = nullptr); ~ModbusRtuManager() = default; - SerialPortMonitor *serialPortMonitor() const; - bool supported() const; QList modbusRtuMasters() const; @@ -74,14 +72,37 @@ public: ModbusRtuError reconfigureModbusRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, int numberOfRetries, int timeout); ModbusRtuError removeModbusRtuMaster(const QUuid &modbusUuid); + // Returns the platform specific list of available serial ports for modbus RTU + SerialPorts serialPorts() const; + bool serialPortAvailable(const QString &systemLocation) const; + signals: + void serialPortAdded(const SerialPort &serialPort); + void serialPortRemoved(const SerialPort &serialPort); + void modbusRtuMasterAdded(ModbusRtuMaster *modbusRtuMaster); void modbusRtuMasterRemoved(ModbusRtuMaster *modbusRtuMaster); void modbusRtuMasterChanged(ModbusRtuMaster *modbusRtuMaster); +private slots: + void onSerialPortAdded(const SerialPort &serialPort); + void onSerialPortRemoved(const SerialPort &serialPort); + private: + typedef struct ModbusRtuPlatformConfiguration { + QString name; + QString description; + QString serialPort; + bool usable; + } ModbusRtuPlatformConfiguration; + + QList m_platformConfigurations; + QHash m_modbusRtuMasters; SerialPortMonitor *m_serialPortMonitor = nullptr; + QHash m_serialPorts; + + void loadPlatformConfiguration(); void loadRtuMasters(); void saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster); diff --git a/libnymea-core/hardware/serialport/serialportmonitor.h b/libnymea-core/hardware/serialport/serialportmonitor.h index 115b6b04..e5f90d4c 100644 --- a/libnymea-core/hardware/serialport/serialportmonitor.h +++ b/libnymea-core/hardware/serialport/serialportmonitor.h @@ -49,7 +49,7 @@ class SerialPort : public QSerialPortInfo { Q_GADGET Q_PROPERTY(QString systemLocation READ systemLocation) Q_PROPERTY(QString manufacturer READ manufacturer) - Q_PROPERTY(QString description READ description) + Q_PROPERTY(QString description READ customDescription) // Note: give the possibility to use a custom description Q_PROPERTY(QString serialNumber READ serialNumber) public: @@ -85,6 +85,22 @@ public: SerialPort() : QSerialPortInfo() { }; explicit SerialPort(const QSerialPortInfo &other) : QSerialPortInfo(other) { }; + QString customDescription() const { + // Note: this creats the possibility to override the desciption of + // serial port for the JSON RPC API. + if (m_customDescription.isEmpty()) + return description(); + + return m_customDescription; + } + + void setCustomDescription(const QString &customDescription) { + m_customDescription = customDescription; + } + +private: + QString m_customDescription; + }; class SerialPorts : public QList diff --git a/libnymea-core/jsonrpc/modbusrtuhandler.cpp b/libnymea-core/jsonrpc/modbusrtuhandler.cpp index 89b169c4..5a597c6d 100644 --- a/libnymea-core/jsonrpc/modbusrtuhandler.cpp +++ b/libnymea-core/jsonrpc/modbusrtuhandler.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -140,13 +140,13 @@ ModbusRtuHandler::ModbusRtuHandler(ModbusRtuManager *modbusRtuManager, QObject * registerMethod("ReconfigureModbusRtuMaster", description, params, returns); // Serial port monitor - connect(modbusRtuManager->serialPortMonitor(), &SerialPortMonitor::serialPortAdded, this, [=](const SerialPort &serialPort){ + connect(modbusRtuManager, &ModbusRtuManager::serialPortAdded, this, [=](const SerialPort &serialPort){ QVariantMap params; params.insert("serialPort", pack(serialPort)); emit SerialPortAdded(params); }); - connect(modbusRtuManager->serialPortMonitor(), &SerialPortMonitor::serialPortRemoved, this, [=](const SerialPort &serialPort){ + connect(modbusRtuManager, &ModbusRtuManager::serialPortRemoved, this, [=](const SerialPort &serialPort){ QVariantMap params; params.insert("serialPort", pack(serialPort)); emit SerialPortRemoved(params); @@ -183,7 +183,7 @@ JsonReply *ModbusRtuHandler::GetSerialPorts(const QVariantMap ¶ms) QVariantMap returnMap; QVariantList portList; - foreach (const SerialPort &serialPort, m_modbusRtuManager->serialPortMonitor()->serialPorts()) { + foreach (const SerialPort &serialPort, m_modbusRtuManager->serialPorts()) { portList << pack(serialPort); } returnMap.insert("serialPorts", portList);