From 64d24d4e7473a5ee2d5c92249f7ed8d40ce8240c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 15 Feb 2021 12:32:44 +0100 Subject: [PATCH] Add modbus RTU hardware resource support --- libnymea-app/libnymea-app-core.h | 10 + libnymea-app/libnymea-app.pri | 12 + libnymea-app/modbus/modbusrtumanager.cpp | 251 ++++++++++++ libnymea-app/modbus/modbusrtumanager.h | 98 +++++ libnymea-app/modbus/modbusrtumaster.cpp | 130 +++++++ libnymea-app/modbus/modbusrtumaster.h | 93 +++++ libnymea-app/modbus/modbusrtumasters.cpp | 169 ++++++++ libnymea-app/modbus/modbusrtumasters.h | 81 ++++ libnymea-app/types/serialport.cpp | 114 ++++++ libnymea-app/types/serialport.h | 93 +++++ libnymea-app/types/serialports.cpp | 109 ++++++ libnymea-app/types/serialports.h | 74 ++++ libnymea-app/types/serialportsproxy.cpp | 69 ++++ libnymea-app/types/serialportsproxy.h | 61 +++ nymea-app/images.qrc | 1 + nymea-app/resources.qrc | 3 + nymea-app/ui/SettingsPage.qml | 17 + nymea-app/ui/images/modbus.svg | 224 +++++++++++ .../ui/system/ModbusRtuAddMasterPage.qml | 249 ++++++++++++ .../system/ModbusRtuReconfigureMasterPage.qml | 284 ++++++++++++++ nymea-app/ui/system/ModbusRtuSettingsPage.qml | 366 ++++++++++++++++++ 21 files changed, 2508 insertions(+) create mode 100644 libnymea-app/modbus/modbusrtumanager.cpp create mode 100644 libnymea-app/modbus/modbusrtumanager.h create mode 100644 libnymea-app/modbus/modbusrtumaster.cpp create mode 100644 libnymea-app/modbus/modbusrtumaster.h create mode 100644 libnymea-app/modbus/modbusrtumasters.cpp create mode 100644 libnymea-app/modbus/modbusrtumasters.h create mode 100644 libnymea-app/types/serialport.cpp create mode 100644 libnymea-app/types/serialport.h create mode 100644 libnymea-app/types/serialports.cpp create mode 100644 libnymea-app/types/serialports.h create mode 100644 libnymea-app/types/serialportsproxy.cpp create mode 100644 libnymea-app/types/serialportsproxy.h create mode 100644 nymea-app/ui/images/modbus.svg create mode 100644 nymea-app/ui/system/ModbusRtuAddMasterPage.qml create mode 100644 nymea-app/ui/system/ModbusRtuReconfigureMasterPage.qml create mode 100644 nymea-app/ui/system/ModbusRtuSettingsPage.qml diff --git a/libnymea-app/libnymea-app-core.h b/libnymea-app/libnymea-app-core.h index af5ad46e..e8881c54 100644 --- a/libnymea-app/libnymea-app-core.h +++ b/libnymea-app/libnymea-app-core.h @@ -130,6 +130,9 @@ #include "applogcontroller.h" #include "tagwatcher.h" #include "appdata.h" +#include "modbus/modbusrtumanager.h" +#include "modbus/modbusrtumasters.h" +#include "types/serialportsproxy.h" #include @@ -312,6 +315,13 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNetwork", "Get it from the ZigbeeManager"); qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNetworks", "Get it from the ZigbeeManager"); + qmlRegisterType(uri, 1, 0, "ModbusRtuManager"); + qmlRegisterUncreatableType(uri, 1, 0, "ModbusRtuMaster", "Get it from the ModbusRtuMasters"); + qmlRegisterUncreatableType(uri, 1, 0, "ModbusRtuMasters", "Get it from the ModbusRtuManager"); + qmlRegisterUncreatableType(uri, 1, 0, "SerialPort", "Get it from the SerialPorts"); + qmlRegisterUncreatableType(uri, 1, 0, "SerialPorts", "Get it from the apropriate manager object."); + qmlRegisterType(uri, 1, 0, "SerialPortsProxy"); + qmlRegisterType(uri, 1, 0, "NetworkManager"); qmlRegisterUncreatableType(uri, 1, 0, "NetworkDevices", "Get it from NetworkManager"); qmlRegisterUncreatableType(uri, 1, 0, "WiredNetworkDevices", "Get it from NetworkManager"); diff --git a/libnymea-app/libnymea-app.pri b/libnymea-app/libnymea-app.pri index 7be3a0f3..131b8068 100644 --- a/libnymea-app/libnymea-app.pri +++ b/libnymea-app/libnymea-app.pri @@ -26,6 +26,12 @@ SOURCES += \ $${PWD}/logging.cpp \ $${PWD}/applogcontroller.cpp \ $${PWD}/wifisetup/btwifisetup.cpp \ + $$PWD/modbus/modbusrtumanager.cpp \ + $$PWD/modbus/modbusrtumaster.cpp \ + $$PWD/modbus/modbusrtumasters.cpp \ + $$PWD/types/serialport.cpp \ + $$PWD/types/serialports.cpp \ + $$PWD/types/serialportsproxy.cpp \ $${PWD}/configuration/networkmanager.cpp \ $${PWD}/engine.cpp \ $${PWD}/models/barseriesadapter.cpp \ @@ -174,6 +180,12 @@ HEADERS += \ $${PWD}/logging.h \ $${PWD}/applogcontroller.h \ $${PWD}/wifisetup/btwifisetup.h \ + $$PWD/modbus/modbusrtumanager.h \ + $$PWD/modbus/modbusrtumaster.h \ + $$PWD/modbus/modbusrtumasters.h \ + $$PWD/types/serialport.h \ + $$PWD/types/serialports.h \ + $$PWD/types/serialportsproxy.h \ $${PWD}/configuration/networkmanager.h \ $${PWD}/engine.h \ $${PWD}/models/barseriesadapter.h \ diff --git a/libnymea-app/modbus/modbusrtumanager.cpp b/libnymea-app/modbus/modbusrtumanager.cpp new file mode 100644 index 00000000..e588dac2 --- /dev/null +++ b/libnymea-app/modbus/modbusrtumanager.cpp @@ -0,0 +1,251 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 "modbusrtumanager.h" +#include "engine.h" +#include "modbusrtumaster.h" +#include "modbusrtumasters.h" + +#include "jsonrpc/jsonrpcclient.h" + +#include + +ModbusRtuManager::ModbusRtuManager(QObject *parent) : + JsonHandler(parent), + m_serialPorts(new SerialPorts(this)), + m_modbusRtuMasters(new ModbusRtuMasters(this)) +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); +} + +ModbusRtuManager::~ModbusRtuManager() +{ + if (m_engine) { + m_engine->jsonRpcClient()->unregisterNotificationHandler(this); + } +} + +QString ModbusRtuManager::nameSpace() const +{ + return "ModbusRtu"; +} + +Engine *ModbusRtuManager::engine() const +{ + return m_engine; +} + +void ModbusRtuManager::setEngine(Engine *engine) +{ + if (m_engine == engine) + return; + + if (m_engine) { + m_engine->jsonRpcClient()->unregisterNotificationHandler(this); + } + + m_engine = engine; + emit engineChanged(); + + if (m_engine) { + init(); + } +} + +bool ModbusRtuManager::supported() const +{ + return m_supported; +} + +SerialPorts *ModbusRtuManager::serialPorts() const +{ + return m_serialPorts; +} + +ModbusRtuMasters *ModbusRtuManager::modbusRtuMasters() const +{ + return m_modbusRtuMasters; +} + +int ModbusRtuManager::addModbusRtuMaster(const QString &serialPort, qint32 baudrate, SerialPort::SerialPortParity parity, SerialPort::SerialPortDataBits dataBits, SerialPort::SerialPortStopBits stopBits) +{ + QVariantMap params; + params.insert("serialPort", serialPort); + params.insert("baudrate", baudrate); + params.insert("parity", QMetaEnum::fromType().valueToKey(parity)); + params.insert("dataBits", QMetaEnum::fromType().valueToKey(dataBits)); + params.insert("stopBits", QMetaEnum::fromType().valueToKey(stopBits)); + return m_engine->jsonRpcClient()->sendCommand("ModbusRtu.AddModbusRtuMaster", params, this, "addModbusRtuMasterResponse"); +} + +int ModbusRtuManager::removeModbusRtuMaster(const QUuid &modbusUuid) +{ + QVariantMap params; + params.insert("modbusUuid", modbusUuid); + return m_engine->jsonRpcClient()->sendCommand("ModbusRtu.RemoveModbusRtuMaster", params, this, "removeModbusRtuMasterResponse"); +} + +int ModbusRtuManager::reconfigureModbusRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, SerialPort::SerialPortParity parity, SerialPort::SerialPortDataBits dataBits, SerialPort::SerialPortStopBits stopBits) +{ + QVariantMap params; + params.insert("modbusUuid", modbusUuid); + params.insert("serialPort", serialPort); + params.insert("baudrate", baudrate); + params.insert("parity", QMetaEnum::fromType().valueToKey(parity)); + params.insert("dataBits", QMetaEnum::fromType().valueToKey(dataBits)); + params.insert("stopBits", QMetaEnum::fromType().valueToKey(stopBits)); + return m_engine->jsonRpcClient()->sendCommand("ModbusRtu.ReconfigureModbusRtuMaster", params, this, "reconfigureModbusRtuMasterResponse"); +} + +void ModbusRtuManager::init() +{ + m_serialPorts->clear(); + m_modbusRtuMasters->clear(); + + m_engine->jsonRpcClient()->registerNotificationHandler(this, "notificationReceived"); + m_engine->jsonRpcClient()->sendCommand("ModbusRtu.GetModbusRtuMasters", this, "getModbusRtuMastersResponse"); +} + +ModbusRtuMaster *ModbusRtuManager::unpackModbusRtuMaster(const QVariantMap &modbusRtuMasterMap) +{ + ModbusRtuMaster *modbusMaster = new ModbusRtuMaster(this); + modbusMaster->setModbusUuid(modbusRtuMasterMap.value("modbusUuid").toUuid()); + modbusMaster->setConnected(modbusRtuMasterMap.value("connected").toBool()); + modbusMaster->setSerialPort(modbusRtuMasterMap.value("serialPort").toString()); + modbusMaster->setBaudrate(modbusRtuMasterMap.value("baudrate").toInt()); + modbusMaster->setParity(SerialPort::stringToSerialPortParity(modbusRtuMasterMap.value("parity").toString())); + modbusMaster->setStopBits(SerialPort::stringToSerialPortStopBits(modbusRtuMasterMap.value("stopBits").toString())); + modbusMaster->setDataBits(SerialPort::stringToSerialPortDataBits(modbusRtuMasterMap.value("dataBits").toString())); + return modbusMaster; +} + +void ModbusRtuManager::notificationReceived(const QVariantMap ¬ification) +{ + QString notificationString = notification.value("notification").toString(); + qDebug() << "Received notification" << notificationString << endl << notification; + if (notificationString == "ModbusRtu.SerialPortAdded") { + QVariantMap serialPortMap = notification.value("params").toMap().value("serialPort").toMap(); + m_serialPorts->addSerialPort(SerialPort::unpackSerialPort(serialPortMap, m_serialPorts)); + return; + } + + if (notificationString == "ModbusRtu.SerialPortRemoved") { + QVariantMap serialPortMap = notification.value("params").toMap().value("serialPort").toMap(); + SerialPort *serialPort = SerialPort::unpackSerialPort(serialPortMap, this); + m_serialPorts->removeSerialPort(serialPort->systemLocation()); + serialPort->deleteLater(); + return; + } + + if (notificationString == "ModbusRtu.ModbusRtuMasterAdded") { + QVariantMap modbusRtuMasterMap = notification.value("params").toMap().value("modbusRtuMaster").toMap(); + ModbusRtuMaster *modbusRtuMaster = unpackModbusRtuMaster(modbusRtuMasterMap); + m_modbusRtuMasters->addModbusRtuMaster(modbusRtuMaster); + return; + } + + if (notificationString == "ModbusRtu.ModbusRtuMasterRemoved") { + QUuid modbusUuid = notification.value("params").toMap().value("modbusUuid").toUuid(); + m_modbusRtuMasters->removeModbusRtuMaster(modbusUuid); + return; + } + + if (notificationString == "ModbusRtu.ModbusRtuMasterChanged") { + QVariantMap modbusRtuMasterMap = notification.value("params").toMap().value("modbusRtuMaster").toMap(); + ModbusRtuMaster *modbusRtuMaster = unpackModbusRtuMaster(modbusRtuMasterMap); + qDebug() << "Modbus master changed" << modbusRtuMaster; + ModbusRtuMaster *currentModbusRtuMaster = m_modbusRtuMasters->getModbusRtuMaster(modbusRtuMaster->modbusUuid()); + if (!currentModbusRtuMaster) { + qWarning() << "Got modbus changed signal but there is no such modbus interface. Ignoring notification"; + return; + } + + qDebug() << "Update modbus values" << currentModbusRtuMaster; + currentModbusRtuMaster->setSerialPort(modbusRtuMaster->serialPort()); + currentModbusRtuMaster->setBaudrate(modbusRtuMaster->baudrate()); + currentModbusRtuMaster->setParity(modbusRtuMaster->parity()); + currentModbusRtuMaster->setDataBits(modbusRtuMaster->dataBits()); + currentModbusRtuMaster->setStopBits(modbusRtuMaster->stopBits()); + currentModbusRtuMaster->setConnected(modbusRtuMaster->connected()); + modbusRtuMaster->deleteLater(); + + return; + } +} + +void ModbusRtuManager::getSerialPortsResponse(int commandId, const QVariantMap ¶ms) +{ + qDebug() << "Get serial ports response" << commandId << params; + m_serialPorts->clear(); + + foreach (const QVariant &serialPortVariant, params.value("serialPorts").toList()) { + m_serialPorts->addSerialPort(SerialPort::unpackSerialPort(serialPortVariant.toMap(), m_serialPorts)); + } +} + +void ModbusRtuManager::getModbusRtuMastersResponse(int commandId, const QVariantMap ¶ms) +{ + qDebug() << "Get modbus RTU masters response" << commandId << params; + + QString error = params.value("modbusError").toString(); + if (error == "ModbusRtuErrorNoError") { + m_supported = true; + emit supportedChanged(m_supported); + + m_modbusRtuMasters->clear(); + foreach (const QVariant &modbusRtuMasterVariant, params.value("modbusRtuMasters").toList()) { + m_modbusRtuMasters->addModbusRtuMaster(unpackModbusRtuMaster(modbusRtuMasterVariant.toMap())); + } + + m_engine->jsonRpcClient()->sendCommand("ModbusRtu.GetSerialPorts", this, "getSerialPortsResponse"); + } else { + qWarning() << "Modbus is not supported on this platform"; + } +} + +void ModbusRtuManager::addModbusRtuMasterResponse(int commandId, const QVariantMap ¶ms) +{ + qDebug() << "Add modbus RTU master response" << commandId << params; + emit addModbusRtuMasterReply(commandId, params.value("modbusError").toString(), params.value("modbusUuid").toUuid()); +} + +void ModbusRtuManager::removeModbusRtuMasterResponse(int commandId, const QVariantMap ¶ms) +{ + qDebug() << "Remove modbus RTU master response" << commandId << params; + emit removeModbusRtuMasterReply(commandId, params.value("modbusError").toString()); +} + +void ModbusRtuManager::reconfigureModbusRtuMasterResponse(int commandId, const QVariantMap ¶ms) +{ + qDebug() << "Reconfigure modbus RTU master response" << commandId << params; + emit reconfigureModbusRtuMasterReply(commandId, params.value("modbusError").toString()); +} diff --git a/libnymea-app/modbus/modbusrtumanager.h b/libnymea-app/modbus/modbusrtumanager.h new file mode 100644 index 00000000..49957aa4 --- /dev/null +++ b/libnymea-app/modbus/modbusrtumanager.h @@ -0,0 +1,98 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 MODBUSRTUMANAGER_H +#define MODBUSRTUMANAGER_H + +#include + +#include "types/serialports.h" +#include "jsonrpc/jsonhandler.h" + +class Engine; +class JsonRpcClient; +class ModbusRtuMaster; +class ModbusRtuMasters; + +class ModbusRtuManager : public JsonHandler +{ + Q_OBJECT + Q_PROPERTY(Engine *engine READ engine WRITE setEngine NOTIFY engineChanged) + Q_PROPERTY(SerialPorts *serialPorts READ serialPorts CONSTANT) + Q_PROPERTY(ModbusRtuMasters *modbusRtuMasters READ modbusRtuMasters CONSTANT) + Q_PROPERTY(bool supported READ supported NOTIFY supportedChanged) + +public: + explicit ModbusRtuManager(QObject *parent = nullptr); + ~ModbusRtuManager(); + + QString nameSpace() const override; + + Engine *engine() const; + void setEngine(Engine *engine); + + bool supported() const; + + SerialPorts *serialPorts() const; + ModbusRtuMasters *modbusRtuMasters() const; + + Q_INVOKABLE int addModbusRtuMaster(const QString &serialPort, qint32 baudrate, SerialPort::SerialPortParity parity, SerialPort::SerialPortDataBits dataBits, SerialPort::SerialPortStopBits stopBits); + Q_INVOKABLE int removeModbusRtuMaster(const QUuid &modbusUuid); + Q_INVOKABLE int reconfigureModbusRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, SerialPort::SerialPortParity parity, SerialPort::SerialPortDataBits dataBits, SerialPort::SerialPortStopBits stopBits); + +signals: + void engineChanged(); + void supportedChanged(bool supported); + void addModbusRtuMasterReply(int commandId, const QString &error, const QUuid &modbusUuid); + void removeModbusRtuMasterReply(int commandId, const QString &error); + void reconfigureModbusRtuMasterReply(int commandId, const QString &error); + +private: + Engine* m_engine = nullptr; + SerialPorts *m_serialPorts = nullptr; + ModbusRtuMasters *m_modbusRtuMasters = nullptr; + bool m_supported = false; + + void init(); + + ModbusRtuMaster *unpackModbusRtuMaster(const QVariantMap &modbusRtuMasterMap); + + Q_INVOKABLE void notificationReceived(const QVariantMap ¬ification); + + Q_INVOKABLE void getSerialPortsResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void getModbusRtuMastersResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void addModbusRtuMasterResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void removeModbusRtuMasterResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void reconfigureModbusRtuMasterResponse(int commandId, const QVariantMap ¶ms); + + +}; + +#endif // MODBUSRTUMANAGER_H diff --git a/libnymea-app/modbus/modbusrtumaster.cpp b/libnymea-app/modbus/modbusrtumaster.cpp new file mode 100644 index 00000000..7d32aa29 --- /dev/null +++ b/libnymea-app/modbus/modbusrtumaster.cpp @@ -0,0 +1,130 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 "modbusrtumaster.h" + +ModbusRtuMaster::ModbusRtuMaster(QObject *parent) : QObject(parent) +{ + +} + +QUuid ModbusRtuMaster::modbusUuid() const +{ + return m_modbusUuid; +} + +void ModbusRtuMaster::setModbusUuid(const QUuid &modbusUuid) +{ + m_modbusUuid = modbusUuid; +} + +QString ModbusRtuMaster::serialPort() const +{ + return m_serialPort; +} + +void ModbusRtuMaster::setSerialPort(const QString &serialPort) +{ + if (m_serialPort == serialPort) + return; + + m_serialPort = serialPort; + emit serialPortChanged(m_serialPort); +} + +qint32 ModbusRtuMaster::baudrate() const +{ + return m_baudrate; +} + +void ModbusRtuMaster::setBaudrate(qint32 baudrate) +{ + if (m_baudrate == baudrate) + return; + + m_baudrate = baudrate; + emit baudrateChanged(m_baudrate); +} + +SerialPort::SerialPortParity ModbusRtuMaster::parity() const +{ + return m_parity; +} + +void ModbusRtuMaster::setParity(SerialPort::SerialPortParity parity) +{ + if (m_parity == parity) + return; + + m_parity = parity; + emit parityChanged(m_parity); +} + +SerialPort::SerialPortDataBits ModbusRtuMaster::dataBits() const +{ + return m_dataBits; +} + +void ModbusRtuMaster::setDataBits(SerialPort::SerialPortDataBits dataBits) +{ + if (m_dataBits == dataBits) + return; + + m_dataBits = dataBits; + emit dataBitsChanged(m_dataBits); +} + +SerialPort::SerialPortStopBits ModbusRtuMaster::stopBits() const +{ + return m_stopBits; +} + +void ModbusRtuMaster::setStopBits(SerialPort::SerialPortStopBits stopBits) +{ + if (m_stopBits == stopBits) + return; + + m_stopBits = stopBits; + emit stopBitsChanged(m_stopBits); +} + +bool ModbusRtuMaster::connected() const +{ + return m_connected; +} + +void ModbusRtuMaster::setConnected(bool connected) +{ + if (m_connected == connected) + return; + + m_connected = connected; + emit connectedChanged(m_connected); +} diff --git a/libnymea-app/modbus/modbusrtumaster.h b/libnymea-app/modbus/modbusrtumaster.h new file mode 100644 index 00000000..51498c28 --- /dev/null +++ b/libnymea-app/modbus/modbusrtumaster.h @@ -0,0 +1,93 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 MODBUSRTUMASTER_H +#define MODBUSRTUMASTER_H + +#include +#include + +#include "types/serialport.h" + +class ModbusRtuMaster : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid modbusUuid READ modbusUuid CONSTANT) + Q_PROPERTY(QString serialPort READ serialPort NOTIFY serialPortChanged) + Q_PROPERTY(qint32 baudrate READ baudrate NOTIFY baudrateChanged) + Q_PROPERTY(SerialPort::SerialPortParity parity READ parity NOTIFY parityChanged) + Q_PROPERTY(SerialPort::SerialPortDataBits dataBits READ dataBits NOTIFY dataBitsChanged) + Q_PROPERTY(SerialPort::SerialPortStopBits stopBits READ stopBits NOTIFY stopBitsChanged) + Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) + +public: + explicit ModbusRtuMaster(QObject *parent = nullptr); + + QUuid modbusUuid() const; + void setModbusUuid(const QUuid &modbusUuid); + + QString serialPort() const; + void setSerialPort(const QString &serialPort); + + qint32 baudrate() const; + void setBaudrate(qint32 baudrate); + + SerialPort::SerialPortParity parity() const; + void setParity(SerialPort::SerialPortParity parity); + + SerialPort::SerialPortDataBits dataBits() const; + void setDataBits(SerialPort::SerialPortDataBits dataBits); + + SerialPort::SerialPortStopBits stopBits() const; + void setStopBits(SerialPort::SerialPortStopBits stopBits); + + bool connected() const; + void setConnected(bool connected); + +signals: + void connectedChanged(bool connected); + void serialPortChanged(const QString &serialPort); + void baudrateChanged(quint32 baudrate); + void parityChanged(SerialPort::SerialPortParity parity); + void dataBitsChanged(SerialPort::SerialPortDataBits dataBits); + void stopBitsChanged(SerialPort::SerialPortStopBits stopBits); + +private: + QUuid m_modbusUuid; + QString m_serialPort; + qint32 m_baudrate; + SerialPort::SerialPortParity m_parity; + SerialPort::SerialPortDataBits m_dataBits; + SerialPort::SerialPortStopBits m_stopBits; + bool m_connected = false; + +}; + +#endif // MODBUSRTUMASTER_H diff --git a/libnymea-app/modbus/modbusrtumasters.cpp b/libnymea-app/modbus/modbusrtumasters.cpp new file mode 100644 index 00000000..e662d1ad --- /dev/null +++ b/libnymea-app/modbus/modbusrtumasters.cpp @@ -0,0 +1,169 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 "modbusrtumasters.h" + +ModbusRtuMasters::ModbusRtuMasters(QObject *parent) : QAbstractListModel(parent) +{ + +} + +QList ModbusRtuMasters::modbusRtuMasters() const +{ + return m_modbusRtuMasters; +} + +int ModbusRtuMasters::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_modbusRtuMasters.count(); +} + +QVariant ModbusRtuMasters::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleUuid: + return m_modbusRtuMasters.at(index.row())->modbusUuid(); + case RoleSerialPort: + return m_modbusRtuMasters.at(index.row())->serialPort(); + case RoleBaudrate: + return m_modbusRtuMasters.at(index.row())->baudrate(); + case RoleParity: + return m_modbusRtuMasters.at(index.row())->parity(); + case RoleDataBits: + return m_modbusRtuMasters.at(index.row())->dataBits(); + case RoleStopBits: + return m_modbusRtuMasters.at(index.row())->stopBits(); + case RoleConnected: + return m_modbusRtuMasters.at(index.row())->connected(); + } + return QVariant(); +} + +QHash ModbusRtuMasters::roleNames() const +{ + QHash roles; + roles.insert(RoleUuid, "modbusUuid"); + roles.insert(RoleSerialPort, "serialPort"); + roles.insert(RoleBaudrate, "baudrate"); + roles.insert(RoleParity, "parity"); + roles.insert(RoleDataBits, "dataBits"); + roles.insert(RoleStopBits, "stopBits"); + roles.insert(RoleConnected, "connected"); + return roles; +} + +void ModbusRtuMasters::addModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster) +{ + modbusRtuMaster->setParent(this); + + connect(modbusRtuMaster, &ModbusRtuMaster::serialPortChanged, this, [=](const QString &serialPort) { + Q_UNUSED(serialPort) + QModelIndex idx = index(m_modbusRtuMasters.indexOf(modbusRtuMaster), 0); + emit dataChanged(idx, idx, {RoleSerialPort}); + }); + + connect(modbusRtuMaster, &ModbusRtuMaster::baudrateChanged, this, [=](qint32 baudrate) { + Q_UNUSED(baudrate) + QModelIndex idx = index(m_modbusRtuMasters.indexOf(modbusRtuMaster), 0); + emit dataChanged(idx, idx, {RoleBaudrate}); + }); + + connect(modbusRtuMaster, &ModbusRtuMaster::parityChanged, this, [=](SerialPort::SerialPortParity parity) { + Q_UNUSED(parity) + QModelIndex idx = index(m_modbusRtuMasters.indexOf(modbusRtuMaster), 0); + emit dataChanged(idx, idx, {RoleParity}); + }); + + connect(modbusRtuMaster, &ModbusRtuMaster::dataBitsChanged, this, [=](SerialPort::SerialPortDataBits dataBits) { + Q_UNUSED(dataBits) + QModelIndex idx = index(m_modbusRtuMasters.indexOf(modbusRtuMaster), 0); + emit dataChanged(idx, idx, {RoleDataBits}); + }); + + connect(modbusRtuMaster, &ModbusRtuMaster::stopBitsChanged, this, [=](SerialPort::SerialPortStopBits stopBites) { + Q_UNUSED(stopBites) + QModelIndex idx = index(m_modbusRtuMasters.indexOf(modbusRtuMaster), 0); + emit dataChanged(idx, idx, {RoleStopBits}); + }); + + connect(modbusRtuMaster, &ModbusRtuMaster::connectedChanged, this, [=](bool connected) { + Q_UNUSED(connected) + QModelIndex idx = index(m_modbusRtuMasters.indexOf(modbusRtuMaster), 0); + emit dataChanged(idx, idx, {RoleConnected}); + }); + + beginInsertRows(QModelIndex(), m_modbusRtuMasters.count(), m_modbusRtuMasters.count()); + m_modbusRtuMasters.append(modbusRtuMaster); + endInsertRows(); + + emit countChanged(); +} + +void ModbusRtuMasters::removeModbusRtuMaster(const QUuid &modbusUuid) +{ + for (int i = 0; i < m_modbusRtuMasters.count(); i++) { + if (m_modbusRtuMasters.at(i)->modbusUuid() == modbusUuid) { + beginRemoveRows(QModelIndex(), i, i); + m_modbusRtuMasters.takeAt(i)->deleteLater(); + endRemoveRows(); + emit countChanged(); + return; + } + } +} + +void ModbusRtuMasters::clear() +{ + beginResetModel(); + qDeleteAll(m_modbusRtuMasters); + m_modbusRtuMasters.clear(); + endResetModel(); + emit countChanged(); +} + +ModbusRtuMaster *ModbusRtuMasters::get(int index) const +{ + if (index < 0 || index >= m_modbusRtuMasters.count()) { + return nullptr; + } + return m_modbusRtuMasters.at(index); +} + +ModbusRtuMaster *ModbusRtuMasters::getModbusRtuMaster(const QUuid &modbusUuid) const +{ + foreach (ModbusRtuMaster *modbusRtuMaster, m_modbusRtuMasters) { + if (modbusRtuMaster->modbusUuid() == modbusUuid) { + return modbusRtuMaster; + } + } + + return nullptr; +} diff --git a/libnymea-app/modbus/modbusrtumasters.h b/libnymea-app/modbus/modbusrtumasters.h new file mode 100644 index 00000000..2d89b896 --- /dev/null +++ b/libnymea-app/modbus/modbusrtumasters.h @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 MODBUSRTUMASTERS_H +#define MODBUSRTUMASTERS_H + +#include +#include + +#include "modbusrtumaster.h" + +class ModbusRtuMasters : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + +public: + enum Roles { + RoleUuid, + RoleSerialPort, + RoleBaudrate, + RoleParity, + RoleDataBits, + RoleStopBits, + RoleConnected + }; + Q_ENUM(Roles) + + explicit ModbusRtuMasters(QObject *parent = nullptr); + virtual ~ModbusRtuMasters() override = default; + + QList modbusRtuMasters() const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + void addModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster); + void removeModbusRtuMaster(const QUuid &modbusUuid); + + void clear(); + + Q_INVOKABLE virtual ModbusRtuMaster *get(int index) const; + Q_INVOKABLE ModbusRtuMaster *getModbusRtuMaster(const QUuid &modbusUuid) const; + +signals: + void countChanged(); + +private: + QList m_modbusRtuMasters; + +}; + +#endif // MODBUSRTUMASTERS_H diff --git a/libnymea-app/types/serialport.cpp b/libnymea-app/types/serialport.cpp new file mode 100644 index 00000000..26ee7b2d --- /dev/null +++ b/libnymea-app/types/serialport.cpp @@ -0,0 +1,114 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 "serialport.h" + +SerialPort::SerialPort(const QString &systemLocation, const QString &manufacturer, const QString &description, const QString &serialNumber, QObject *parent) : + QObject(parent), + m_systemLocation(systemLocation), + m_manufacturer(manufacturer), + m_description(description), + m_serialNumber(serialNumber) +{ + +} + +QString SerialPort::systemLocation() const +{ + return m_systemLocation; +} + +QString SerialPort::manufacturer() const +{ + return m_manufacturer; +} + +QString SerialPort::description() const +{ + return m_description; +} + +QString SerialPort::serialNumber() const +{ + return m_serialNumber; +} + +SerialPort *SerialPort::unpackSerialPort(const QVariantMap &serialPortMap, QObject *parent) +{ + return new SerialPort(serialPortMap.value("systemLocation").toString(), + serialPortMap.value("manufacturer").toString(), + serialPortMap.value("description").toString(), + serialPortMap.value("serialNumber").toString(), parent); +} + +SerialPort::SerialPortParity SerialPort::stringToSerialPortParity(const QString &parityString) +{ + if (parityString == "SerialPortParityNoParity") { + return SerialPort::SerialPortParityNoParity; + } else if (parityString == "SerialPortParityEvenParity") { + return SerialPort::SerialPortParityEvenParity; + } else if (parityString == "SerialPortParityOddParity") { + return SerialPort::SerialPortParityOddParity; + } else if (parityString == "SerialPortParitySpaceParity") { + return SerialPort::SerialPortParitySpaceParity; + } else if (parityString == "SerialPortParityMarkParity") { + return SerialPort::SerialPortParityMarkParity; + } + + return SerialPort::SerialPortParityUnknownParity; +} + +SerialPort::SerialPortDataBits SerialPort::stringToSerialPortDataBits(const QString &dataBitsString) +{ + if (dataBitsString == "SerialPortDataBitsData5") { + return SerialPort::SerialPortDataBitsData5; + } else if (dataBitsString == "SerialPortDataBitsData6") { + return SerialPort::SerialPortDataBitsData6; + } else if (dataBitsString == "SerialPortDataBitsData7") { + return SerialPort::SerialPortDataBitsData7; + } else if (dataBitsString == "SerialPortDataBitsData8") { + return SerialPort::SerialPortDataBitsData7; + } + + return SerialPort::SerialPortDataBitsUnknownDataBits; +} + +SerialPort::SerialPortStopBits SerialPort::stringToSerialPortStopBits(const QString &stopBitsString) +{ + if (stopBitsString == "SerialPortStopBitsOneStop") { + return SerialPort::SerialPortStopBitsOneStop; + } else if (stopBitsString == "SerialPortStopBitsOneAndHalfStop") { + return SerialPort::SerialPortStopBitsOneAndHalfStop; + } else if (stopBitsString == "SerialPortStopBitsTwoStop") { + return SerialPort::SerialPortStopBitsTwoStop; + } + + return SerialPort::SerialPortStopBitsUnknownStopBits; +} diff --git a/libnymea-app/types/serialport.h b/libnymea-app/types/serialport.h new file mode 100644 index 00000000..76839aab --- /dev/null +++ b/libnymea-app/types/serialport.h @@ -0,0 +1,93 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 SERIALPORT_H +#define SERIALPORT_H + +#include +#include + +class SerialPort : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString systemLocation READ systemLocation CONSTANT) + Q_PROPERTY(QString manufacturer READ manufacturer CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QString serialNumber READ serialNumber CONSTANT) + +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) + + explicit SerialPort(const QString &systemLocation, const QString &manufacturer, const QString &description, const QString &serialNumber, QObject *parent = nullptr); + + QString systemLocation() const; + QString manufacturer() const; + QString description() const; + QString serialNumber() const; + + static SerialPort *unpackSerialPort(const QVariantMap &serialPortMap, QObject *parent); + static SerialPort::SerialPortParity stringToSerialPortParity(const QString &parityString); + static SerialPort::SerialPortDataBits stringToSerialPortDataBits(const QString &dataBitsString); + static SerialPort::SerialPortStopBits stringToSerialPortStopBits(const QString &stopBitsString); + +private: + QString m_systemLocation; + QString m_manufacturer; + QString m_description; + QString m_serialNumber; + +}; + +#endif // SERIALPORT_H diff --git a/libnymea-app/types/serialports.cpp b/libnymea-app/types/serialports.cpp new file mode 100644 index 00000000..5a5805f9 --- /dev/null +++ b/libnymea-app/types/serialports.cpp @@ -0,0 +1,109 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 "serialports.h" + +SerialPorts::SerialPorts(QObject *parent) : QAbstractListModel(parent) +{ + +} + +int SerialPorts::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_serialPorts.count(); +} + +QVariant SerialPorts::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleSystmLocation: + return m_serialPorts.at(index.row())->systemLocation(); + case RoleManufacturer: + return m_serialPorts.at(index.row())->manufacturer(); + case RoleDescription: + return m_serialPorts.at(index.row())->description(); + case RoleSerialNumber: + return m_serialPorts.at(index.row())->serialNumber(); + } + return QVariant(); +} + +QHash SerialPorts::roleNames() const +{ + QHash roles; + roles.insert(RoleSystmLocation, "systemLocation"); + roles.insert(RoleManufacturer, "manufacturer"); + roles.insert(RoleDescription, "description"); + roles.insert(RoleSerialNumber, "serialNumber"); + return roles; +} + +void SerialPorts::addSerialPort(SerialPort *serialPort) +{ + serialPort->setParent(this); + + beginInsertRows(QModelIndex(), m_serialPorts.count(), m_serialPorts.count()); + m_serialPorts.append(serialPort); + endInsertRows(); + + emit countChanged(); +} + +void SerialPorts::removeSerialPort(const QString &systemLocation) +{ + for (int i = 0; i < m_serialPorts.count(); i++) { + if (m_serialPorts.at(i)->systemLocation() == systemLocation) { + beginRemoveRows(QModelIndex(), i, i); + m_serialPorts.takeAt(i)->deleteLater(); + endRemoveRows(); + emit countChanged(); + return; + } + } +} + +void SerialPorts::clear() +{ + beginResetModel(); + qDeleteAll(m_serialPorts); + m_serialPorts.clear(); + endResetModel(); + emit countChanged(); +} + +SerialPort *SerialPorts::get(int index) const +{ + if (index < 0 || index >= m_serialPorts.count()) { + return nullptr; + } + + return m_serialPorts.at(index); +} diff --git a/libnymea-app/types/serialports.h b/libnymea-app/types/serialports.h new file mode 100644 index 00000000..7a2a8506 --- /dev/null +++ b/libnymea-app/types/serialports.h @@ -0,0 +1,74 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 SERIALPORTS_H +#define SERIALPORTS_H + +#include +#include + +#include "serialport.h" + +class SerialPorts : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + +public: + enum Roles { + RoleSystmLocation, + RoleManufacturer, + RoleDescription, + RoleSerialNumber + }; + Q_ENUM(Roles) + + explicit SerialPorts(QObject *parent = nullptr); + virtual ~SerialPorts() override = default; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + void addSerialPort(SerialPort *serialPort); + void removeSerialPort(const QString &systemLocation); + + void clear(); + + Q_INVOKABLE virtual SerialPort *get(int index) const; + +signals: + void countChanged(); + +protected: + QList m_serialPorts; +}; + +#endif // SERIALPORTS_H diff --git a/libnymea-app/types/serialportsproxy.cpp b/libnymea-app/types/serialportsproxy.cpp new file mode 100644 index 00000000..8d6dc705 --- /dev/null +++ b/libnymea-app/types/serialportsproxy.cpp @@ -0,0 +1,69 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 "serialportsproxy.h" + +SerialPortsProxy::SerialPortsProxy(QObject *parent) : QSortFilterProxyModel(parent) +{ + +} + +SerialPorts *SerialPortsProxy::serialPorts() const +{ + return m_serialPorts; +} + +void SerialPortsProxy::setSerialPorts(SerialPorts *serialPorts) +{ + if (m_serialPorts == serialPorts) + return; + + m_serialPorts = serialPorts; + connect(m_serialPorts, &SerialPorts::countChanged, this, [this](){ + emit countChanged(); + }); + connect(m_serialPorts, &SerialPorts::countChanged, this, [this]() { + sort(0, Qt::DescendingOrder); + }); + + setSourceModel(serialPorts); + setSortRole(SerialPorts::RoleSystmLocation); + sort(0, Qt::DescendingOrder); + emit countChanged(); +} + +SerialPort *SerialPortsProxy::get(int index) const +{ + if (index >= 0 && index < m_serialPorts->rowCount()) { + return m_serialPorts->get(mapToSource(this->index(index, 0)).row()); + } + return nullptr; +} + diff --git a/libnymea-app/types/serialportsproxy.h b/libnymea-app/types/serialportsproxy.h new file mode 100644 index 00000000..a6f16d05 --- /dev/null +++ b/libnymea-app/types/serialportsproxy.h @@ -0,0 +1,61 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 SERIALPORTSPROXY_H +#define SERIALPORTSPROXY_H + +#include +#include + +#include "serialports.h" + +class SerialPortsProxy : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + +public: + explicit SerialPortsProxy(QObject *parent = nullptr); + + SerialPorts *serialPorts() const; + void setSerialPorts(SerialPorts *serialPorts); + + Q_INVOKABLE SerialPort* get(int index) const; + +signals: + void countChanged(); + +private: + SerialPorts *m_serialPorts = nullptr; + +}; + +#endif // SERIALPORTSPROXY_H diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index d0ca9cc1..02bf4a33 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -259,5 +259,6 @@ ui/images/nymea-splash.svg ui/images/cleaning-robot.svg ui/images/chart.svg + ui/images/modbus.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 1207ad3d..dce7ce38 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -246,5 +246,8 @@ ui/mainviews/dashboard/DashboardSceneDelegate.qml ui/mainviews/dashboard/DashboardWebViewDelegate.qml ui/components/SelectionTabs.qml + ui/system/ModbusRtuSettingsPage.qml + ui/system/ModbusRtuAddMasterPage.qml + ui/system/ModbusRtuReconfigureMasterPage.qml diff --git a/nymea-app/ui/SettingsPage.qml b/nymea-app/ui/SettingsPage.qml index 97e78c96..ad15932c 100644 --- a/nymea-app/ui/SettingsPage.qml +++ b/nymea-app/ui/SettingsPage.qml @@ -186,6 +186,23 @@ Page { } + Pane { + Layout.fillWidth: true + Material.elevation: layout.isGrid ? 1 : 0 + visible: engine.jsonRpcClient.ensureServerVersion("5.4") + + padding: 0 + NymeaSwipeDelegate { + width: parent.width + iconName: "../images/modbus.svg" + text: qsTr("Modbus RTU") + subText: qsTr("Configure Modbus RTU master interfaces") + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("system/ModbusRtuSettingsPage.qml")) + } + } + Pane { Layout.fillWidth: true Material.elevation: layout.isGrid ? 1 : 0 diff --git a/nymea-app/ui/images/modbus.svg b/nymea-app/ui/images/modbus.svg new file mode 100644 index 00000000..45d6f022 --- /dev/null +++ b/nymea-app/ui/images/modbus.svg @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/system/ModbusRtuAddMasterPage.qml b/nymea-app/ui/system/ModbusRtuAddMasterPage.qml new file mode 100644 index 00000000..7636cdc4 --- /dev/null +++ b/nymea-app/ui/system/ModbusRtuAddMasterPage.qml @@ -0,0 +1,249 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +SettingsPageBase { + id: root + + property ModbusRtuManager modbusRtuManager + property ListModel serialPortBaudrateModel + property ListModel serialPortParityModel + property ListModel serialPortDataBitsModel + property ListModel serialPortStopBitsModel + + header: NymeaHeader { + text: qsTr("Add a new Modbus RTU master") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + SettingsPageSectionHeader { + text: qsTr("Serial ports") + } + + Label { + Layout.fillWidth: true + Layout.topMargin: app.margins + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + wrapMode: Text.WordWrap + text: modbusRtuManager.serialPorts.count !== 0 ? qsTr("Please select one of the following serial ports detected on the system.") : qsTr("There are no serial ports available.\n\nPlease make sure the modbus RTU interface is connected to the system.") + } + + Repeater { + model: modbusRtuManager.serialPorts + delegate: NymeaSwipeDelegate { + Layout.fillWidth: true + iconName: "../images/stock_usb.svg" + text: model.description + (model.manufacturer === "" ? "" : " - " + model.manufacturer) + subText: model.systemLocation + (model.serialNumber === "" ? "" : " - " + model.serialNumber) + onClicked: pageStack.push(configureNewModbusRtuMasterPage, { modbusRtuManager: modbusRtuManager, serialPort: modbusRtuManager.serialPorts.get(index) }) + } + } + + Component { + id: configureNewModbusRtuMasterPage + + SettingsPageBase { + id: root + + property ModbusRtuManager modbusRtuManager + property SerialPort serialPort + busy: d.pendingCommandId != -1 + + header: NymeaHeader { + text: qsTr("Configure modbus RTU master") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + QtObject { + id: d + property int pendingCommandId: -1 + + function addModbusRtuMaster(serialPort, baudRate, parity, dataBits, stopBits) { + d.pendingCommandId = root.modbusRtuManager.addModbusRtuMaster(serialPort, baudRate, parity, dataBits, stopBits) + } + } + + Connections { + target: root.modbusRtuManager + onAddModbusRtuMasterReply: { + if (commandId === d.pendingCommandId) { + d.pendingCommandId = -1 + if (modbusRtuManager.handleModbusError(error)) { + pageStack.pop(); + pageStack.pop(); + } + } + } + } + + + SettingsPageSectionHeader { + text: qsTr("Serial port") + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("System location") + subText: serialPort.systemLocation + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Description") + subText: serialPort.description + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Manufacturer") + subText: serialPort.manufacturer + progressive: false + prominentSubText: false + visible: serialPort.manufacturer !== "" + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Serialnumber") + subText: serialPort.serialNumber + progressive: false + prominentSubText: false + visible: serialPort.serialNumber !== "" + } + + SettingsPageSectionHeader { + text: qsTr("Configuration") + } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + Label { + text: qsTr("Baud rate") + Layout.fillWidth: true + } + + ComboBox { + id: baudRateComboBox + Layout.minimumWidth: 250 + textRole: "text" + enabled: !root.busy + onActivated: console.log("Selected baud rate", currentText, model.get(currentIndex).value) + model: serialPortBaudrateModel + } + } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + + Label { + text: qsTr("Parity") + Layout.fillWidth: true + } + + ComboBox { + id: parityComboBox + textRole: "text" + enabled: !root.busy + Layout.minimumWidth: 250 + onActivated: console.log("Selected parity", currentText, model.get(currentIndex).value) + model: serialPortParityModel + } + } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + + Label { + text: qsTr("Data bits") + Layout.fillWidth: true + } + + ComboBox { + id: dataBitsComboBox + textRole: "text" + enabled: !root.busy + Layout.minimumWidth: 250 + onActivated: console.log("Selected data bits", currentText, model.get(currentIndex).value) + model: serialPortDataBitsModel + Component.onCompleted: { + currentIndex = 3 + } + } + } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + + Label { + text: qsTr("Stop bits") + Layout.fillWidth: true + } + + ComboBox { + id: stopBitsComboBox + textRole: "text" + enabled: !root.busy + Layout.minimumWidth: 250 + onActivated: console.log("Selected stop bits", currentText, model.get(currentIndex).value) + model: serialPortStopBitsModel + } + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Add") + enabled: !root.busy + onClicked: { + var baudrate = serialPortBaudrateModel.get(baudRateComboBox.currentIndex).value + var parity = serialPortParityModel.get(parityComboBox.currentIndex).value + var dataBits = serialPortDataBitsModel.get(dataBitsComboBox.currentIndex).value + var stopBits = serialPortStopBitsModel.get(stopBitsComboBox.currentIndex).value + console.log("Adding modbus RTU with", serialPort.systemLocation, baudrate, parity, dataBits, stopBits) + + d.addModbusRtuMaster(serialPort.systemLocation, baudrate, parity, dataBits, stopBits) + } + } + } + } +} diff --git a/nymea-app/ui/system/ModbusRtuReconfigureMasterPage.qml b/nymea-app/ui/system/ModbusRtuReconfigureMasterPage.qml new file mode 100644 index 00000000..62648945 --- /dev/null +++ b/nymea-app/ui/system/ModbusRtuReconfigureMasterPage.qml @@ -0,0 +1,284 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +SettingsPageBase { + id: root + + property ModbusRtuManager modbusRtuManager + property ModbusRtuMaster modbusRtuMaster + property ListModel serialPortBaudrateModel + property ListModel serialPortParityModel + property ListModel serialPortDataBitsModel + property ListModel serialPortStopBitsModel + + header: NymeaHeader { + text: qsTr("Reconfigure modbus RTU master") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + SettingsPageSectionHeader { + text: qsTr("Serial ports") + } + + Label { + Layout.fillWidth: true + Layout.topMargin: app.margins + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + wrapMode: Text.WordWrap + text: modbusRtuManager.serialPorts.count !== 0 ? qsTr("Please select one of the following serial ports detected on the system.") : qsTr("There are no serial ports available.\n\nPlease make sure the modbus RTU interface is connected to the system.") + } + + Repeater { + model: modbusRtuManager.serialPorts + delegate: NymeaSwipeDelegate { + Layout.fillWidth: true + iconName: "../images/stock_usb.svg" + text: model.description + (model.manufacturer === "" ? "" : " - " + model.manufacturer) + subText: model.systemLocation + (model.serialNumber === "" ? "" : " - " + model.serialNumber) + onClicked: pageStack.push(reconfigureNewModbusRtuMasterPage, { modbusRtuManager: modbusRtuManager, serialPort: modbusRtuManager.serialPorts.get(index), modbusRtuMaster: modbusRtuMaster }) + } + } + + Component { + id: reconfigureNewModbusRtuMasterPage + + SettingsPageBase { + id: root + + property ModbusRtuManager modbusRtuManager + property SerialPort serialPort + property ModbusRtuMaster modbusRtuMaster + + busy: d.pendingCommandId != -1 + + header: NymeaHeader { + text: qsTr("Reconfigure modbus RTU master") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + QtObject { + id: d + property int pendingCommandId: -1 + + function reconfigureModbusRtuMaster(modbusUuid, serialPort, baudRate, parity, dataBits, stopBits) { + d.pendingCommandId = root.modbusRtuManager.reconfigureModbusRtuMaster(modbusUuid, serialPort, baudRate, parity, dataBits, stopBits) + } + } + + Connections { + target: root.modbusRtuManager + onReconfigureModbusRtuMasterReply: { + if (commandId === d.pendingCommandId) { + d.pendingCommandId = -1 + if (modbusRtuManager.handleModbusError(error)) { + pageStack.pop(); + pageStack.pop(); + } + } + } + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("UUID") + subText: modbusRtuMaster.modbusUuid + progressive: false + prominentSubText: false + } + + SettingsPageSectionHeader { + text: qsTr("Serial port") + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("System location") + subText: serialPort.systemLocation + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Description") + subText: serialPort.description + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Manufacturer") + subText: serialPort.manufacturer + progressive: false + prominentSubText: false + visible: serialPort.manufacturer !== "" + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Serialnumber") + subText: serialPort.serialNumber + progressive: false + prominentSubText: false + visible: serialPort.serialNumber !== "" + } + + SettingsPageSectionHeader { + text: qsTr("Configuration") + } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + Label { + text: qsTr("Baud rate") + Layout.fillWidth: true + } + + ComboBox { + id: baudRateComboBox + Layout.minimumWidth: 250 + enabled: !root.busy + textRole: "text" + model: serialPortBaudrateModel + onActivated: console.log("Selected baudrate", currentText, model.get(currentIndex).value) + Component.onCompleted: { + for (var i = 0; i < serialPortBaudrateModel.count; i++) { + if (serialPortBaudrateModel.get(i).value === modbusRtuMaster.baudrate) { + currentIndex = i + } + } + } + } + } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + + Label { + text: qsTr("Parity") + Layout.fillWidth: true + } + + ComboBox { + id: parityComboBox + textRole: "text" + enabled: !root.busy + Layout.minimumWidth: 250 + onActivated: console.log("Selected parity", currentText, model.get(currentIndex).value) + model: serialPortParityModel + Component.onCompleted: { + for (var i = 0; i < serialPortParityModel.count; i++) { + if (serialPortParityModel.get(i).value === modbusRtuMaster.parity) { + currentIndex = i + } + } + } + } + } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + + Label { + text: qsTr("Data bits") + Layout.fillWidth: true + } + + ComboBox { + id: dataBitsComboBox + textRole: "text" + enabled: !root.busy + Layout.minimumWidth: 250 + onActivated: console.log("Selected data bits", currentText, model.get(currentIndex).value) + model: serialPortDataBitsModel + Component.onCompleted: { + for (var i = 0; i < serialPortDataBitsModel.count; i++) { + if (serialPortDataBitsModel.get(i).value === modbusRtuMaster.dataBits) { + currentIndex = i + } + } + } + } + } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + + Label { + text: qsTr("Stop bits") + Layout.fillWidth: true + } + + ComboBox { + id: stopBitsComboBox + textRole: "text" + enabled: !root.busy + Layout.minimumWidth: 250 + onActivated: console.log("Selected stop bits", currentText, model.get(currentIndex).value) + model: serialPortStopBitsModel + Component.onCompleted: { + for (var i = 0; i < serialPortStopBitsModel.count; i++) { + if (serialPortStopBitsModel.get(i).value === modbusRtuMaster.stopBits) { + currentIndex = i + } + } + } + } + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Apply") + enabled: !root.busy + onClicked: { + var baudrate = serialPortBaudrateModel.get(baudRateComboBox.currentIndex).value + var parity = serialPortParityModel.get(parityComboBox.currentIndex).value + var dataBits = serialPortDataBitsModel.get(dataBitsComboBox.currentIndex).value + var stopBits = serialPortStopBitsModel.get(stopBitsComboBox.currentIndex).value + + console.log("Reconfigure modbus RTU", modbusRtuMaster.modbusUuid, "with", serialPort.systemLocation, baudrate, parity, dataBits, stopBits) + d.reconfigureModbusRtuMaster(modbusRtuMaster.modbusUuid, serialPort.systemLocation, baudrate, parity, dataBits, stopBits) + } + } + } + } +} diff --git a/nymea-app/ui/system/ModbusRtuSettingsPage.qml b/nymea-app/ui/system/ModbusRtuSettingsPage.qml new file mode 100644 index 00000000..b63ade92 --- /dev/null +++ b/nymea-app/ui/system/ModbusRtuSettingsPage.qml @@ -0,0 +1,366 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU 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 General +* Public License for more details. +* +* You should have received a copy of the GNU 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +SettingsPageBase { + id: root + + header: NymeaHeader { + text: qsTr("Modbus RTU") + backButtonVisible: true + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "../images/add.svg" + text: qsTr("Add Modbus RTU master") + onClicked: pageStack.push(Qt.resolvedUrl("ModbusRtuAddMasterPage.qml"), { + modbusRtuManager: modbusRtuManager, + serialPortBaudrateModel: serialPortBaudrateModel, + serialPortParityModel: serialPortParityModel, + serialPortDataBitsModel: serialPortDataBitsModel, + serialPortStopBitsModel: serialPortStopBitsModel + }) + enabled: modbusRtuManager.supported + } + } + + ModbusRtuManager { + id: modbusRtuManager + engine: _engine + + function handleModbusError(error) { + var props = {}; + switch (error) { + case "ModbusRtuErrorNoError": + return true; + case "ModbusRtuErrorNotAvailable": + props.text = qsTr("The serial port is not available any more."); + break; + case "ModbusRtuErrorNotSupported": + props.text = qsTr("Modbus is not supported on this platform."); + break; + case "ModbusRtuErrorHardwareNotFound": + props.text = qsTr("The modbus RTU hardware could not be found."); + break; + case "ModbusRtuErrorUuidNotFound": + props.text = qsTr("The selected modbus RTU master does not exist any more."); + break; + case "ModbusRtuErrorConnectionFailed": + props.text = qsTr("Unable to connect to the modbus RTU master.\n\nMaybe the hardware is already in use."); + break; + default: + props.errorCode = error; + } + var comp = Qt.createComponent("../components/ErrorDialog.qml") + var popup = comp.createObject(app, props) + popup.open(); + + return false; + } + } + + SettingsPageSectionHeader { + text: qsTr("Modbus RTU masters") + } + + ListModel { + id: serialPortBaudrateModel + ListElement { value: 9600; text: qsTr("9600 Bd") } + ListElement { value: 14400; text: qsTr("14400 Bd") } + ListElement { value: 19200; text: qsTr("19200 Bd") } + ListElement { value: 38400; text: qsTr("38400 Bd") } + ListElement { value: 57600; text: qsTr("57600 Bd") } + ListElement { value: 115200; text: qsTr("115200 Bd") } + ListElement { value: 128000; text: qsTr("128000 Bd") } + ListElement { value: 230400; text: qsTr("230400 Bd") } + ListElement { value: 256000; text: qsTr("256000 Bd") } + + function getText(baudrate) { + for (var index = 0; index < serialPortBaudrateModel.count; index++) { + if (serialPortBaudrateModel.get(index).value === baudrate) { + return serialPortBaudrateModel.get(index).text + } + } + + return qsTr("Unknown baud rate") + } + } + + ListModel { + id: serialPortParityModel + ListElement { value: SerialPort.SerialPortParityNoParity; text: qsTr("No parity") } + ListElement { value: SerialPort.SerialPortParityEvenParity; text: qsTr("Even parity") } + ListElement { value: SerialPort.SerialPortParityOddParity; text: qsTr("Odd parity") } + ListElement { value: SerialPort.SerialPortParitySpaceParity; text: qsTr("Space parity") } + ListElement { value: SerialPort.SerialPortParityMarkParity; text: qsTr("Mark parity") } + + function getText(parity) { + for (var index = 0; index < serialPortParityModel.count; index++) { + if (serialPortParityModel.get(index).value === parity) { + return serialPortParityModel.get(index).text + } + } + + return qsTr("Unknown parity") + } + } + + ListModel { + id: serialPortDataBitsModel + ListElement { value: SerialPort.SerialPortDataBitsData5; text: qsTr("5 data bits") } + ListElement { value: SerialPort.SerialPortDataBitsData6; text: qsTr("6 data bits") } + ListElement { value: SerialPort.SerialPortDataBitsData7; text: qsTr("7 data bits") } + ListElement { value: SerialPort.SerialPortDataBitsData8; text: qsTr("8 data bits") } + + function getText(dataBits) { + for (var index = 0; index < serialPortDataBitsModel.count; index++) { + if (serialPortDataBitsModel.get(index).value === dataBits) { + return serialPortDataBitsModel.get(index).text + } + } + + return qsTr("Unknown data bits") + } + } + + ListModel { + id: serialPortStopBitsModel + ListElement { value: SerialPort.SerialPortStopBitsOneStop; text: qsTr("One stop bit") } + ListElement { value: SerialPort.SerialPortStopBitsOneAndHalfStop; text: qsTr("One and a half stop bits") } + ListElement { value: SerialPort.SerialPortStopBitsTwoStop; text: qsTr("Two stop bits") } + + function getText(stopBits) { + for (var index = 0; index < serialPortStopBitsModel.count; index++) { + if (serialPortStopBitsModel.get(index).value === stopBits) { + return serialPortStopBitsModel.get(index).text + } + } + + return qsTr("Unknown stop bits") + } + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + wrapMode: Text.WordWrap + text: qsTr("In this section you can configure system wide modbus RTU master connections which can be used in different plugins.") + visible: modbusRtuManager.supported + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + wrapMode: Text.WordWrap + text: qsTr("There are no modbus RTU masters set up yet.") + visible: modbusRtuManager.modbusRtuMasters.count === 0 && modbusRtuManager.supported + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + wrapMode: Text.WordWrap + text: qsTr("Modbus RTU is not supported on this platorm.") + visible: !modbusRtuManager.supported + } + + // Modbus RTU masters + Repeater { + enabled: modbusRtuManager.supported + model: modbusRtuManager.modbusRtuMasters + delegate: NymeaSwipeDelegate { + Layout.fillWidth: true + iconName: "../images/modbus.svg" + text: model.serialPort + " " + model.baudrate + subText: model.connected ? qsTr("Connected") : qsTr("Disconnected") + onClicked: pageStack.push(modbusDetailsComponent, { modbusRtuManager: modbusRtuManager, modbusRtuMaster: modbusRtuManager.modbusRtuMasters.get(index) }) + } + } + + Component { + id: modbusDetailsComponent + + SettingsPageBase { + id: root + + property ModbusRtuManager modbusRtuManager + property ModbusRtuMaster modbusRtuMaster + + busy: d.pendingCommandId !== -1 + + header: NymeaHeader { + text: qsTr("Modbus RTU master") + backButtonVisible: true + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "../images/delete.svg" + text: qsTr("Remove modbus RTU master") + enabled: modbusRtuManager.supported + onClicked: { + var dialog = removeModbusMasterDialogComponent.createObject(app, {modbusRtuMaster: root.modbusRtuMaster}) + dialog.open() + } + } + } + + Component { + id: removeModbusMasterDialogComponent + + MeaDialog { + id: removeModbusMasterDialog + + property ModbusRtuMaster modbusRtuMaster + + headerIcon: "../images/modbus.svg" + title: qsTr("Remove modbus RTU master") + text: qsTr("Are you sure you want to remove this modbus RTU master?") + standardButtons: Dialog.Ok | Dialog.Cancel + + Label { + text: qsTr("Please note that all related things will stop working until you assign a new modbus RTU master to them.") + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + onAccepted: { + d.removeModbusRtuMaster(modbusRtuMaster.modbusUuid) + } + } + } + + QtObject { + id: d + property int pendingCommandId: -1 + + function removeModbusRtuMaster(modbusUuid) { + d.pendingCommandId = root.modbusRtuManager.removeModbusRtuMaster(modbusUuid) + } + } + + Connections { + target: root.modbusRtuManager + onRemoveModbusRtuMasterReply: { + if (commandId === d.pendingCommandId) { + d.pendingCommandId = -1 + if (modbusRtuManager.handleModbusError(error)) { + // FIXME: the page does not work if I pop the page here + //pageStack.pop() + } + } + } + } + + SettingsPageSectionHeader { + text: qsTr("Information") + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("UUID") + subText: modbusRtuMaster ? modbusRtuMaster.modbusUuid : "" + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("System location") + subText: modbusRtuMaster ? modbusRtuMaster.serialPort : "" + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Connection status") + subText: modbusRtuMaster && modbusRtuMaster.connected ? qsTr("Connected") : qsTr("Disconnected") + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Baud rate") + subText: modbusRtuMaster ? serialPortBaudrateModel.getText(modbusRtuMaster.baudrate) : "" + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Parity") + subText: modbusRtuMaster ? serialPortParityModel.getText(modbusRtuMaster.parity) : "" + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Data bits") + subText: modbusRtuMaster ? serialPortDataBitsModel.getText(modbusRtuMaster.dataBits) : "" + progressive: false + prominentSubText: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Stop bits") + subText: modbusRtuMaster ? serialPortStopBitsModel.getText(modbusRtuMaster.stopBits) : "" + progressive: false + prominentSubText: false + } + + Button { + id: reconfigureButton + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Reconfigure") + enabled: !root.busy + onClicked: pageStack.push(Qt.resolvedUrl("ModbusRtuReconfigureMasterPage.qml"), { + modbusRtuManager: modbusRtuManager, + modbusRtuMaster: root.modbusRtuMaster, + serialPortBaudrateModel: serialPortBaudrateModel, + serialPortParityModel: serialPortParityModel, + serialPortDataBitsModel: serialPortDataBitsModel, + serialPortStopBitsModel: serialPortStopBitsModel + }) + } + } + } +} +