From d6e5347666895fa9783208a13cd918d306aaa524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 5 Feb 2021 13:07:48 +0100 Subject: [PATCH] Implement loading and saving of modbus hardware resources and finish the RTU master implementation --- ...odbusrtuhardwareresourceimplementation.cpp | 18 +- .../modbusrtuhardwareresourceimplementation.h | 4 +- libnymea-core/libnymea-core.pro | 6 +- libnymea-core/modbus/modbusrtumanager.cpp | 39 +- libnymea-core/modbus/modbusrtumanager.h | 11 +- libnymea-core/modbus/modbusrtumasterimpl.cpp | 382 ++++++++++++++---- libnymea-core/modbus/modbusrtumasterimpl.h | 28 +- libnymea-core/modbus/modbusrtureplyimpl.cpp | 6 +- libnymea-core/modbus/modbusrtureplyimpl.h | 10 +- .../modbus/modbusrtuhardwareresource.h | 11 +- libnymea/hardware/modbus/modbusrtumaster.h | 31 +- libnymea/hardware/modbus/modbusrtureply.h | 4 +- libnymea/libnymea.pro | 2 +- libnymea/nymeasettings.cpp | 15 +- libnymea/nymeasettings.h | 2 + 15 files changed, 431 insertions(+), 138 deletions(-) diff --git a/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.cpp b/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.cpp index 902d75ba..2e76b0b5 100644 --- a/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.cpp +++ b/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.cpp @@ -44,10 +44,20 @@ ModbusRtuHardwareResourceImplementation::ModbusRtuHardwareResourceImplementation } -//QList ModbusRtuHardwareResourceImplementation::modbusRtuMasters() const -//{ -// return m_modbusRtuManager->modbusRtuMasters(); -//} +QList ModbusRtuHardwareResourceImplementation::modbusRtuMasters() const +{ + return m_modbusRtuManager->modbusRtuMasters(); +} + +bool ModbusRtuHardwareResourceImplementation::hasModbusRtuMaster(const QUuid &modbusUuid) +{ + return m_modbusRtuManager->hasModbusRtuMaster(modbusUuid); +} + +ModbusRtuMaster *ModbusRtuHardwareResourceImplementation::getModbusRtuMaster(const QUuid &modbusUuid) +{ + return m_modbusRtuManager->getModbusRtuMaster(modbusUuid); +} bool ModbusRtuHardwareResourceImplementation::available() const { diff --git a/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.h b/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.h index ef33dd69..edf2873f 100644 --- a/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.h +++ b/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.h @@ -44,7 +44,9 @@ class ModbusRtuHardwareResourceImplementation : public ModbusRtuHardwareResource public: explicit ModbusRtuHardwareResourceImplementation(ModbusRtuManager *modbusRtuManager, QObject *parent = nullptr); - //QList modbusRtuMasters() const override; + QList modbusRtuMasters() const override; + bool hasModbusRtuMaster(const QUuid &modbusUuid) override; + ModbusRtuMaster *getModbusRtuMaster(const QUuid &modbusUuid) override; bool available() const override; bool enabled() const override; diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index c427f3d6..11701bd4 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -3,7 +3,7 @@ TARGET = nymea-core include(../nymea.pri) -QT += bluetooth dbus qml sql websockets +QT += bluetooth dbus qml sql websockets serialport INCLUDEPATH += $$top_srcdir/libnymea $$top_builddir LIBS += -L$$top_builddir/libnymea/ -lnymea -lssl -lcrypto @@ -40,9 +40,9 @@ CONFIG(withoutpython) { # Let's check if the package exists, not the qt version packagesExist(Qt5SerialBus) { DEFINES += WITH_QTSERIALBUS - Qt += serialbus serialport + Qt += serialbus } else { - message("Qt5SerialBus not available") + message("Qt5SerialBus library is not available. Modbus resource disabled.") } diff --git a/libnymea-core/modbus/modbusrtumanager.cpp b/libnymea-core/modbus/modbusrtumanager.cpp index 12d51bfc..148b63e4 100644 --- a/libnymea-core/modbus/modbusrtumanager.cpp +++ b/libnymea-core/modbus/modbusrtumanager.cpp @@ -31,6 +31,7 @@ #include "modbusrtumanager.h" #include "nymeasettings.h" #include "loggingcategories.h" +#include "modbusrtumasterimpl.h" NYMEA_LOGGING_CATEGORY(dcModbusRtu, "ModbusRtu") @@ -63,10 +64,46 @@ ModbusRtuMaster *ModbusRtuManager::getModbusRtuMaster(const QUuid &modbusUuid) void ModbusRtuManager::init() { // Load uart configurations + NymeaSettings settings(NymeaSettings::SettingsRoleModbusRtu); + qCDebug(dcModbusRtu()) << "Loading modbus RTU resources from" << settings.fileName(); - // Init modbus rtu masters + settings.beginGroup("ModbusRtuMasters"); + foreach (const QString &uuidString, settings.childGroups()) { + settings.beginGroup(uuidString); + QString serialPort = settings.value("serialPort").toString(); + quint32 baudrate = settings.value("baudrate").toUInt(); + QSerialPort::Parity parity = static_cast(settings.value("parity").toInt()); + QSerialPort::DataBits dataBits = static_cast(settings.value("dataBits").toInt()); + QSerialPort::StopBits stopBits = static_cast(settings.value("stopBits").toInt()); + settings.endGroup(); // uuid + + ModbusRtuMasterImpl *modbus = new ModbusRtuMasterImpl(QUuid(uuidString), serialPort, baudrate, parity, dataBits, stopBits, this); + ModbusRtuMaster *modbusRtuMaster = qobject_cast(modbus); + qCDebug(dcModbusRtu()) << "Loaded" << modbusRtuMaster; + m_modbusRtuMasters.insert(modbusRtuMaster->modbusUuid(), modbusRtuMaster); + emit modbusRtuMasterAdded(modbusRtuMaster->modbusUuid()); + } + + settings.endGroup(); // ModbusRtuMasters // Connect signals + + // Enable autoconnect for each modbus rtu master + +} + +void ModbusRtuManager::saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster) +{ + NymeaSettings settings(NymeaSettings::SettingsRoleModbusRtu); + settings.beginGroup("ModbusRtuMasters"); + settings.beginGroup(modbusRtuMaster->modbusUuid().toString()); + settings.setValue("serialPort", modbusRtuMaster->serialPort()); + settings.setValue("baudrate", modbusRtuMaster->baudrate()); + settings.setValue("parity", static_cast(modbusRtuMaster->parity())); + settings.setValue("dataBits", static_cast(modbusRtuMaster->dataBits())); + settings.setValue("stopBits", static_cast(modbusRtuMaster->stopBits())); + settings.endGroup(); // uuid + settings.endGroup(); // ModbusRtuMasters } } diff --git a/libnymea-core/modbus/modbusrtumanager.h b/libnymea-core/modbus/modbusrtumanager.h index f31dca9a..95497e00 100644 --- a/libnymea-core/modbus/modbusrtumanager.h +++ b/libnymea-core/modbus/modbusrtumanager.h @@ -31,8 +31,9 @@ #ifndef MODBUSRTUMANAGER_H #define MODBUSRTUMANAGER_H -#include #include +#include +#include #include "hardware/modbus/modbusrtumaster.h" @@ -52,13 +53,15 @@ public: void init(); signals: - void modbusRtuMasterAdded(ModbusRtuMaster *modbusRtuMaster); - void modbusRtuMasterRemoved(ModbusRtuMaster *modbusRtuMaster); - void modbusRtuMasterChanged(ModbusRtuMaster *modbusRtuMaster); + void modbusRtuMasterAdded(const QUuid &modbusUuid); + void modbusRtuMasterRemoved(const QUuid &modbusUuid); + void modbusRtuMasterChanged(const QUuid &modbusUuid); private: QHash m_modbusRtuMasters; + void saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster); + }; } diff --git a/libnymea-core/modbus/modbusrtumasterimpl.cpp b/libnymea-core/modbus/modbusrtumasterimpl.cpp index ba6c3406..14be6556 100644 --- a/libnymea-core/modbus/modbusrtumasterimpl.cpp +++ b/libnymea-core/modbus/modbusrtumasterimpl.cpp @@ -42,25 +42,36 @@ namespace nymeaserver { ModbusRtuMasterImpl::ModbusRtuMasterImpl(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, QObject *parent) : ModbusRtuMaster(parent), - m_modbusUuid(modbusUuid) + m_modbusUuid(modbusUuid), + m_serialPort(serialPort), + m_baudrate(baudrate), + m_parity(parity), + m_dataBits(dataBits), + m_stopBits(stopBits) { #ifdef WITH_QTSERIALBUS m_modbus = new QModbusRtuSerialMaster(this); - m_modbus->setConnectionParameter(QModbusDevice::SerialPortNameParameter, serialPort); - m_modbus->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, baudrate); - m_modbus->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, dataBits); - m_modbus->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, stopBits); - m_modbus->setConnectionParameter(QModbusDevice::SerialParityParameter, parity); + m_modbus->setConnectionParameter(QModbusDevice::SerialPortNameParameter, m_serialPort); + m_modbus->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_baudrate); + m_modbus->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, m_dataBits); + m_modbus->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, m_stopBits); + m_modbus->setConnectionParameter(QModbusDevice::SerialParityParameter, m_parity); connect(m_modbus, &QModbusTcpClient::stateChanged, this, [=](QModbusDevice::State state){ - qCDebug(dcModbusRtu()) << "Connection state changed" << m_modbusUuid.toString() << serialPort << state; + qCDebug(dcModbusRtu()) << "Connection state changed" << m_modbusUuid.toString() << m_serialPort << state; + if (state == QModbusDevice::ConnectedState) { + m_connected = true; + emit connectedChanged(m_connected); + } else { + m_connected = false; + emit connectedChanged(m_connected); + } }); - //connect(m_modbus, &QModbusRtuSerialMaster::errorOccurred, this, &ModbusRtuMaster::onModbusErrorOccurred); - -// m_reconnectTimer = new QTimer(this); -// m_reconnectTimer->setSingleShot(true); -// connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusRTUMaster::onReconnectTimer); + connect(m_modbus, &QModbusRtuSerialMaster::errorOccurred, this, [=](QModbusDevice::Error error){ + qCDebug(dcModbusRtu()) << "Error occured for modbus RTU master" << m_modbusUuid.toString() << m_serialPort << error << m_modbus->errorString(); + // TODO: check if disconnected... + }); #endif } @@ -74,143 +85,344 @@ QString ModbusRtuMasterImpl::serialPort() const return m_serialPort; } +qint32 ModbusRtuMasterImpl::baudrate() const +{ + return m_baudrate; +} + +QSerialPort::Parity ModbusRtuMasterImpl::parity() const +{ + return m_parity; +} + +QSerialPort::DataBits ModbusRtuMasterImpl::dataBits() const +{ + return m_dataBits; +} + +QSerialPort::StopBits ModbusRtuMasterImpl::stopBits() +{ + return m_stopBits; +} + bool ModbusRtuMasterImpl::connected() const { return m_connected; } -ModbusRtuReply *ModbusRtuMasterImpl::readCoil(uint slaveAddress, uint registerAddress, uint size) +ModbusRtuReply *ModbusRtuMasterImpl::readCoil(int slaveAddress, int registerAddress, quint16 size) { -#ifndef WITH_QTSERIALBUS - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(size) - return nullptr; -#else +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + // Create the actual modbus lib reply QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, size); QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress); - // TODO: fill data and return reply - ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error if (modbusReply->error() != QModbusDevice::NoError) { qCWarning(dcModbusRtu()) << "Read coil request finished with error" << modbusReply->error() << modbusReply->errorString(); - + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + return qobject_cast(reply); -#endif -} - -ModbusRtuReply *ModbusRtuMasterImpl::readDiscreteInput(uint slaveAddress, uint registerAddress, uint size) -{ -#ifndef WITH_QTSERIALBUS - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(size) - return nullptr; #else - // TODO: Q_UNUSED(slaveAddress) Q_UNUSED(registerAddress) Q_UNUSED(size) + return nullptr; #endif } -ModbusRtuReply *ModbusRtuMasterImpl::readInputRegister(uint slaveAddress, uint registerAddress, uint size) +ModbusRtuReply *ModbusRtuMasterImpl::readDiscreteInput(int slaveAddress, int registerAddress, quint16 size) { -#ifndef WITH_QTSERIALBUS - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(size) - return nullptr; +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, registerAddress, size); + QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read descrete inputs request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read descrete inputs request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); #else - // TODO: Q_UNUSED(slaveAddress) Q_UNUSED(registerAddress) Q_UNUSED(size) + return nullptr; #endif } -ModbusRtuReply *ModbusRtuMasterImpl::readHoldingRegister(uint slaveAddress, uint registerAddress, uint size) +ModbusRtuReply *ModbusRtuMasterImpl::readInputRegister(int slaveAddress, int registerAddress, quint16 size) { -#ifndef WITH_QTSERIALBUS - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(size) - return nullptr; +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, registerAddress, size); + QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read input registers request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read input registers request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); #else - // TODO: Q_UNUSED(slaveAddress) Q_UNUSED(registerAddress) Q_UNUSED(size) + return nullptr; #endif } -ModbusRtuReply *ModbusRtuMasterImpl::writeCoil(uint slaveAddress, uint registerAddress, bool status) +ModbusRtuReply *ModbusRtuMasterImpl::readHoldingRegister(int slaveAddress, int registerAddress, quint16 size) { -#ifndef WITH_QTSERIALBUS - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(size) - return nullptr; +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, size); + QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read holding registers request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read holding registers request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); #else - // TODO: Q_UNUSED(slaveAddress) Q_UNUSED(registerAddress) - Q_UNUSED(status) + Q_UNUSED(size) + return nullptr; #endif } -ModbusRtuReply *ModbusRtuMasterImpl::writeCoils(uint slaveAddress, uint registerAddress, const QVector &values) +ModbusRtuReply *ModbusRtuMasterImpl::writeCoils(int slaveAddress, int registerAddress, const QVector &values) { -#ifndef WITH_QTSERIALBUS - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(values) - return nullptr; -#else - // TODO: +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, values.length()); + request.setValues(values); + + QModbusReply *modbusReply = m_modbus->sendWriteRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); +#else Q_UNUSED(slaveAddress) Q_UNUSED(registerAddress) Q_UNUSED(values) + return nullptr; #endif } -ModbusRtuReply *ModbusRtuMasterImpl::writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value) + +ModbusRtuReply *ModbusRtuMasterImpl::writeHoldingRegisters(int slaveAddress, int registerAddress, const QVector &values) { -#ifndef WITH_QTSERIALBUS - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(value) - return nullptr; -#else - // TODO: - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(value) - return nullptr; -#endif -} +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); -ModbusRtuReply *ModbusRtuMasterImpl::writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector &values) -{ -#ifndef WITH_QTSERIALBUS - Q_UNUSED(slaveAddress) - Q_UNUSED(registerAddress) - Q_UNUSED(values) - return nullptr; + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length()); + request.setValues(values); + + QModbusReply *modbusReply = m_modbus->sendWriteRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); #else - // TODO: Q_UNUSED(slaveAddress) Q_UNUSED(registerAddress) Q_UNUSED(values) + return nullptr; #endif } diff --git a/libnymea-core/modbus/modbusrtumasterimpl.h b/libnymea-core/modbus/modbusrtumasterimpl.h index b47f6b7a..15a99f52 100644 --- a/libnymea-core/modbus/modbusrtumasterimpl.h +++ b/libnymea-core/modbus/modbusrtumasterimpl.h @@ -32,10 +32,10 @@ #define MODBUSRTUMASTERIMPL_H #include +#include #ifdef WITH_QTSERIALBUS #include -#include #endif #include "hardware/modbus/modbusrtumaster.h" @@ -50,32 +50,36 @@ public: ~ModbusRtuMasterImpl() override = default; QUuid modbusUuid() const override; - QString serialPort() const override; + qint32 baudrate() const override; + QSerialPort::Parity parity() const override; + QSerialPort::DataBits dataBits() const override; + QSerialPort::StopBits stopBits() override; bool connected() const override; // Requests - ModbusRtuReply *readCoil(uint slaveAddress, uint registerAddress, uint size = 1) override; - ModbusRtuReply *readDiscreteInput(uint slaveAddress, uint registerAddress, uint size = 1) override; - ModbusRtuReply *readInputRegister(uint slaveAddress, uint registerAddress, uint size = 1) override; - ModbusRtuReply *readHoldingRegister(uint slaveAddress, uint registerAddress, uint size = 1) override; + ModbusRtuReply *readCoil(int slaveAddress, int registerAddress, quint16 size = 1) override; + ModbusRtuReply *readDiscreteInput(int slaveAddress, int registerAddress, quint16 size = 1) override; + ModbusRtuReply *readInputRegister(int slaveAddress, int registerAddress, quint16 size = 1) override; + ModbusRtuReply *readHoldingRegister(int slaveAddress, int registerAddress, quint16 size = 1) override; - ModbusRtuReply *writeCoil(uint slaveAddress, uint registerAddress, bool status) override; - ModbusRtuReply *writeCoils(uint slaveAddress, uint registerAddress, const QVector &values) override; - - ModbusRtuReply *writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value) override; - ModbusRtuReply *writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector &values) override; + ModbusRtuReply *writeCoils(int slaveAddress, int registerAddress, const QVector &values) override; + ModbusRtuReply *writeHoldingRegisters(int slaveAddress, int registerAddress, const QVector &values) override; private: QUuid m_modbusUuid; + bool m_connected = false; #ifdef WITH_QTSERIALBUS QModbusRtuSerialMaster *m_modbus = nullptr; #endif QString m_serialPort; - bool m_connected = false; + qint32 m_baudrate; + QSerialPort::Parity m_parity; + QSerialPort::DataBits m_dataBits; + QSerialPort::StopBits m_stopBits; }; } diff --git a/libnymea-core/modbus/modbusrtureplyimpl.cpp b/libnymea-core/modbus/modbusrtureplyimpl.cpp index ff14b0f6..0c66d2ac 100644 --- a/libnymea-core/modbus/modbusrtureplyimpl.cpp +++ b/libnymea-core/modbus/modbusrtureplyimpl.cpp @@ -33,7 +33,7 @@ namespace nymeaserver { -ModbusRtuReplyImpl::ModbusRtuReplyImpl(uint slaveAddress, uint registerAddress, QObject *parent) : +ModbusRtuReplyImpl::ModbusRtuReplyImpl(int slaveAddress, int registerAddress, QObject *parent) : ModbusRtuReply(parent), m_slaveAddress(slaveAddress), m_registerAddress(registerAddress) @@ -51,12 +51,12 @@ void ModbusRtuReplyImpl::setFinished(bool finished) m_finished = finished; } -uint ModbusRtuReplyImpl::slaveAddress() const +int ModbusRtuReplyImpl::slaveAddress() const { return m_slaveAddress; } -uint ModbusRtuReplyImpl::registerAddress() const +int ModbusRtuReplyImpl::registerAddress() const { return m_registerAddress; } diff --git a/libnymea-core/modbus/modbusrtureplyimpl.h b/libnymea-core/modbus/modbusrtureplyimpl.h index 10da27c5..5851b423 100644 --- a/libnymea-core/modbus/modbusrtureplyimpl.h +++ b/libnymea-core/modbus/modbusrtureplyimpl.h @@ -41,13 +41,13 @@ class ModbusRtuReplyImpl : public ModbusRtuReply { Q_OBJECT public: - explicit ModbusRtuReplyImpl(uint slaveAddress, uint registerAddress, QObject *parent = nullptr); + explicit ModbusRtuReplyImpl(int slaveAddress, int registerAddress, QObject *parent = nullptr); bool isFinished() const override; void setFinished(bool finished); - uint slaveAddress() const override; - uint registerAddress() const override; + int slaveAddress() const override; + int registerAddress() const override; QString errorString() const override; void setErrorString(const QString &errorString); @@ -60,8 +60,8 @@ public: private: bool m_finished = false; - uint m_slaveAddress; - uint m_registerAddress; + int m_slaveAddress; + int m_registerAddress; Error m_error = UnknownError; QString m_errorString; QVector m_result; diff --git a/libnymea/hardware/modbus/modbusrtuhardwareresource.h b/libnymea/hardware/modbus/modbusrtuhardwareresource.h index 0767e7e5..55356537 100644 --- a/libnymea/hardware/modbus/modbusrtuhardwareresource.h +++ b/libnymea/hardware/modbus/modbusrtuhardwareresource.h @@ -41,13 +41,18 @@ class ModbusRtuHardwareResource : public HardwareResource { Q_OBJECT public: - explicit ModbusRtuHardwareResource(QObject *parent = nullptr); - virtual ~ModbusRtuHardwareResource() = default; - //virtual QList modbusRtuMasters() const = 0; + virtual QList modbusRtuMasters() const = 0; + virtual bool hasModbusRtuMaster(const QUuid &modbusUuid) = 0; + virtual ModbusRtuMaster *getModbusRtuMaster(const QUuid &modbusUuid) = 0; protected: + explicit ModbusRtuHardwareResource(QObject *parent = nullptr); + virtual ~ModbusRtuHardwareResource() = default; signals: + void modbusRtuMasterAdded(const QUuid &modbusUuid); + void modbusRtuMasterRemoved(const QUuid &modbusUuid); + void modbusRtuMasterChanged(const QUuid &modbusUuid); }; diff --git a/libnymea/hardware/modbus/modbusrtumaster.h b/libnymea/hardware/modbus/modbusrtumaster.h index 754ccfb2..a8caf09c 100644 --- a/libnymea/hardware/modbus/modbusrtumaster.h +++ b/libnymea/hardware/modbus/modbusrtumaster.h @@ -31,8 +31,10 @@ #ifndef MODBUSRTUMASTER_H #define MODBUSRTUMASTER_H -#include #include +#include +#include +#include #include "modbusrtureply.h" @@ -43,20 +45,21 @@ public: // Properties virtual QUuid modbusUuid() const = 0; virtual QString serialPort() const = 0; + virtual qint32 baudrate() const = 0; + virtual QSerialPort::Parity parity() const = 0; + virtual QSerialPort::DataBits dataBits() const = 0; + virtual QSerialPort::StopBits stopBits() = 0; virtual bool connected() const = 0; // Requests - virtual ModbusRtuReply *readCoil(uint slaveAddress, uint registerAddress, uint size = 1) = 0; - virtual ModbusRtuReply *readDiscreteInput(uint slaveAddress, uint registerAddress, uint size = 1) = 0; - virtual ModbusRtuReply *readInputRegister(uint slaveAddress, uint registerAddress, uint size = 1) = 0; - virtual ModbusRtuReply *readHoldingRegister(uint slaveAddress, uint registerAddress, uint size = 1) = 0; + virtual ModbusRtuReply *readCoil(int slaveAddress, int registerAddress, quint16 size = 1) = 0; + virtual ModbusRtuReply *readDiscreteInput(int slaveAddress, int registerAddress, quint16 size = 1) = 0; + virtual ModbusRtuReply *readInputRegister(int slaveAddress, int registerAddress, quint16 size = 1) = 0; + virtual ModbusRtuReply *readHoldingRegister(int slaveAddress, int registerAddress, quint16 size = 1) = 0; - virtual ModbusRtuReply *writeCoil(uint slaveAddress, uint registerAddress, bool status) = 0; - virtual ModbusRtuReply *writeCoils(uint slaveAddress, uint registerAddress, const QVector &values) = 0; - - virtual ModbusRtuReply *writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value) = 0; - virtual ModbusRtuReply *writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector &values) = 0; + virtual ModbusRtuReply *writeCoils(int slaveAddress, int registerAddress, const QVector &values) = 0; + virtual ModbusRtuReply *writeHoldingRegisters(int slaveAddress, int registerAddress, const QVector &values) = 0; protected: explicit ModbusRtuMaster(QObject *parent = nullptr) : QObject(parent) { }; @@ -67,4 +70,12 @@ signals: }; +inline QDebug operator<<(QDebug debug, ModbusRtuMaster *modbusRtuMaster) { + debug.nospace() << "ModbusRtuMaster(" << modbusRtuMaster->modbusUuid().toString(); + debug.nospace() << ", " << modbusRtuMaster->serialPort(); + debug.nospace() << ", BaudRate: " << modbusRtuMaster->baudrate() << ") "; + return debug; +}; + + #endif // MODBUSRTUMASTER_H diff --git a/libnymea/hardware/modbus/modbusrtureply.h b/libnymea/hardware/modbus/modbusrtureply.h index eec9f8ab..46e5a86a 100644 --- a/libnymea/hardware/modbus/modbusrtureply.h +++ b/libnymea/hardware/modbus/modbusrtureply.h @@ -53,8 +53,8 @@ public: virtual bool isFinished() const = 0; - virtual uint slaveAddress() const = 0; - virtual uint registerAddress() const = 0; + virtual int slaveAddress() const = 0; + virtual int registerAddress() const = 0; virtual QString errorString() const = 0; virtual ModbusRtuReply::Error error() const = 0; diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 0b6382c6..41752d75 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -3,7 +3,7 @@ include(../nymea.pri) TARGET = nymea TEMPLATE = lib -QT += network bluetooth dbus +QT += network bluetooth dbus serialport QT -= gui DEFINES += LIBNYMEA_LIBRARY diff --git a/libnymea/nymeasettings.cpp b/libnymea/nymeasettings.cpp index 417c7472..e57c3a25 100644 --- a/libnymea/nymeasettings.cpp +++ b/libnymea/nymeasettings.cpp @@ -64,6 +64,10 @@ This role will create the \b{mqttpolicies.conf} file and is used to store the \l{MqttPolicy}{MqttPolicies}. \value SettingsRoleIOConnections This role will create the \b{ioconnections.conf} file and is used to store the \l{IOConnection}{IOConnections}. + \value SettingsRoleZigbee + This role will create the \b{zigbee.conf} file and is used to store the Zigbee networks. + \value SettingsRoleModbusRtu + This role will create the \b{modbusrtu.conf} file and is used to store the modbus RTU resources. */ @@ -83,7 +87,7 @@ NymeaSettings::NymeaSettings(const SettingsRole &role, QObject *parent): QString settingsPrefix = QCoreApplication::instance()->organizationName() + "/"; QString basePath; - if (!qgetenv("SNAP").isEmpty()) { + if (!qEnvironmentVariableIsEmpty("SNAP")) { basePath = QString(qgetenv("SNAP_DATA")) + "/"; settingsPrefix.clear(); // We don't want that in the snappy case... } else if (settingsPrefix == "nymea-test/") { @@ -125,6 +129,9 @@ NymeaSettings::NymeaSettings(const SettingsRole &role, QObject *parent): case SettingsRoleZigbee: fileName = "zigbee.conf"; break; + case SettingsRoleModbusRtu: + fileName = "modbusrtu.conf"; + break; } m_settings = new QSettings(basePath + settingsPrefix + fileName, QSettings::IniFormat, this); } @@ -157,7 +164,7 @@ QString NymeaSettings::settingsPath() QString path; QString organisationName = QCoreApplication::instance()->organizationName(); - if (!qgetenv("SNAP").isEmpty()) { + if (!qEnvironmentVariableIsEmpty("SNAP")) { path = QString(qgetenv("SNAP_DATA")); } else if (organisationName == "nymea-test") { path = "/tmp/" + organisationName; @@ -174,7 +181,7 @@ QString NymeaSettings::translationsPath() { QString organisationName = QCoreApplication::instance()->organizationName(); - if (!qgetenv("SNAP").isEmpty()) { + if (!qEnvironmentVariableIsEmpty("SNAP")) { return QString(qgetenv("SNAP") + "/usr/share/nymea/translations"); } else if (organisationName == "nymea-test") { return "/tmp/" + organisationName; @@ -188,7 +195,7 @@ QString NymeaSettings::storagePath() { QString path; QString organisationName = QCoreApplication::instance()->organizationName(); - if (!qgetenv("SNAP").isEmpty()) { + if (!qEnvironmentVariableIsEmpty("SNAP")) { path = QString(qgetenv("SNAP_DATA")); } else if (organisationName == "nymea-test") { path = "/tmp/" + organisationName; diff --git a/libnymea/nymeasettings.h b/libnymea/nymeasettings.h index 36301eb3..8c514b68 100644 --- a/libnymea/nymeasettings.h +++ b/libnymea/nymeasettings.h @@ -53,7 +53,9 @@ public: SettingsRoleMqttPolicies, SettingsRoleIOConnections, SettingsRoleZigbee, + SettingsRoleModbusRtu }; + Q_ENUM(SettingsRole) explicit NymeaSettings(const SettingsRole &role = SettingsRoleNone, QObject *parent = nullptr); ~NymeaSettings();