modbus RTU: add platform configuration

pull/678/head
Simon Stürz 2024-07-26 15:32:37 +02:00
parent c0532a64ea
commit 87ed98b72f
4 changed files with 176 additions and 30 deletions

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2021, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -34,6 +34,12 @@
#include "modbusrtumasterimpl.h" #include "modbusrtumasterimpl.h"
#include "hardware/serialport/serialportmonitor.h" #include "hardware/serialport/serialportmonitor.h"
#include "qglobal.h"
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonParseError>
NYMEA_LOGGING_CATEGORY(dcModbusRtu, "ModbusRtu") NYMEA_LOGGING_CATEGORY(dcModbusRtu, "ModbusRtu")
@ -43,18 +49,75 @@ ModbusRtuManager::ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject
QObject(parent), QObject(parent),
m_serialPortMonitor(serialPortMonitor) m_serialPortMonitor(serialPortMonitor)
{ {
if (!supported())
return;
// Load the platform config
loadPlatformConfiguration();
// Load uart configurations // Load uart configurations
loadRtuMasters(); loadRtuMasters();
connect(m_serialPortMonitor, &SerialPortMonitor::serialPortAdded, this, [=](const QSerialPortInfo &serialPortInfo){ // Initialize the list of serial ports already available in the system...
qCDebug(dcModbusRtu()) << "Serial port added. Verify modbus RTU masters..."; foreach (const SerialPort &serialPort, m_serialPortMonitor->serialPorts()) {
onSerialPortAdded(serialPort);
}
connect(m_serialPortMonitor, &SerialPortMonitor::serialPortAdded, this, &ModbusRtuManager::onSerialPortAdded);
connect(m_serialPortMonitor, &SerialPortMonitor::serialPortRemoved, this, &ModbusRtuManager::onSerialPortRemoved);
// Try to connect the modbus RTU masters
foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) {
ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast<ModbusRtuMasterImpl *>(modbusMaster);
if (!modbusMasterImpl->connectDevice()) {
qCWarning(dcModbusRtu()) << "Failed to connect modbus RTU master. Could not connect to" << modbusMaster;
}
}
}
SerialPorts ModbusRtuManager::serialPorts() const
{
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 // Check if we have to reconnect any modbus RTU masters
foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) { foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) {
ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast<ModbusRtuMasterImpl *>(modbusMaster); ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast<ModbusRtuMasterImpl *>(modbusMaster);
// Try only to reconnect if the added serial port matches a disconnected modbus RTU master // Try only to reconnect if the added serial port matches a disconnected modbus RTU master
if (!modbusMasterImpl->connected() && modbusMasterImpl->serialPort() == serialPortInfo.systemLocation()) { if (!modbusMasterImpl->connected() && modbusMasterImpl->serialPort() == serialPort.systemLocation()) {
if (!modbusMasterImpl->connectDevice()) { if (!modbusMasterImpl->connectDevice()) {
qCDebug(dcModbusRtu()) << "Reconnect" << modbusMaster << "failed."; qCDebug(dcModbusRtu()) << "Reconnect" << modbusMaster << "failed.";
} else { } else {
@ -62,21 +125,15 @@ ModbusRtuManager::ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject
} }
} }
} }
});
// Try to connect the modbus rtu masters
foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) {
ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast<ModbusRtuMasterImpl *>(modbusMaster);
if (!modbusMasterImpl->connectDevice()) {
qCWarning(dcModbusRtu()) << "Failed to connect modbus RTU master. Could not connect to" << modbusMaster;
}
}
} }
SerialPortMonitor *ModbusRtuManager::serialPortMonitor() const void ModbusRtuManager::onSerialPortRemoved(const SerialPort &serialPort)
{ {
return m_serialPortMonitor; 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 bool ModbusRtuManager::supported() const
@ -211,6 +268,58 @@ ModbusRtuManager::ModbusRtuError ModbusRtuManager::removeModbusRtuMaster(const Q
return ModbusRtuErrorNoError; 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() void ModbusRtuManager::loadRtuMasters()
{ {
if (!supported()) if (!supported())

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2021, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -37,10 +37,10 @@
#include <QTimer> #include <QTimer>
#include "hardware/modbus/modbusrtumaster.h" #include "hardware/modbus/modbusrtumaster.h"
#include "hardware/serialport/serialportmonitor.h"
namespace nymeaserver { namespace nymeaserver {
class SerialPortMonitor;
class ModbusRtuMasterImpl; class ModbusRtuMasterImpl;
class ModbusRtuManager : public QObject class ModbusRtuManager : public QObject
@ -62,8 +62,6 @@ public:
explicit ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject *parent = nullptr); explicit ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject *parent = nullptr);
~ModbusRtuManager() = default; ~ModbusRtuManager() = default;
SerialPortMonitor *serialPortMonitor() const;
bool supported() const; bool supported() const;
QList<ModbusRtuMaster *> modbusRtuMasters() const; QList<ModbusRtuMaster *> 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 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); 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: signals:
void serialPortAdded(const SerialPort &serialPort);
void serialPortRemoved(const SerialPort &serialPort);
void modbusRtuMasterAdded(ModbusRtuMaster *modbusRtuMaster); void modbusRtuMasterAdded(ModbusRtuMaster *modbusRtuMaster);
void modbusRtuMasterRemoved(ModbusRtuMaster *modbusRtuMaster); void modbusRtuMasterRemoved(ModbusRtuMaster *modbusRtuMaster);
void modbusRtuMasterChanged(ModbusRtuMaster *modbusRtuMaster); void modbusRtuMasterChanged(ModbusRtuMaster *modbusRtuMaster);
private slots:
void onSerialPortAdded(const SerialPort &serialPort);
void onSerialPortRemoved(const SerialPort &serialPort);
private: private:
typedef struct ModbusRtuPlatformConfiguration {
QString name;
QString description;
QString serialPort;
bool usable;
} ModbusRtuPlatformConfiguration;
QList<ModbusRtuPlatformConfiguration> m_platformConfigurations;
QHash<QUuid, ModbusRtuMaster *> m_modbusRtuMasters; QHash<QUuid, ModbusRtuMaster *> m_modbusRtuMasters;
SerialPortMonitor *m_serialPortMonitor = nullptr; SerialPortMonitor *m_serialPortMonitor = nullptr;
QHash<QString, SerialPort> m_serialPorts;
void loadPlatformConfiguration();
void loadRtuMasters(); void loadRtuMasters();
void saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster); void saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster);

View File

@ -49,7 +49,7 @@ class SerialPort : public QSerialPortInfo {
Q_GADGET Q_GADGET
Q_PROPERTY(QString systemLocation READ systemLocation) Q_PROPERTY(QString systemLocation READ systemLocation)
Q_PROPERTY(QString manufacturer READ manufacturer) 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) Q_PROPERTY(QString serialNumber READ serialNumber)
public: public:
@ -85,6 +85,22 @@ public:
SerialPort() : QSerialPortInfo() { }; SerialPort() : QSerialPortInfo() { };
explicit SerialPort(const QSerialPortInfo &other) : QSerialPortInfo(other) { }; 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<SerialPort> class SerialPorts : public QList<SerialPort>

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2021, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -140,13 +140,13 @@ ModbusRtuHandler::ModbusRtuHandler(ModbusRtuManager *modbusRtuManager, QObject *
registerMethod("ReconfigureModbusRtuMaster", description, params, returns); registerMethod("ReconfigureModbusRtuMaster", description, params, returns);
// Serial port monitor // Serial port monitor
connect(modbusRtuManager->serialPortMonitor(), &SerialPortMonitor::serialPortAdded, this, [=](const SerialPort &serialPort){ connect(modbusRtuManager, &ModbusRtuManager::serialPortAdded, this, [=](const SerialPort &serialPort){
QVariantMap params; QVariantMap params;
params.insert("serialPort", pack(serialPort)); params.insert("serialPort", pack(serialPort));
emit SerialPortAdded(params); emit SerialPortAdded(params);
}); });
connect(modbusRtuManager->serialPortMonitor(), &SerialPortMonitor::serialPortRemoved, this, [=](const SerialPort &serialPort){ connect(modbusRtuManager, &ModbusRtuManager::serialPortRemoved, this, [=](const SerialPort &serialPort){
QVariantMap params; QVariantMap params;
params.insert("serialPort", pack(serialPort)); params.insert("serialPort", pack(serialPort));
emit SerialPortRemoved(params); emit SerialPortRemoved(params);
@ -183,7 +183,7 @@ JsonReply *ModbusRtuHandler::GetSerialPorts(const QVariantMap &params)
QVariantMap returnMap; QVariantMap returnMap;
QVariantList portList; QVariantList portList;
foreach (const SerialPort &serialPort, m_modbusRtuManager->serialPortMonitor()->serialPorts()) { foreach (const SerialPort &serialPort, m_modbusRtuManager->serialPorts()) {
portList << pack(serialPort); portList << pack(serialPort);
} }
returnMap.insert("serialPorts", portList); returnMap.insert("serialPorts", portList);