From 72a0db6d0d47442e63afacea37f68736f5a3a31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 10 Feb 2021 17:07:57 +0100 Subject: [PATCH] Implement ModbusRtu JSONRPC API --- .../hardware/serialport/serialportmonitor.cpp | 189 ++++++++++++++++ .../hardware/serialport/serialportmonitor.h | 145 ++++++++++++ .../jsonrpc/jsonrpcserverimplementation.cpp | 2 + libnymea-core/jsonrpc/modbusrtuhandler.cpp | 214 +++++++++++++++++- libnymea-core/jsonrpc/modbusrtuhandler.h | 24 +- libnymea-core/libnymea-core.pro | 10 + libnymea-core/modbus/modbusrtumanager.cpp | 102 +++++---- libnymea-core/modbus/modbusrtumanager.h | 27 ++- libnymea-core/nymeacore.cpp | 6 +- libnymea-core/nymeacore.h | 2 + 10 files changed, 663 insertions(+), 58 deletions(-) create mode 100644 libnymea-core/hardware/serialport/serialportmonitor.cpp create mode 100644 libnymea-core/hardware/serialport/serialportmonitor.h diff --git a/libnymea-core/hardware/serialport/serialportmonitor.cpp b/libnymea-core/hardware/serialport/serialportmonitor.cpp new file mode 100644 index 00000000..cd1addfc --- /dev/null +++ b/libnymea-core/hardware/serialport/serialportmonitor.cpp @@ -0,0 +1,189 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "serialportmonitor.h" +#include "loggingcategories.h" + +namespace nymeaserver { + +NYMEA_LOGGING_CATEGORY(dcSerialPortMonitor, "SerialPortMonitor") + +SerialPortMonitor::SerialPortMonitor(QObject *parent) : QObject(parent) +{ + // Read initially all tty devices + foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) { + m_serialPorts.insert(serialPortInfo.systemLocation(), SerialPort(serialPortInfo)); + } + +#ifdef WITH_UDEV + // Init udev + m_udev = udev_new(); + if (!m_udev) { + qCWarning(dcSerialPortMonitor()) << "Could not initialize udev for the adapter monitor"; + return; + } + + // Create udev monitor + m_monitor = udev_monitor_new_from_netlink(m_udev, "udev"); + if (!m_monitor) { + qCWarning(dcSerialPortMonitor()) << "Could not initialize udev monitor."; + udev_unref(m_udev); + m_udev = nullptr; + return; + } + + // Set monitor filter to tty subsystem + if (udev_monitor_filter_add_match_subsystem_devtype(m_monitor, "tty", nullptr) < 0) { + qCWarning(dcSerialPortMonitor()) << "Could not set subsystem device type filter to tty."; + udev_monitor_unref(m_monitor); + m_monitor = nullptr; + udev_unref(m_udev); + m_udev = nullptr; + return; + } + + // Enable the monitor + if (udev_monitor_enable_receiving(m_monitor) < 0) { + qCWarning(dcSerialPortMonitor()) << "Could not enable udev monitor."; + udev_monitor_unref(m_monitor); + m_monitor = nullptr; + udev_unref(m_udev); + m_udev = nullptr; + return; + } + + // Create socket notifier for read + int socketDescriptor = udev_monitor_get_fd(m_monitor); + m_notifier = new QSocketNotifier(socketDescriptor, QSocketNotifier::Read, this); + connect(m_notifier, &QSocketNotifier::activated, this, [this, socketDescriptor](int socket){ + // Make sure the socket matches + if (socketDescriptor != socket) { + qCWarning(dcSerialPortMonitor()) << "Socket handles do not match. socket != socketdescriptor"; + return; + } + + // Create udev device + udev_device *device = udev_monitor_receive_device(m_monitor); + if (!device) { + qCWarning(dcSerialPortMonitor()) << "Got socket sotification but could not read device information."; + return; + } + + QString actionString = QString::fromLatin1(udev_device_get_action(device)); + QString systemPath = QString::fromLatin1(udev_device_get_property_value(device,"DEVNAME")); + QString manufacturerString = QString::fromLatin1(udev_device_get_property_value(device,"ID_VENDOR_ENC")); + QString descriptionString = QString::fromLatin1(udev_device_get_property_value(device,"ID_MODEL_ENC")); + QString serialNumberString = QString::fromLatin1(udev_device_get_property_value(device, "ID_SERIAL_SHORT")); + + // Clean udev device + udev_device_unref(device); + + // Make sure we know the action + if (actionString.isEmpty()) + return; + + if (actionString == "add") { + qCDebug(dcSerialPortMonitor()) << "[+]" << systemPath << serialNumberString << manufacturerString << descriptionString; + if (!m_serialPorts.contains(systemPath)) { + // Get the serial port info and add it internally + foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) { + if (serialPortInfo.systemLocation() == systemPath) { + addSerialPortInternally(SerialPort(serialPortInfo)); + } + } + + } + } + + if (actionString == "remove") { + qCDebug(dcSerialPortMonitor()) << "[-]" << systemPath << serialNumberString << manufacturerString << descriptionString; + if (m_serialPorts.contains(systemPath)) { + SerialPort serialPort = m_serialPorts.take(systemPath); + qCDebug(dcSerialPortMonitor()) << "Removed" << serialPort.systemLocation(); + emit serialPortRemoved(serialPort); + } + } + }); + + qCDebug(dcSerialPortMonitor()) << "Serial port monitor enabled successfully"; + foreach (const SerialPort &serialPort, m_serialPorts) { + qCDebug(dcSerialPortMonitor()) << "-" << serialPort.systemLocation() << serialPort.description(); + } + m_notifier->setEnabled(true); +#else + m_timer = new QTimer(this); + m_timer->setInterval(5000); + m_timer->setSingleShot(false); + connect(m_timer, &QTimer::timeout, this, [=](){ + QStringList availablePorts; + // Add a new adapter if not in the list already + foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) { + availablePorts.append(serialPortInfo.systemLocation()); + if (!m_serialPorts.keys().contains(serialPortInfo.systemLocation())) { + qCDebug(dcSerialPortMonitor()) << "[+]" << serialPortInfo.systemLocation() << serialPortInfo.manufacturer() << serialPortInfo.description(); + addSerialPortInternally(SerialPort(serialPortInfo)); + } + } + // Remove adapters no longer available + foreach (const QString &systemLocation, m_serialPorts.keys()) { + if (!availablePorts.contains(systemLocation)) { + SerialPort serialPortInfo = m_serialPorts.take(systemLocation); + qCDebug(dcSerialPortMonitor()) << "[-]" << serialPortInfo.systemLocation() << serialPortInfo.manufacturer() << serialPortInfo.description(); + emit serialPortRemoved(serialPortInfo); + } + } + }); + m_timer->start(); + +#endif +} + +SerialPorts SerialPortMonitor::serialPorts() const +{ + return m_serialPorts.values(); +} + +bool SerialPortMonitor::serialPortAvailable(const QString &systemLocation) const +{ + return m_serialPorts.keys().contains(systemLocation); +} + +void SerialPortMonitor::addSerialPortInternally(const SerialPort &serialPort) +{ + if (m_serialPorts.keys().contains(serialPort.systemLocation())) { + qCWarning(dcSerialPortMonitor()) << "Tried to add serial port but the port has alrady been added."; + return; + } + + m_serialPorts.insert(serialPort.systemLocation(), serialPort); + emit serialPortAdded(serialPort); +} + +} diff --git a/libnymea-core/hardware/serialport/serialportmonitor.h b/libnymea-core/hardware/serialport/serialportmonitor.h new file mode 100644 index 00000000..115b6b04 --- /dev/null +++ b/libnymea-core/hardware/serialport/serialportmonitor.h @@ -0,0 +1,145 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SERIALPORTMONITOR_H +#define SERIALPORTMONITOR_H + +#include +#include +#include +#include + +#ifdef WITH_UDEV +#include +#include +#else +#include +#endif + +namespace nymeaserver { + +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 serialNumber READ serialNumber) + +public: + enum SerialPortParity { + SerialPortParityNoParity = 0, + SerialPortParityEvenParity = 2, + SerialPortParityOddParity = 3, + SerialPortParitySpaceParity = 4, + SerialPortParityMarkParity = 5, + SerialPortParityUnknownParity = -1 + }; + Q_ENUM(SerialPortParity) + + + enum SerialPortDataBits { + SerialPortDataBitsData5 = 5, + SerialPortDataBitsData6 = 6, + SerialPortDataBitsData7 = 7, + SerialPortDataBitsData8 = 8, + SerialPortDataBitsUnknownDataBits = -1 + }; + Q_ENUM(SerialPortDataBits) + + + enum SerialPortStopBits { + SerialPortStopBitsOneStop = 1, + SerialPortStopBitsOneAndHalfStop = 3, + SerialPortStopBitsTwoStop = 2, + SerialPortStopBitsUnknownStopBits = -1 + }; + Q_ENUM(SerialPortStopBits) + + SerialPort() : QSerialPortInfo() { }; + explicit SerialPort(const QSerialPortInfo &other) : QSerialPortInfo(other) { }; + +}; + +class SerialPorts : public QList +{ + Q_GADGET + Q_PROPERTY(int count READ count) + +public: + inline SerialPorts() = default; + inline SerialPorts(const QList &other) : QList(other) { }; + inline bool hasSerialPort(const QString &serialPort) { + for (int i = 0; i < count(); i++) { + if (at(i).systemLocation() == serialPort) { + return true; + } + } + return false; + }; + + inline Q_INVOKABLE QVariant get(int index) const { return QVariant::fromValue(at(index)); }; + inline Q_INVOKABLE void put(const QVariant &variant) { append(variant.value()); }; +}; + + +class SerialPortMonitor : public QObject +{ + Q_OBJECT +public: + explicit SerialPortMonitor(QObject *parent = nullptr); + + SerialPorts serialPorts() const; + bool serialPortAvailable(const QString &systemLocation) const; + +signals: + void serialPortAdded(const SerialPort &serialPort); + void serialPortRemoved(const SerialPort &serialPort); + +private: + QHash m_serialPorts; + + void addSerialPortInternally(const SerialPort &serialPort); + +#ifdef WITH_UDEV + struct udev *m_udev = nullptr; + struct udev_monitor *m_monitor = nullptr; + QSocketNotifier *m_notifier = nullptr; +#else + QTimer *m_timer = nullptr; +#endif + +}; + +} + +Q_DECLARE_METATYPE(nymeaserver::SerialPort) +Q_DECLARE_METATYPE(nymeaserver::SerialPorts) + +#endif // SERIALPORTMONITOR_H diff --git a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp index 36a75646..147c460e 100644 --- a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp +++ b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp @@ -75,6 +75,7 @@ #include "systemhandler.h" #include "usershandler.h" #include "zigbeehandler.h" +#include "modbusrtuhandler.h" #include #include @@ -599,6 +600,7 @@ void JsonRPCServerImplementation::setup() registerHandler(new SystemHandler(NymeaCore::instance()->platform(), this)); registerHandler(new UsersHandler(NymeaCore::instance()->userManager(), this)); registerHandler(new ZigbeeHandler(NymeaCore::instance()->zigbeeManager(), this)); + registerHandler(new ModbusRtuHandler(NymeaCore::instance()->modbusRtuManager(), this)); connect(NymeaCore::instance()->cloudManager(), &CloudManager::pairingReply, this, &JsonRPCServerImplementation::pairingFinished); connect(NymeaCore::instance()->cloudManager(), &CloudManager::connectionStateChanged, this, &JsonRPCServerImplementation::onCloudConnectionStateChanged); diff --git a/libnymea-core/jsonrpc/modbusrtuhandler.cpp b/libnymea-core/jsonrpc/modbusrtuhandler.cpp index 535deb6b..1a210485 100644 --- a/libnymea-core/jsonrpc/modbusrtuhandler.cpp +++ b/libnymea-core/jsonrpc/modbusrtuhandler.cpp @@ -29,12 +29,140 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "modbusrtuhandler.h" +#include "modbus/modbusrtumanager.h" +#include "hardware/serialport/serialportmonitor.h" namespace nymeaserver { -ModbusRtuHandler::ModbusRtuHandler(QObject *parent) : JsonHandler(parent) +ModbusRtuHandler::ModbusRtuHandler(ModbusRtuManager *modbusRtuManager, QObject *parent) : + JsonHandler(parent), + m_modbusRtuManager(modbusRtuManager) { + qRegisterMetaType(); + registerEnum(); + registerEnum(); + registerEnum(); + registerEnum(); + registerObject(); + QVariantMap modbusRtuMasterDescription; + modbusRtuMasterDescription.insert("modbusUuid", enumValueName(Uuid)); + modbusRtuMasterDescription.insert("connected", enumValueName(Bool)); + modbusRtuMasterDescription.insert("serialPort", enumValueName(String)); + modbusRtuMasterDescription.insert("baudrate", enumValueName(Uint)); + modbusRtuMasterDescription.insert("parity", enumRef()); + modbusRtuMasterDescription.insert("stopBits", enumRef()); + modbusRtuMasterDescription.insert("dataBits", enumRef()); + registerObject("ModbusRtuMaster", modbusRtuMasterDescription); + + QVariantMap params, returns; + QString description; + + // GetSerialPorts + params.clear(); returns.clear(); + description = "Get the list of available serial ports from the host system."; + returns.insert("serialPorts", objectRef()); + registerMethod("GetSerialPorts", description, params, returns); + + // SerialPortAdded notification + params.clear(); + description = "Emitted whenever a serial port has been added to the system."; + params.insert("serialPort", objectRef()); + registerNotification("SerialPortAdded", description, params); + + // SerialPortRemoved notification + params.clear(); + description = "Emitted whenever a serial port has been removed from the system."; + params.insert("serialPort", objectRef()); + registerNotification("SerialPortRemoved", description, params); + + // GetModbusRtuMasters + params.clear(); returns.clear(); + description = "Get the list of configured modbus RTU masters."; + returns.insert("modbusRtuMasters", QVariantList() << objectRef("ModbusRtuMaster")); + registerMethod("GetModbusRtuMasters", description, params, returns); + + // ModbusRtuMasterAdded notification + params.clear(); + description = "Emitted whenever a new modbus RTU master has been added to the system."; + params.insert("modbusRtuMaster", objectRef("ModbusRtuMaster")); + registerNotification("ModbusRtuMasterAdded", description, params); + + // ModbusRtuMasterRemoved notification + params.clear(); + description = "Emitted whenever a new modbus RTU master has been removed from the system."; + params.insert("modbusUuid", enumValueName(Uuid)); + registerNotification("ModbusRtuMasterRemoved", description, params); + + // ModbusRtuMasterChanged notification + params.clear(); + description = "Emitted whenever a new modbus RTU master has been changed to the system."; + params.insert("modbusRtuMaster", objectRef("ModbusRtuMaster")); + registerNotification("ModbusRtuMasterChanged", description, params); + + // AddModbusRtuMaster + params.clear(); returns.clear(); + description = "Add a new modbus RTU master with the given configuration."; + params.insert("serialPort", enumValueName(String)); + params.insert("baudrate", enumValueName(Uint)); + params.insert("parity", enumRef()); + params.insert("dataBits", enumRef()); + params.insert("stopBits", enumRef()); + returns.insert("o:modbusUuid", enumValueName(Uuid)); + returns.insert("modbusError", enumRef()); + registerMethod("AddModbusRtuMaster", description, params, returns); + + // RemoveModbusRtuMaster + params.clear(); returns.clear(); + description = "Remove the modbus RTU master with the given modbus UUID."; + params.insert("modbusUuid", enumValueName(Uuid)); + returns.insert("modbusError", enumRef()); + registerMethod("RemoveModbusRtuMaster", description, params, returns); + + // ReconfigureModbusRtuMaster + params.clear(); returns.clear(); + description = "Rconfigure the modbus RTU master with the given UUID and configuration."; + params.insert("modbusUuid", enumValueName(Uuid)); + params.insert("serialPort", enumValueName(String)); + params.insert("baudrate", enumValueName(Uint)); + params.insert("parity", enumRef()); + params.insert("dataBits", enumRef()); + params.insert("stopBits", enumRef()); + returns.insert("modbusError", enumRef()); + registerMethod("ReconfigureModbusRtuMaster", description, params, returns); + + // Serial port monitor + connect(modbusRtuManager->serialPortMonitor(), &SerialPortMonitor::serialPortAdded, this, [=](const SerialPort &serialPort){ + QVariantMap params; + params.insert("serialPort", pack(serialPort)); + emit SerialPortAdded(params); + }); + + connect(modbusRtuManager->serialPortMonitor(), &SerialPortMonitor::serialPortRemoved, this, [=](const SerialPort &serialPort){ + QVariantMap params; + params.insert("serialPort", pack(serialPort)); + emit SerialPortAdded(params); + }); + + + // Modbus manager + connect(modbusRtuManager, &ModbusRtuManager::modbusRtuMasterAdded, this, [=](ModbusRtuMaster *modbusRtuMaster){ + QVariantMap params; + params.insert("modbusRtuMaster", packModbusRtuMaster(modbusRtuMaster)); + emit ModbusRtuMasterAdded(params); + }); + + connect(modbusRtuManager, &ModbusRtuManager::modbusRtuMasterChanged, this, [=](ModbusRtuMaster *modbusRtuMaster){ + QVariantMap params; + params.insert("modbusRtuMaster", packModbusRtuMaster(modbusRtuMaster)); + emit ModbusRtuMasterChanged(params); + }); + + connect(modbusRtuManager, &ModbusRtuManager::modbusRtuMasterRemoved, this, [=](ModbusRtuMaster *modbusRtuMaster){ + QVariantMap params; + params.insert("modbusUuid", modbusRtuMaster->modbusUuid()); + emit ModbusRtuMasterRemoved(params); + }); } QString ModbusRtuHandler::name() const @@ -42,4 +170,88 @@ QString ModbusRtuHandler::name() const return "ModbusRtu"; } +JsonReply *ModbusRtuHandler::GetSerialPorts(const QVariantMap ¶ms) +{ + Q_UNUSED(params) + + QVariantMap returnMap; + QVariantList portList; + foreach (const SerialPort &serialPort, m_modbusRtuManager->serialPortMonitor()->serialPorts()) { + portList << pack(serialPort); + } + returnMap.insert("serialPorts", portList); + return createReply(returnMap); +} + +JsonReply *ModbusRtuHandler::GetModbusRtuMasters(const QVariantMap ¶ms) +{ + Q_UNUSED(params) + + QVariantMap returnMap; + QVariantList modbusList; + foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuManager->modbusRtuMasters()) { + modbusList << packModbusRtuMaster(modbusMaster); + } + returnMap.insert("modbusRtuMasters", modbusList); + return createReply(returnMap); +} + +JsonReply *ModbusRtuHandler::AddModbusRtuMaster(const QVariantMap ¶ms) +{ + QString serialPort = params.value("serialPort").toString(); + qint32 baudrate = params.value("baudrate").toUInt(); + QSerialPort::Parity parity = static_cast(enumNameToValue(params.value("parity").toString())); + QSerialPort::StopBits stopBits = static_cast(enumNameToValue(params.value("stopBits").toString())); + QSerialPort::DataBits dataBits = static_cast(enumNameToValue(params.value("dataBits").toString())); + + QPair result = m_modbusRtuManager->addNewModbusRtuMaster(serialPort, baudrate, parity, dataBits, stopBits); + + QVariantMap returnMap; + returnMap.insert("modbusError", enumValueName(result.first)); + if (result.first == ModbusRtuManager::ModbusRtuErrorNoError) { + returnMap.insert("modbusUuid", result.second); + } + + return createReply(returnMap); +} + +JsonReply *ModbusRtuHandler::RemoveModbusRtuMaster(const QVariantMap ¶ms) +{ + QUuid modbusUuid = params.value("modbusUuid").toUuid(); + + ModbusRtuManager::ModbusRtuError result = m_modbusRtuManager->removeModbusRtuMaster(modbusUuid); + QVariantMap returnMap; + returnMap.insert("modbusError", enumValueName(result)); + return createReply(returnMap); +} + +JsonReply *ModbusRtuHandler::ReconfigureModbusRtuMaster(const QVariantMap ¶ms) +{ + QUuid modbusUuid = params.value("modbusUuid").toUuid(); + QString serialPort = params.value("serialPort").toString(); + qint32 baudrate = params.value("baudrate").toUInt(); + QSerialPort::Parity parity = static_cast(enumNameToValue(params.value("parity").toString())); + QSerialPort::StopBits stopBits = static_cast(enumNameToValue(params.value("stopBits").toString())); + QSerialPort::DataBits dataBits = static_cast(enumNameToValue(params.value("dataBits").toString())); + + ModbusRtuManager::ModbusRtuError result = m_modbusRtuManager->reconfigureModbusRtuMaster(modbusUuid, serialPort, baudrate, parity, dataBits, stopBits); + QVariantMap returnMap; + returnMap.insert("modbusError", enumValueName(result)); + return createReply(returnMap); +} + + +QVariantMap ModbusRtuHandler::packModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster) +{ + QVariantMap modbusRtuMasterMap; + modbusRtuMasterMap.insert("modbusUuid", modbusRtuMaster->modbusUuid()); + modbusRtuMasterMap.insert("connected", modbusRtuMaster->connected()); + modbusRtuMasterMap.insert("serialPort", modbusRtuMaster->serialPort()); + modbusRtuMasterMap.insert("baudrate", modbusRtuMaster->baudrate()); + modbusRtuMasterMap.insert("parity", enumValueName(static_cast(modbusRtuMaster->parity()))); + modbusRtuMasterMap.insert("stopBits", enumValueName(static_cast(modbusRtuMaster->stopBits()))); + modbusRtuMasterMap.insert("dataBits", enumValueName(static_cast(modbusRtuMaster->dataBits()))); + return modbusRtuMasterMap; +} + } diff --git a/libnymea-core/jsonrpc/modbusrtuhandler.h b/libnymea-core/jsonrpc/modbusrtuhandler.h index a0242525..171d3540 100644 --- a/libnymea-core/jsonrpc/modbusrtuhandler.h +++ b/libnymea-core/jsonrpc/modbusrtuhandler.h @@ -34,19 +34,39 @@ #include #include "jsonrpc/jsonhandler.h" +#include "hardware/modbus/modbusrtumaster.h" namespace nymeaserver { +class ModbusRtuManager; + class ModbusRtuHandler : public JsonHandler { Q_OBJECT public: - explicit ModbusRtuHandler(QObject *parent = nullptr); + explicit ModbusRtuHandler(ModbusRtuManager *modbusRtuManager, QObject *parent = nullptr); QString name() const override; -signals: + Q_INVOKABLE JsonReply *GetSerialPorts(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *GetModbusRtuMasters(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *AddModbusRtuMaster(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *RemoveModbusRtuMaster(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *ReconfigureModbusRtuMaster(const QVariantMap ¶ms); + +signals: + void SerialPortAdded(const QVariantMap ¶ms); + void SerialPortRemoved(const QVariantMap ¶ms); + + void ModbusRtuMasterAdded(const QVariantMap ¶ms); + void ModbusRtuMasterRemoved(const QVariantMap ¶ms); + void ModbusRtuMasterChanged(const QVariantMap ¶ms); + +private: + ModbusRtuManager *m_modbusRtuManager = nullptr; + + QVariantMap packModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster); }; } diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index 1668715f..eb17feef 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -47,6 +47,14 @@ packagesExist(Qt5SerialBus) { message("Qt5SerialBus package not found. Building without QtSerialBus support.") } +# Note: udev is not available on all platforms +packagesExist(libudev) { + message("Build with udev support") + PKGCONFIG += libudev + DEFINES += WITH_UDEV +} else { + message("Build without udev support.") +} target.path = $$[QT_INSTALL_LIBS] INSTALLS += target @@ -57,6 +65,7 @@ RESOURCES += $$top_srcdir/icons.qrc \ HEADERS += nymeacore.h \ + hardware/serialport/serialportmonitor.h \ integrations/apikeysprovidersloader.h \ integrations/plugininfocache.h \ integrations/python/pyapikeystorage.h \ @@ -156,6 +165,7 @@ HEADERS += nymeacore.h \ SOURCES += nymeacore.cpp \ + hardware/serialport/serialportmonitor.cpp \ integrations/apikeysprovidersloader.cpp \ integrations/plugininfocache.cpp \ integrations/thingmanagerimplementation.cpp \ diff --git a/libnymea-core/modbus/modbusrtumanager.cpp b/libnymea-core/modbus/modbusrtumanager.cpp index 241f2e3c..55b20a37 100644 --- a/libnymea-core/modbus/modbusrtumanager.cpp +++ b/libnymea-core/modbus/modbusrtumanager.cpp @@ -33,32 +33,28 @@ #include "loggingcategories.h" #include "modbusrtumasterimpl.h" +#include "hardware/serialport/serialportmonitor.h" NYMEA_LOGGING_CATEGORY(dcModbusRtu, "ModbusRtu") namespace nymeaserver { -ModbusRtuManager::ModbusRtuManager(QObject *parent) : QObject(parent) +ModbusRtuManager::ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject *parent) : + QObject(parent), + m_serialPortMonitor(serialPortMonitor) { - // Load available serial ports - updateSerialPorts(); - // Load uart configurations loadRtuMasters(); - // Enable autoconnect for each modbus rtu master - m_timer = new QTimer(this); - m_timer->setInterval(5000); - m_timer->setSingleShot(false); - connect(m_timer, &QTimer::timeout, this, [=](){ - // Update serial port list - updateSerialPorts(); + connect(m_serialPortMonitor, &SerialPortMonitor::serialPortAdded, this, [=](const QSerialPortInfo &serialPortInfo){ + qCDebug(dcModbusRtu()) << "Serial port added. Verify modbus RTU masters..."; - // Check if we have to reconnect a device + // Check if we have to reconnect any modbus RTU masters foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) { ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast(modbusMaster); - if (!modbusMasterImpl->connected()) { + // 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 { @@ -67,8 +63,11 @@ ModbusRtuManager::ModbusRtuManager(QObject *parent) : QObject(parent) } } }); +} - m_timer->start(); +SerialPortMonitor *ModbusRtuManager::serialPortMonitor() const +{ + return m_serialPortMonitor; } QList ModbusRtuManager::modbusRtuMasters() const @@ -90,30 +89,37 @@ ModbusRtuMaster *ModbusRtuManager::getModbusRtuMaster(const QUuid &modbusUuid) return nullptr; } -QPair ModbusRtuManager::addNewModbusRtuMaster(const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits) +QPair ModbusRtuManager::addNewModbusRtuMaster(const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits) { // Check if the serial port exists - - // Check if the serial port is not occupied + if (!m_serialPortMonitor->serialPortAvailable(serialPort)) { + qCWarning(dcModbusRtu()) << "Cannot add new modbus RTU master because the serial port" << serialPort << "is not available any more"; + return QPair(ModbusRtuErrorHardwareNotFound, QUuid()); + } QUuid modbusUuid = QUuid::createUuid(); ModbusRtuMasterImpl *modbusMaster = new ModbusRtuMasterImpl(modbusUuid, serialPort, baudrate, parity, dataBits, stopBits, this); ModbusRtuMaster *modbus = qobject_cast(modbusMaster); - qCDebug(dcModbusRtu()) << "Adding new" << qobject_cast(modbusMaster) << parity << dataBits << stopBits; + qCDebug(dcModbusRtu()) << "Adding new" << modbus << parity << dataBits << stopBits; - // Connect the modbus master bus; - m_modbusRtuMasters.insert(modbusUuid, modbus); - emit modbusRtuMasterAdded(modbus); + // Note: We could add the modbus master event if a connection is currently not possible...not sure yet + if (!modbusMaster->connectDevice()) { + qCWarning(dcModbusRtu()) << "Failed to add new modbus RTU master. Could not connect to" << modbus << parity << dataBits << stopBits; + modbusMaster->deleteLater(); + return QPair(ModbusRtuErrorConnectionFailed, QUuid()); + } + + addModbusRtuMasterInternally(modbusMaster); saveModbusRtuMaster(modbus); - return QPair(ErrorNoError, modbusUuid); + return QPair(ModbusRtuErrorNoError, modbusUuid); } -ModbusRtuManager::Error ModbusRtuManager::reconfigureRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits) +ModbusRtuManager::ModbusRtuError ModbusRtuManager::reconfigureModbusRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits) { if (!m_modbusRtuMasters.contains(modbusUuid)) { qCWarning(dcModbusRtu()) << "Could not reconfigure modbus RTU master because no resource could be found with uuid" << modbusUuid.toString(); - return ErrorNotFound; + return ModbusRtuErrorUuidNotFound; } ModbusRtuMasterImpl *modbusMaster = qobject_cast(m_modbusRtuMasters.value(modbusUuid)); @@ -131,38 +137,33 @@ ModbusRtuManager::Error ModbusRtuManager::reconfigureRtuMaster(const QUuid &modb // Connect again if (!modbusMaster->connectDevice()) { qCWarning(dcModbusRtu()) << "Failed to connect to" << m_modbusRtuMasters.value(modbusUuid); - return ErrorConnectionFailed; + emit modbusRtuMasterChanged(m_modbusRtuMasters.value(modbusUuid)); + return ModbusRtuErrorConnectionFailed; } emit modbusRtuMasterChanged(m_modbusRtuMasters.value(modbusUuid)); qCDebug(dcModbusRtu()) << "Reconfigured successfully" << m_modbusRtuMasters.value(modbusUuid); - return ErrorNoError; + return ModbusRtuErrorNoError; } - -ModbusRtuManager::Error ModbusRtuManager::removeModbusRtuMaster(const QUuid &modbusUuid) +ModbusRtuManager::ModbusRtuError ModbusRtuManager::removeModbusRtuMaster(const QUuid &modbusUuid) { - ModbusRtuMasterImpl *modbusMaster = qobject_cast(m_modbusRtuMasters.value(modbusUuid)); - if (!modbusMaster) { + if (!m_modbusRtuMasters.contains(modbusUuid)) { qCWarning(dcModbusRtu()) << "Could not remove modbus RTU master because no resource could be found with uuid" << modbusUuid.toString(); - return ErrorNotFound; + return ModbusRtuErrorUuidNotFound; } + ModbusRtuMasterImpl *modbusMaster = qobject_cast(m_modbusRtuMasters.take(modbusUuid)); qCDebug(dcModbusRtu()) << "Removing modbus RTU master" << qobject_cast(modbusMaster); + modbusMaster->disconnectDevice(); + modbusMaster->deleteLater(); + emit modbusRtuMasterRemoved(modbusMaster); - modbusMaster->deleteLater(); - return ErrorNoError; + return ModbusRtuErrorNoError; } -void ModbusRtuManager::updateSerialPorts() -{ - // Check if serial port added or removed - -} - - void ModbusRtuManager::loadRtuMasters() { NymeaSettings settings(NymeaSettings::SettingsRoleModbusRtu); @@ -178,11 +179,7 @@ void ModbusRtuManager::loadRtuMasters() 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); + addModbusRtuMasterInternally(new ModbusRtuMasterImpl(QUuid(uuidString), serialPort, baudrate, parity, dataBits, stopBits, this)); } settings.endGroup(); // ModbusRtuMasters @@ -191,6 +188,7 @@ void ModbusRtuManager::loadRtuMasters() void ModbusRtuManager::saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster) { NymeaSettings settings(NymeaSettings::SettingsRoleModbusRtu); + qCDebug(dcModbusRtu()) << "Saving" << modbusRtuMaster << "to" << settings.fileName(); settings.beginGroup("ModbusRtuMasters"); settings.beginGroup(modbusRtuMaster->modbusUuid().toString()); settings.setValue("serialPort", modbusRtuMaster->serialPort()); @@ -202,4 +200,18 @@ void ModbusRtuManager::saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster) settings.endGroup(); // ModbusRtuMasters } +void ModbusRtuManager::addModbusRtuMasterInternally(ModbusRtuMasterImpl *modbusRtuMaster) +{ + ModbusRtuMaster *modbusMaster = qobject_cast(modbusRtuMaster); + qCDebug(dcModbusRtu()) << "Adding" << modbusMaster; + m_modbusRtuMasters.insert(modbusMaster->modbusUuid(), modbusMaster); + + connect(modbusMaster, &ModbusRtuMaster::connectedChanged, this, [=](bool connected){ + qCDebug(dcModbusRtu()) << modbusMaster << (connected ? "connected" : "disconnected"); + emit modbusRtuMasterChanged(modbusMaster); + }); + + emit modbusRtuMasterAdded(modbusMaster); +} + } diff --git a/libnymea-core/modbus/modbusrtumanager.h b/libnymea-core/modbus/modbusrtumanager.h index fc01aa32..f4529ec4 100644 --- a/libnymea-core/modbus/modbusrtumanager.h +++ b/libnymea-core/modbus/modbusrtumanager.h @@ -40,27 +40,33 @@ namespace nymeaserver { +class SerialPortMonitor; +class ModbusRtuMasterImpl; + class ModbusRtuManager : public QObject { Q_OBJECT public: - enum Error { - ErrorNoError, - ErrorNotFound, - ErrorConnectionFailed + enum ModbusRtuError { + ModbusRtuErrorNoError, + ModbusRtuErrorUuidNotFound, + ModbusRtuErrorHardwareNotFound, + ModbusRtuErrorConnectionFailed }; - Q_ENUM(Error) + Q_ENUM(ModbusRtuError) - explicit ModbusRtuManager(QObject *parent = nullptr); + explicit ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject *parent = nullptr); ~ModbusRtuManager() = default; + SerialPortMonitor *serialPortMonitor() const; + QList modbusRtuMasters() const; bool hasModbusRtuMaster(const QUuid &modbusUuid) const; ModbusRtuMaster *getModbusRtuMaster(const QUuid &modbusUuid); - QPair addNewModbusRtuMaster(const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits); - Error reconfigureRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits); - Error removeModbusRtuMaster(const QUuid &modbusUuid); + QPair addNewModbusRtuMaster(const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits); + ModbusRtuError reconfigureModbusRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits); + ModbusRtuError removeModbusRtuMaster(const QUuid &modbusUuid); signals: void modbusRtuMasterAdded(ModbusRtuMaster *modbusRtuMaster); @@ -69,10 +75,13 @@ signals: private: QHash m_modbusRtuMasters; + SerialPortMonitor *m_serialPortMonitor = nullptr; void loadRtuMasters(); void saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster); + void addModbusRtuMasterInternally(ModbusRtuMasterImpl *modbusRtuMaster); + }; } diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 4f8d9330..ca7cda79 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -54,6 +54,7 @@ #include "zigbee/zigbeemanager.h" #include "modbus/modbusrtumanager.h" +#include "hardware/serialport/serialportmonitor.h" #include @@ -110,8 +111,11 @@ void NymeaCore::init(const QStringList &additionalInterfaces) { qCDebug(dcCore()) << "Create Zigbee Manager"; m_zigbeeManager = new ZigbeeManager(this); + qCDebug(dcCore()) << "Create Serial Port Monitor"; + m_serialPortMonitor = new SerialPortMonitor(this); + qCDebug(dcCore()) << "Create Modbus RTU Manager"; - m_modbusRtuManager = new ModbusRtuManager(this); + m_modbusRtuManager = new ModbusRtuManager(m_serialPortMonitor, this); qCDebug(dcCore) << "Creating Hardware Manager"; m_hardwareManager = new HardwareManagerImplementation(m_platform, m_serverManager->mqttBroker(), m_zigbeeManager, m_modbusRtuManager, this); diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index 2514f7d4..b67ac29a 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -68,6 +68,7 @@ class ScriptEngine; class CloudManager; class ZigbeeManager; class ModbusRtuManager; +class SerialPortMonitor; class NymeaCore : public QObject { @@ -153,6 +154,7 @@ private: System *m_system; ExperienceManager *m_experienceManager; ZigbeeManager *m_zigbeeManager; + SerialPortMonitor *m_serialPortMonitor; ModbusRtuManager *m_modbusRtuManager; QList m_executingRules;