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
*
* This file is part of nymea.
@ -34,6 +34,12 @@
#include "modbusrtumasterimpl.h"
#include "hardware/serialport/serialportmonitor.h"
#include "qglobal.h"
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonParseError>
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<ModbusRtuMasterImpl *>(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<ModbusRtuMasterImpl *>(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<ModbusRtuMasterImpl *>(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())

View File

@ -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 <QTimer>
#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<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 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<ModbusRtuPlatformConfiguration> m_platformConfigurations;
QHash<QUuid, ModbusRtuMaster *> m_modbusRtuMasters;
SerialPortMonitor *m_serialPortMonitor = nullptr;
QHash<QString, SerialPort> m_serialPorts;
void loadPlatformConfiguration();
void loadRtuMasters();
void saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster);

View File

@ -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<SerialPort>

View File

@ -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 &params)
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);