diff --git a/debian/control b/debian/control index 0581cb77..62258bd4 100644 --- a/debian/control +++ b/debian/control @@ -26,6 +26,8 @@ Build-Depends: debhelper (>= 9.0.0), qttools5-dev-tools, qtconnectivity5-dev, qtdeclarative5-dev, + libqt5serialport5-dev, + libqt5serialbus5-dev Package: nymea Architecture: any diff --git a/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.cpp b/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.cpp new file mode 100644 index 00000000..501691bb --- /dev/null +++ b/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.cpp @@ -0,0 +1,124 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "modbusrtuhardwareresourceimplementation.h" +#include "loggingcategories.h" +#include "nymeasettings.h" +#include "hardware/modbus/modbusrtumanager.h" + +NYMEA_LOGGING_CATEGORY(dcModbusRtuResource, "ModbusRtuResource") + +namespace nymeaserver { + +ModbusRtuHardwareResourceImplementation::ModbusRtuHardwareResourceImplementation(ModbusRtuManager *modbusRtuManager, QObject *parent) : + ModbusRtuHardwareResource(parent), + m_modbusRtuManager(modbusRtuManager) +{ + connect(m_modbusRtuManager, &ModbusRtuManager::modbusRtuMasterAdded, this, [=](ModbusRtuMaster *modbusRtuMaster){ + emit modbusRtuMasterAdded(modbusRtuMaster->modbusUuid()); + }); + + connect(m_modbusRtuManager, &ModbusRtuManager::modbusRtuMasterRemoved, this, [=](ModbusRtuMaster *modbusRtuMaster){ + emit modbusRtuMasterRemoved(modbusRtuMaster->modbusUuid()); + }); + + connect(m_modbusRtuManager, &ModbusRtuManager::modbusRtuMasterChanged, this, [=](ModbusRtuMaster *modbusRtuMaster){ + emit modbusRtuMasterChanged(modbusRtuMaster->modbusUuid()); + }); +} + +QList ModbusRtuHardwareResourceImplementation::modbusRtuMasters() const +{ + return m_modbusRtuManager->modbusRtuMasters(); +} + +bool ModbusRtuHardwareResourceImplementation::hasModbusRtuMaster(const QUuid &modbusUuid) const +{ + return m_modbusRtuManager->hasModbusRtuMaster(modbusUuid); +} + +ModbusRtuMaster *ModbusRtuHardwareResourceImplementation::getModbusRtuMaster(const QUuid &modbusUuid) const +{ + return m_modbusRtuManager->getModbusRtuMaster(modbusUuid); +} + +bool ModbusRtuHardwareResourceImplementation::available() const +{ + return m_modbusRtuManager->supported(); +} + +bool ModbusRtuHardwareResourceImplementation::enabled() const +{ + return m_enabled; +} + +bool ModbusRtuHardwareResourceImplementation::enable() +{ + qCWarning(dcModbusRtuResource()) << "Enable hardware resource. Not implemented yet."; + + // TODO: enable all modbus clients + + return true; +} + +bool ModbusRtuHardwareResourceImplementation::disable() +{ + qCWarning(dcModbusRtuResource()) << "Disable hardware resource. Not implemented yet."; + + // TODO: disable all modbus clients + + return true; +} + +void ModbusRtuHardwareResourceImplementation::setEnabled(bool enabled) +{ + qCDebug(dcModbusRtuResource()) << "Set" << (enabled ? "enabled" : "disabled"); + if (m_enabled && enabled) { + qCDebug(dcModbusRtuResource()) << "Already enabled."; + return; + } else if (!m_enabled && !enabled) { + qCDebug(dcModbusRtuResource()) << "Already disabled."; + return; + } + + bool success = false; + if (enabled) { + success = enable(); + } else { + success = disable(); + } + + if (success) { + m_enabled = enabled; + emit enabledChanged(m_enabled); + } +} + +} diff --git a/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.h b/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.h new file mode 100644 index 00000000..73e480dc --- /dev/null +++ b/libnymea-core/hardware/modbus/modbusrtuhardwareresourceimplementation.h @@ -0,0 +1,72 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 MODBUSRTUHARDWARERESOURCEIMPLEMENTATION_H +#define MODBUSRTUHARDWARERESOURCEIMPLEMENTATION_H + +#include + +#include "hardware/modbus/modbusrtumanager.h" +#include "hardware/modbus/modbusrtumaster.h" +#include "hardware/modbus/modbusrtuhardwareresource.h" + +namespace nymeaserver { + +class ModbusRtuHardwareResourceImplementation : public ModbusRtuHardwareResource +{ + Q_OBJECT +public: + explicit ModbusRtuHardwareResourceImplementation(ModbusRtuManager *modbusRtuManager, QObject *parent = nullptr); + ~ModbusRtuHardwareResourceImplementation() override = default; + + QList modbusRtuMasters() const override; + bool hasModbusRtuMaster(const QUuid &modbusUuid) const override; + ModbusRtuMaster *getModbusRtuMaster(const QUuid &modbusUuid) const override; + + bool available() const override; + bool enabled() const override; + +public slots: + bool enable(); + bool disable(); + +protected: + void setEnabled(bool enabled) override; + +private: + ModbusRtuManager *m_modbusRtuManager = nullptr; + bool m_available = false; + bool m_enabled = false; + +}; + +} + +#endif // MODBUSRTUHARDWARERESOURCEIMPLEMENTATION_H diff --git a/libnymea-core/hardware/modbus/modbusrtumanager.cpp b/libnymea-core/hardware/modbus/modbusrtumanager.cpp new file mode 100644 index 00000000..d2cdafa4 --- /dev/null +++ b/libnymea-core/hardware/modbus/modbusrtumanager.cpp @@ -0,0 +1,270 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "modbusrtumanager.h" +#include "nymeasettings.h" +#include "loggingcategories.h" + +#include "modbusrtumasterimpl.h" +#include "hardware/serialport/serialportmonitor.h" + +NYMEA_LOGGING_CATEGORY(dcModbusRtu, "ModbusRtu") + +namespace nymeaserver { + +ModbusRtuManager::ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject *parent) : + QObject(parent), + m_serialPortMonitor(serialPortMonitor) +{ + // Load uart configurations + loadRtuMasters(); + + connect(m_serialPortMonitor, &SerialPortMonitor::serialPortAdded, this, [=](const QSerialPortInfo &serialPortInfo){ + qCDebug(dcModbusRtu()) << "Serial port added. Verify modbus RTU masters..."; + + // Check if we have to reconnect any modbus RTU masters + foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) { + ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast(modbusMaster); + + // Try only to reconnect if the added serial port matches a disconnected modbus RTU master + if (!modbusMasterImpl->connected() && modbusMasterImpl->serialPort() == serialPortInfo.systemLocation()) { + if (!modbusMasterImpl->connectDevice()) { + qCDebug(dcModbusRtu()) << "Reconnect" << modbusMaster << "failed."; + } else { + qCDebug(dcModbusRtu()) << "Reconnected" << modbusMaster << "successfully."; + } + } + } + }); + + // Try to connect the modbus rtu masters + foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuMasters.values()) { + ModbusRtuMasterImpl *modbusMasterImpl = qobject_cast(modbusMaster); + if (!modbusMasterImpl->connectDevice()) { + qCWarning(dcModbusRtu()) << "Failed to connect modbus RTU master. Could not connect to" << modbusMaster; + } + } + +} + +SerialPortMonitor *ModbusRtuManager::serialPortMonitor() const +{ + return m_serialPortMonitor; +} + +bool ModbusRtuManager::supported() const +{ +#ifdef WITH_QTSERIALBUS + return true; +#else + return false; +#endif +} + +QList ModbusRtuManager::modbusRtuMasters() const +{ + return m_modbusRtuMasters.values(); +} + +bool ModbusRtuManager::hasModbusRtuMaster(const QUuid &modbusUuid) const +{ + return m_modbusRtuMasters.value(modbusUuid) != nullptr; +} + +ModbusRtuMaster *ModbusRtuManager::getModbusRtuMaster(const QUuid &modbusUuid) +{ + if (hasModbusRtuMaster(modbusUuid)) { + return m_modbusRtuMasters.value(modbusUuid); + } + + return nullptr; +} + +QPair ModbusRtuManager::addNewModbusRtuMaster(const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, int numberOfRetries, int timeout) +{ + if (!supported()) { + qCWarning(dcModbusRtu()) << "Cannot add new modbus RTU master because serialbus is not suppoerted on this platform."; + return QPair(ModbusRtuErrorNotSupported, QUuid()); + } + + // Check if the serial port exists + 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, numberOfRetries, timeout, this); + ModbusRtuMaster *modbus = qobject_cast(modbusMaster); + qCDebug(dcModbusRtu()) << "Adding new" << modbus << parity << dataBits << stopBits; + + // 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(ModbusRtuErrorNoError, modbusUuid); +} + +ModbusRtuManager::ModbusRtuError ModbusRtuManager::reconfigureModbusRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, int numberOfRetries, int timeout) +{ + if (!supported()) { + qCWarning(dcModbusRtu()) << "Cannot reconfigure modbus RTU master because serialbus is not suppoerted on this platform."; + return ModbusRtuErrorNotSupported; + } + + if (!m_modbusRtuMasters.contains(modbusUuid)) { + qCWarning(dcModbusRtu()) << "Could not reconfigure modbus RTU master because no resource could be found with uuid" << modbusUuid.toString(); + return ModbusRtuErrorUuidNotFound; + } + + // Take the modbus masters + ModbusRtuMasterImpl *modbusMaster = qobject_cast(m_modbusRtuMasters.value(modbusUuid)); + + // Disconnect + modbusMaster->disconnectDevice(); + + // Reconfigure + modbusMaster->setSerialPort(serialPort); + modbusMaster->setBaudrate(baudrate); + modbusMaster->setParity(parity); + modbusMaster->setDataBits(dataBits); + modbusMaster->setStopBits(stopBits); + modbusMaster->setNumberOfRetries(numberOfRetries); + modbusMaster->setTimeout(timeout); + + // Connect again + if (!modbusMaster->connectDevice()) { + qCWarning(dcModbusRtu()) << "Failed to connect to" << m_modbusRtuMasters.value(modbusUuid); + // FIXME: check if we should reload the old configuration + emit modbusRtuMasterChanged(m_modbusRtuMasters.value(modbusUuid)); + return ModbusRtuErrorConnectionFailed; + } + + emit modbusRtuMasterChanged(m_modbusRtuMasters.value(modbusUuid)); + + qCDebug(dcModbusRtu()) << "Reconfigured successfully" << m_modbusRtuMasters.value(modbusUuid); + saveModbusRtuMaster(modbusMaster); + return ModbusRtuErrorNoError; +} + +ModbusRtuManager::ModbusRtuError ModbusRtuManager::removeModbusRtuMaster(const QUuid &modbusUuid) +{ + if (!supported()) { + qCWarning(dcModbusRtu()) << "Cannot remove modbus RTU master because serialbus is not suppoerted on this platform."; + return ModbusRtuErrorNotSupported; + } + + if (!m_modbusRtuMasters.contains(modbusUuid)) { + qCWarning(dcModbusRtu()) << "Could not remove modbus RTU master because no resource could be found with uuid" << modbusUuid.toString(); + return ModbusRtuErrorUuidNotFound; + } + + ModbusRtuMasterImpl *modbusMaster = qobject_cast(m_modbusRtuMasters.take(modbusUuid)); + qCDebug(dcModbusRtu()) << "Removing modbus RTU master" << qobject_cast(modbusMaster); + modbusMaster->disconnectDevice(); + modbusMaster->deleteLater(); + + // Remove from settings + NymeaSettings settings(NymeaSettings::SettingsRoleModbusRtu); + settings.beginGroup("ModbusRtuMasters"); + settings.beginGroup(modbusUuid.toString()); + settings.remove(""); + settings.endGroup(); // modbusUuid + settings.endGroup(); // ModbusRtuMasters + + emit modbusRtuMasterRemoved(modbusMaster); + + return ModbusRtuErrorNoError; +} + +void ModbusRtuManager::loadRtuMasters() +{ + if (!supported()) + return; + + NymeaSettings settings(NymeaSettings::SettingsRoleModbusRtu); + qCDebug(dcModbusRtu()) << "Loading modbus RTU resources from" << settings.fileName(); + settings.beginGroup("ModbusRtuMasters"); + foreach (const QString &uuidString, settings.childGroups()) { + settings.beginGroup(uuidString); + QString serialPort = settings.value("serialPort").toString(); + quint32 baudrate = settings.value("baudrate").toUInt(); + QSerialPort::Parity parity = static_cast(settings.value("parity").toInt()); + QSerialPort::DataBits dataBits = static_cast(settings.value("dataBits").toInt()); + QSerialPort::StopBits stopBits = static_cast(settings.value("stopBits").toInt()); + int numberOfRetries = settings.value("numberOfRetries").toInt(); + int timeout = settings.value("timeout").toInt(); + settings.endGroup(); // uuid + + addModbusRtuMasterInternally(new ModbusRtuMasterImpl(QUuid(uuidString), serialPort, baudrate, parity, dataBits, stopBits, numberOfRetries, timeout, this)); + } + + settings.endGroup(); // ModbusRtuMasters +} + +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()); + settings.setValue("baudrate", modbusRtuMaster->baudrate()); + settings.setValue("parity", static_cast(modbusRtuMaster->parity())); + settings.setValue("dataBits", static_cast(modbusRtuMaster->dataBits())); + settings.setValue("stopBits", static_cast(modbusRtuMaster->stopBits())); + settings.setValue("numberOfRetries", modbusRtuMaster->numberOfRetries()); + settings.setValue("timeout", modbusRtuMaster->timeout()); + settings.endGroup(); // uuid + 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/hardware/modbus/modbusrtumanager.h b/libnymea-core/hardware/modbus/modbusrtumanager.h new file mode 100644 index 00000000..837188c6 --- /dev/null +++ b/libnymea-core/hardware/modbus/modbusrtumanager.h @@ -0,0 +1,95 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 MODBUSRTUMANAGER_H +#define MODBUSRTUMANAGER_H + +#include +#include +#include +#include + +#include "hardware/modbus/modbusrtumaster.h" + +namespace nymeaserver { + +class SerialPortMonitor; +class ModbusRtuMasterImpl; + +class ModbusRtuManager : public QObject +{ + Q_OBJECT +public: + enum ModbusRtuError { + ModbusRtuErrorNoError, + ModbusRtuErrorNotAvailable, + ModbusRtuErrorUuidNotFound, + ModbusRtuErrorHardwareNotFound, + ModbusRtuErrorResourceBusy, + ModbusRtuErrorNotSupported, + ModbusRtuErrorInvalidTimeoutValue, + ModbusRtuErrorConnectionFailed + }; + Q_ENUM(ModbusRtuError) + + explicit ModbusRtuManager(SerialPortMonitor *serialPortMonitor, QObject *parent = nullptr); + ~ModbusRtuManager() = default; + + SerialPortMonitor *serialPortMonitor() const; + + bool supported() 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, int numberOfRetries, int timeout); + ModbusRtuError reconfigureModbusRtuMaster(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, int numberOfRetries, int timeout); + ModbusRtuError removeModbusRtuMaster(const QUuid &modbusUuid); + +signals: + void modbusRtuMasterAdded(ModbusRtuMaster *modbusRtuMaster); + void modbusRtuMasterRemoved(ModbusRtuMaster *modbusRtuMaster); + void modbusRtuMasterChanged(ModbusRtuMaster *modbusRtuMaster); + +private: + QHash m_modbusRtuMasters; + SerialPortMonitor *m_serialPortMonitor = nullptr; + + void loadRtuMasters(); + void saveModbusRtuMaster(ModbusRtuMaster *modbusRtuMaster); + + void addModbusRtuMasterInternally(ModbusRtuMasterImpl *modbusRtuMaster); + +}; + +} + +#endif // MODBUSRTUMANAGER_H diff --git a/libnymea-core/hardware/modbus/modbusrtumasterimpl.cpp b/libnymea-core/hardware/modbus/modbusrtumasterimpl.cpp new file mode 100644 index 00000000..eab5c4c7 --- /dev/null +++ b/libnymea-core/hardware/modbus/modbusrtumasterimpl.cpp @@ -0,0 +1,553 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "modbusrtumasterimpl.h" +#include "modbusrtureplyimpl.h" + +#include + +#ifdef WITH_QTSERIALBUS +#include +#include +#endif + +Q_DECLARE_LOGGING_CATEGORY(dcModbusRtu) + +namespace nymeaserver { + +ModbusRtuMasterImpl::ModbusRtuMasterImpl(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, int numberOfRetries, int timeout, QObject *parent) : + ModbusRtuMaster(parent), + m_modbusUuid(modbusUuid), + m_serialPort(serialPort), + m_baudrate(baudrate), + m_parity(parity), + m_dataBits(dataBits), + m_stopBits(stopBits), + m_numberOfRetries(numberOfRetries), + m_timeout(timeout) +{ +#ifdef WITH_QTSERIALBUS + m_modbus = new QModbusRtuSerialMaster(this); + m_modbus->setConnectionParameter(QModbusDevice::SerialPortNameParameter, m_serialPort); + m_modbus->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_baudrate); + m_modbus->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, m_dataBits); + m_modbus->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, m_stopBits); + m_modbus->setConnectionParameter(QModbusDevice::SerialParityParameter, m_parity); + m_modbus->setNumberOfRetries(m_numberOfRetries); + m_modbus->setTimeout(m_timeout); + + connect(m_modbus, &QModbusTcpClient::stateChanged, this, [=](QModbusDevice::State state){ + qCDebug(dcModbusRtu()) << "Connection state changed" << m_modbusUuid.toString() << m_serialPort << state; + if (state == QModbusDevice::ConnectedState) { + if (m_connected != true) { + m_connected = true; + emit connectedChanged(m_connected); + } + } else { + if (m_connected != false) { + m_connected = false; + emit connectedChanged(m_connected); + } + } + }); + + connect(m_modbus, &QModbusRtuSerialMaster::errorOccurred, this, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Error occured for modbus RTU master" << m_modbusUuid.toString() << m_serialPort << error << m_modbus->errorString(); + if (error != QModbusDevice::NoError) { + disconnectDevice(); + } + }); +#endif +} + +QUuid ModbusRtuMasterImpl::modbusUuid() const +{ + return m_modbusUuid; +} + +QString ModbusRtuMasterImpl::serialPort() const +{ + return m_serialPort; +} + +void ModbusRtuMasterImpl::setSerialPort(const QString &serialPort) +{ + if (m_serialPort == serialPort) + return; + + m_serialPort = serialPort; + emit serialPortChanged(m_serialPort); +} + +qint32 ModbusRtuMasterImpl::baudrate() const +{ + return m_baudrate; +} + +void ModbusRtuMasterImpl::setBaudrate(qint32 baudrate) +{ + if (m_baudrate == baudrate) + return; + + m_baudrate = baudrate; + emit baudrateChanged(m_baudrate); +} + +QSerialPort::Parity ModbusRtuMasterImpl::parity() const +{ + return m_parity; +} + +void ModbusRtuMasterImpl::setParity(QSerialPort::Parity parity) +{ + if (m_parity == parity) + return; + + m_parity = parity; + emit parityChanged(m_parity); +} + +QSerialPort::DataBits ModbusRtuMasterImpl::dataBits() const +{ + return m_dataBits; +} + +void ModbusRtuMasterImpl::setDataBits(QSerialPort::DataBits dataBits) +{ + if (m_dataBits == dataBits) + return; + + m_dataBits = dataBits; + emit dataBitsChanged(m_dataBits); +} + +QSerialPort::StopBits ModbusRtuMasterImpl::stopBits() +{ + return m_stopBits; +} + +void ModbusRtuMasterImpl::setStopBits(QSerialPort::StopBits stopBits) +{ + if (m_stopBits == stopBits) + return; + + m_stopBits = stopBits; + emit stopBitsChanged(m_stopBits); +} + +bool ModbusRtuMasterImpl::connected() const +{ + return m_connected; +} + +bool ModbusRtuMasterImpl::connectDevice() +{ +#ifdef WITH_QTSERIALBUS + m_modbus->setConnectionParameter(QModbusDevice::SerialPortNameParameter, m_serialPort); + m_modbus->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_baudrate); + m_modbus->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, m_dataBits); + m_modbus->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, m_stopBits); + m_modbus->setConnectionParameter(QModbusDevice::SerialParityParameter, m_parity); + m_modbus->setNumberOfRetries(m_numberOfRetries); + m_modbus->setTimeout(m_timeout); + return m_modbus->connectDevice(); +#else + qCWarning(dcModbusRtu()) << "Modbus is not available on this platform."; + return false; +#endif +} + +void ModbusRtuMasterImpl::disconnectDevice() +{ +#ifdef WITH_QTSERIALBUS + m_modbus->disconnectDevice(); +#else + qCWarning(dcModbusRtu()) << "Modbus is not available on this platform."; +#endif +} + +int ModbusRtuMasterImpl::numberOfRetries() const +{ + return m_numberOfRetries; +} + +void ModbusRtuMasterImpl::setNumberOfRetries(int numberOfRetries) +{ + if (m_numberOfRetries == numberOfRetries) + return; + + m_numberOfRetries = numberOfRetries; + emit numberOfRetriesChanged(m_numberOfRetries); +#ifdef WITH_QTSERIALBUS + m_modbus->setNumberOfRetries(m_numberOfRetries); +#endif +} + +int ModbusRtuMasterImpl::timeout() const +{ + return m_timeout; +} + +void ModbusRtuMasterImpl::setTimeout(int timeout) +{ + if (m_timeout == timeout) + return; + + m_timeout = timeout; + emit timeoutChanged(m_timeout); +#ifdef WITH_QTSERIALBUS + m_modbus->setTimeout(m_timeout); +#endif +} + +ModbusRtuReply *ModbusRtuMasterImpl::readCoil(int slaveAddress, int registerAddress, quint16 size) +{ +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, size); + QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); +#else + Q_UNUSED(slaveAddress) + Q_UNUSED(registerAddress) + Q_UNUSED(size) + qCWarning(dcModbusRtu()) << "Modbus is not available on this platform."; + + return nullptr; +#endif +} + +ModbusRtuReply *ModbusRtuMasterImpl::readDiscreteInput(int slaveAddress, int registerAddress, quint16 size) +{ +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, registerAddress, size); + QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read descrete inputs request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read descrete inputs request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); +#else + Q_UNUSED(slaveAddress) + Q_UNUSED(registerAddress) + Q_UNUSED(size) + qCWarning(dcModbusRtu()) << "Modbus is not available on this platform."; + + return nullptr; +#endif +} + +ModbusRtuReply *ModbusRtuMasterImpl::readInputRegister(int slaveAddress, int registerAddress, quint16 size) +{ +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, registerAddress, size); + QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read input registers request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read input registers request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); +#else + Q_UNUSED(slaveAddress) + Q_UNUSED(registerAddress) + Q_UNUSED(size) + qCWarning(dcModbusRtu()) << "Modbus is not available on this platform."; + + return nullptr; +#endif +} + +ModbusRtuReply *ModbusRtuMasterImpl::readHoldingRegister(int slaveAddress, int registerAddress, quint16 size) +{ +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, size); + QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read holding registers request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read holding registers request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); +#else + Q_UNUSED(slaveAddress) + Q_UNUSED(registerAddress) + Q_UNUSED(size) + qCWarning(dcModbusRtu()) << "Modbus is not available on this platform."; + + return nullptr; +#endif +} + +ModbusRtuReply *ModbusRtuMasterImpl::writeCoils(int slaveAddress, int registerAddress, const QVector &values) +{ +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, values.length()); + request.setValues(values); + + QModbusReply *modbusReply = m_modbus->sendWriteRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); +#else + Q_UNUSED(slaveAddress) + Q_UNUSED(registerAddress) + Q_UNUSED(values) + qCWarning(dcModbusRtu()) << "Modbus is not available on this platform."; + + return nullptr; +#endif +} + + +ModbusRtuReply *ModbusRtuMasterImpl::writeHoldingRegisters(int slaveAddress, int registerAddress, const QVector &values) +{ +#ifdef WITH_QTSERIALBUS + // Create the reply for the plugin + ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this); + connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater); + + // Create the actual modbus lib reply + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length()); + request.setValues(values); + + QModbusReply *modbusReply = m_modbus->sendWriteRequest(request, slaveAddress); + + connect(modbusReply, &QModbusReply::finished, modbusReply, [=](){ + modbusReply->deleteLater(); + + // Fill common reply data + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + + // Check if the reply finished with an error + if (modbusReply->error() != QModbusDevice::NoError) { + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << modbusReply->error() << modbusReply->errorString(); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + return; + } + + // Parse the data unit and set reply result + const QModbusDataUnit unit = modbusReply->result(); + reply->setResult(unit.values()); + emit reply->finished(); + }); + + connect(modbusReply, &QModbusReply::errorOccurred, modbusReply, [=](QModbusDevice::Error error){ + qCWarning(dcModbusRtu()) << "Read coil request finished with error" << error << modbusReply->errorString(); + reply->setFinished(true); + reply->setError(static_cast(modbusReply->error())); + reply->setErrorString(modbusReply->errorString()); + emit reply->errorOccurred(reply->error()); + emit reply->finished(); + }); + + return qobject_cast(reply); +#else + Q_UNUSED(slaveAddress) + Q_UNUSED(registerAddress) + Q_UNUSED(values) + qCWarning(dcModbusRtu()) << "Modbus is not available on this platform."; + + return nullptr; +#endif +} + +} diff --git a/libnymea-core/hardware/modbus/modbusrtumasterimpl.h b/libnymea-core/hardware/modbus/modbusrtumasterimpl.h new file mode 100644 index 00000000..6df0749f --- /dev/null +++ b/libnymea-core/hardware/modbus/modbusrtumasterimpl.h @@ -0,0 +1,108 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 MODBUSRTUMASTERIMPL_H +#define MODBUSRTUMASTERIMPL_H + +#include +#include + +#ifdef WITH_QTSERIALBUS +#include +#endif + +#include "hardware/modbus/modbusrtumaster.h" + +namespace nymeaserver { + +class ModbusRtuMasterImpl : public ModbusRtuMaster +{ + Q_OBJECT +public: + explicit ModbusRtuMasterImpl(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, int numberOfRetries, int timeout, QObject *parent = nullptr); + ~ModbusRtuMasterImpl() override = default; + + QUuid modbusUuid() const override; + + QString serialPort() const override; + void setSerialPort(const QString &serialPort); + + qint32 baudrate() const override; + void setBaudrate(qint32 baudrate); + + QSerialPort::Parity parity() const override; + void setParity(QSerialPort::Parity parity); + + QSerialPort::DataBits dataBits() const override; + void setDataBits(QSerialPort::DataBits dataBits); + + QSerialPort::StopBits stopBits() override; + void setStopBits(QSerialPort::StopBits stopBits); + + bool connected() const override; + + bool connectDevice(); + void disconnectDevice(); + + int numberOfRetries() const override; + void setNumberOfRetries(int numberOfRetries); + + int timeout() const override; + void setTimeout(int timeout); + + // Requests + ModbusRtuReply *readCoil(int slaveAddress, int registerAddress, quint16 size = 1) override; + ModbusRtuReply *readDiscreteInput(int slaveAddress, int registerAddress, quint16 size = 1) override; + ModbusRtuReply *readInputRegister(int slaveAddress, int registerAddress, quint16 size = 1) override; + ModbusRtuReply *readHoldingRegister(int slaveAddress, int registerAddress, quint16 size = 1) override; + + ModbusRtuReply *writeCoils(int slaveAddress, int registerAddress, const QVector &values) override; + ModbusRtuReply *writeHoldingRegisters(int slaveAddress, int registerAddress, const QVector &values) override; + +private: + QUuid m_modbusUuid; + bool m_connected = false; + +#ifdef WITH_QTSERIALBUS + QModbusRtuSerialMaster *m_modbus = nullptr; +#endif + + QString m_serialPort; + qint32 m_baudrate; + QSerialPort::Parity m_parity; + QSerialPort::DataBits m_dataBits; + QSerialPort::StopBits m_stopBits; + int m_numberOfRetries = 3; + int m_timeout = 100; +}; + +} + +#endif // MODBUSRTUMASTERIMPL_H diff --git a/libnymea-core/hardware/modbus/modbusrtureplyimpl.cpp b/libnymea-core/hardware/modbus/modbusrtureplyimpl.cpp new file mode 100644 index 00000000..dc00ac94 --- /dev/null +++ b/libnymea-core/hardware/modbus/modbusrtureplyimpl.cpp @@ -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 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 "modbusrtureplyimpl.h" + +namespace nymeaserver { + +ModbusRtuReplyImpl::ModbusRtuReplyImpl(int slaveAddress, int registerAddress, QObject *parent) : + ModbusRtuReply(parent), + m_slaveAddress(slaveAddress), + m_registerAddress(registerAddress) +{ + +} + +bool ModbusRtuReplyImpl::isFinished() const +{ + return m_finished; +} + +void ModbusRtuReplyImpl::setFinished(bool finished) +{ + m_finished = finished; +} + +int ModbusRtuReplyImpl::slaveAddress() const +{ + return m_slaveAddress; +} + +int ModbusRtuReplyImpl::registerAddress() const +{ + return m_registerAddress; +} + +QString ModbusRtuReplyImpl::errorString() const +{ + return m_errorString; +} + +void ModbusRtuReplyImpl::setErrorString(const QString &errorString) +{ + m_errorString = errorString; +} + +ModbusRtuReply::Error ModbusRtuReplyImpl::error() const +{ + return m_error; +} + +void ModbusRtuReplyImpl::setError(ModbusRtuReply::Error error) +{ + m_error = error; +} + +QVector ModbusRtuReplyImpl::result() const +{ + return m_result; +} + +void ModbusRtuReplyImpl::setResult(const QVector &result) +{ + m_result = result; +} + +} diff --git a/libnymea-core/hardware/modbus/modbusrtureplyimpl.h b/libnymea-core/hardware/modbus/modbusrtureplyimpl.h new file mode 100644 index 00000000..5851b423 --- /dev/null +++ b/libnymea-core/hardware/modbus/modbusrtureplyimpl.h @@ -0,0 +1,73 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 MODBUSRTUREPLYIMPL_H +#define MODBUSRTUREPLYIMPL_H + +#include + +#include "hardware/modbus/modbusrtureply.h" + +namespace nymeaserver { + +class ModbusRtuReplyImpl : public ModbusRtuReply +{ + Q_OBJECT +public: + explicit ModbusRtuReplyImpl(int slaveAddress, int registerAddress, QObject *parent = nullptr); + + bool isFinished() const override; + void setFinished(bool finished); + + int slaveAddress() const override; + int registerAddress() const override; + + QString errorString() const override; + void setErrorString(const QString &errorString); + + ModbusRtuReply::Error error() const override; + void setError(ModbusRtuReply::Error error); + + QVector result() const override; + void setResult(const QVector &result); + +private: + bool m_finished = false; + int m_slaveAddress; + int m_registerAddress; + Error m_error = UnknownError; + QString m_errorString; + QVector m_result; + +}; + +} + +#endif // MODBUSRTUREPLYIMPL_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/hardwaremanagerimplementation.cpp b/libnymea-core/hardwaremanagerimplementation.cpp index 8d19558c..e873b839 100644 --- a/libnymea-core/hardwaremanagerimplementation.cpp +++ b/libnymea-core/hardwaremanagerimplementation.cpp @@ -44,9 +44,12 @@ #include "hardware/i2c/i2cmanagerimplementation.h" #include "hardware/zigbee/zigbeehardwareresourceimplementation.h" +#include "hardware/modbus/modbusrtumanager.h" +#include "hardware/modbus/modbusrtuhardwareresourceimplementation.h" + namespace nymeaserver { -HardwareManagerImplementation::HardwareManagerImplementation(Platform *platform, MqttBroker *mqttBroker, ZigbeeManager *zigbeeManager, QObject *parent) : +HardwareManagerImplementation::HardwareManagerImplementation(Platform *platform, MqttBroker *mqttBroker, ZigbeeManager *zigbeeManager, ModbusRtuManager *modbusRtuManager, QObject *parent) : HardwareManager(parent), m_platform(platform) { @@ -73,6 +76,8 @@ HardwareManagerImplementation::HardwareManagerImplementation(Platform *platform, m_zigbeeResource = new ZigbeeHardwareResourceImplementation(zigbeeManager, this); + m_modbusRtuResource = new ModbusRtuHardwareResourceImplementation(modbusRtuManager, this); + // Enable all the resources setResourceEnabled(m_pluginTimerManager, true); setResourceEnabled(m_radio433, true); @@ -142,6 +147,11 @@ ZigbeeHardwareResource *HardwareManagerImplementation::zigbeeResource() return m_zigbeeResource; } +ModbusRtuHardwareResource *HardwareManagerImplementation::modbusRtuResource() +{ + return m_modbusRtuResource; +} + void HardwareManagerImplementation::thingsLoaded() { m_zigbeeResource->thingsLoaded(); diff --git a/libnymea-core/hardwaremanagerimplementation.h b/libnymea-core/hardwaremanagerimplementation.h index 8a67e54e..c808b0d2 100644 --- a/libnymea-core/hardwaremanagerimplementation.h +++ b/libnymea-core/hardwaremanagerimplementation.h @@ -43,13 +43,15 @@ class Platform; class MqttBroker; class ZigbeeManager; class ZigbeeHardwareResourceImplementation; +class ModbusRtuManager; +class ModbusRtuHardwareResourceImplementation; class HardwareManagerImplementation : public HardwareManager { Q_OBJECT public: - explicit HardwareManagerImplementation(Platform *platform, MqttBroker *mqttBroker, ZigbeeManager *zigbeeManager, QObject *parent = nullptr); + explicit HardwareManagerImplementation(Platform *platform, MqttBroker *mqttBroker, ZigbeeManager *zigbeeManager, ModbusRtuManager *modbusRtuManager, QObject *parent = nullptr); ~HardwareManagerImplementation() override; Radio433 *radio433() override; @@ -61,6 +63,7 @@ public: MqttProvider *mqttProvider() override; I2CManager *i2cManager() override; ZigbeeHardwareResource *zigbeeResource() override; + ModbusRtuHardwareResource *modbusRtuResource() override; public slots: void thingsLoaded(); @@ -79,6 +82,7 @@ private: MqttProvider *m_mqttProvider = nullptr; I2CManager *m_i2cManager = nullptr; ZigbeeHardwareResourceImplementation *m_zigbeeResource = nullptr; + ModbusRtuHardwareResourceImplementation *m_modbusRtuResource = nullptr; }; } 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 new file mode 100644 index 00000000..89b169c4 --- /dev/null +++ b/libnymea-core/jsonrpc/modbusrtuhandler.cpp @@ -0,0 +1,286 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "modbusrtuhandler.h" +#include "hardware/modbus/modbusrtumanager.h" +#include "hardware/serialport/serialportmonitor.h" + +namespace nymeaserver { + +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()); + modbusRtuMasterDescription.insert("numberOfRetries", enumValueName(Uint)); + modbusRtuMasterDescription.insert("timeout", enumValueName(Uint)); + + 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("o:modbusRtuMasters", QVariantList() << objectRef("ModbusRtuMaster")); + returns.insert("modbusError", enumRef()); + 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 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 modbus RTU master has been changed in 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. The timeout value is in milli seconds and the minimum value is 10 ms."; + params.insert("serialPort", enumValueName(String)); + params.insert("baudrate", enumValueName(Uint)); + params.insert("parity", enumRef()); + params.insert("dataBits", enumRef()); + params.insert("stopBits", enumRef()); + params.insert("numberOfRetries", enumValueName(Uint)); + params.insert("timeout", enumValueName(Uint)); + 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 = "Reconfigure 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()); + params.insert("numberOfRetries", enumValueName(Uint)); + params.insert("timeout", enumValueName(Uint)); + 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 SerialPortRemoved(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 +{ + 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; + + if (m_modbusRtuManager->supported()) { + QVariantList modbusList; + foreach (ModbusRtuMaster *modbusMaster, m_modbusRtuManager->modbusRtuMasters()) { + modbusList << packModbusRtuMaster(modbusMaster); + } + returnMap.insert("modbusRtuMasters", modbusList); + returnMap.insert("modbusError", enumValueName(ModbusRtuManager::ModbusRtuErrorNoError)); + } else { + returnMap.insert("modbusError", enumValueName(ModbusRtuManager::ModbusRtuErrorNotSupported)); + } + + + 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())); + uint numberOfRetries = params.value("numberOfRetries").toUInt(); + uint timeout = params.value("timeout").toUInt(); + + QVariantMap returnMap; + if (timeout < 10) { + returnMap.insert("modbusError", enumValueName(ModbusRtuManager::ModbusRtuErrorInvalidTimeoutValue)); + return createReply(returnMap); + } + + QPair result = m_modbusRtuManager->addNewModbusRtuMaster(serialPort, baudrate, parity, dataBits, stopBits, numberOfRetries, timeout); + 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())); + uint numberOfRetries = params.value("numberOfRetries").toUInt(); + uint timeout = params.value("timeout").toUInt(); + + QVariantMap returnMap; + if (timeout < 10) { + returnMap.insert("modbusError", enumValueName(ModbusRtuManager::ModbusRtuErrorInvalidTimeoutValue)); + return createReply(returnMap); + } + + ModbusRtuManager::ModbusRtuError result = m_modbusRtuManager->reconfigureModbusRtuMaster(modbusUuid, serialPort, baudrate, parity, dataBits, stopBits, numberOfRetries, timeout); + 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()))); + modbusRtuMasterMap.insert("numberOfRetries", modbusRtuMaster->numberOfRetries()); + modbusRtuMasterMap.insert("timeout", modbusRtuMaster->timeout()); + return modbusRtuMasterMap; +} + +} diff --git a/libnymea-core/jsonrpc/modbusrtuhandler.h b/libnymea-core/jsonrpc/modbusrtuhandler.h new file mode 100644 index 00000000..171d3540 --- /dev/null +++ b/libnymea-core/jsonrpc/modbusrtuhandler.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 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 MODBUSRTUHANDLER_H +#define MODBUSRTUHANDLER_H + +#include + +#include "jsonrpc/jsonhandler.h" +#include "hardware/modbus/modbusrtumaster.h" + +namespace nymeaserver { + +class ModbusRtuManager; + +class ModbusRtuHandler : public JsonHandler +{ + Q_OBJECT +public: + explicit ModbusRtuHandler(ModbusRtuManager *modbusRtuManager, QObject *parent = nullptr); + + QString name() const override; + + 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); +}; + +} + +#endif // MODBUSRTUHANDLER_H diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index 1d2d0197..7985a191 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -3,7 +3,7 @@ TARGET = nymea-core include(../nymea.pri) -QT += bluetooth dbus qml sql websockets +QT += bluetooth dbus qml sql websockets serialport INCLUDEPATH += $$top_srcdir/libnymea $$top_builddir LIBS += -L$$top_builddir/libnymea/ -lnymea -lssl -lcrypto @@ -35,6 +35,27 @@ CONFIG(withoutpython) { CONFIG -= python } +# Qt serial bus module is officially available since Qt 5.8 +# but not all platforms host the qt serialbus package. +# Let's check if the package exists, not the qt version +packagesExist(Qt5SerialBus) { + message("Building with QtSerialBus support.") + # Qt += serialbus + PKGCONFIG += Qt5SerialBus + DEFINES += WITH_QTSERIALBUS +} else { + message("Qt5SerialBus package not found. Building without QtSerialBus support.") +} + +# Note: udev is not available on all platforms +packagesExist(libudev) { + message("Building with udev support") + PKGCONFIG += libudev + DEFINES += WITH_UDEV +} else { + message("Building without udev support.") +} + target.path = $$[QT_INSTALL_LIBS] INSTALLS += target @@ -44,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 \ @@ -55,6 +77,7 @@ HEADERS += nymeacore.h \ integrations/thingmanagerimplementation.h \ integrations/translator.h \ experiences/experiencemanager.h \ + jsonrpc/modbusrtuhandler.h \ jsonrpc/zigbeehandler.h \ ruleengine/ruleengine.h \ ruleengine/rule.h \ @@ -118,6 +141,10 @@ HEADERS += nymeacore.h \ hardware/bluetoothlowenergy/bluetoothlowenergymanagerimplementation.h \ hardware/bluetoothlowenergy/bluetoothlowenergydeviceimplementation.h \ hardware/bluetoothlowenergy/bluetoothdiscoveryreplyimplementation.h \ + hardware/modbus/modbusrtuhardwareresourceimplementation.h \ + hardware/modbus/modbusrtumanager.h \ + hardware/modbus/modbusrtumasterimpl.h \ + hardware/modbus/modbusrtureplyimpl.h \ hardware/network/networkaccessmanagerimpl.h \ hardware/network/upnp/upnpdiscoveryimplementation.h \ hardware/network/upnp/upnpdiscoveryrequest.h \ @@ -138,11 +165,13 @@ HEADERS += nymeacore.h \ SOURCES += nymeacore.cpp \ + hardware/serialport/serialportmonitor.cpp \ integrations/apikeysprovidersloader.cpp \ integrations/plugininfocache.cpp \ integrations/thingmanagerimplementation.cpp \ integrations/translator.cpp \ experiences/experiencemanager.cpp \ + jsonrpc/modbusrtuhandler.cpp \ jsonrpc/zigbeehandler.cpp \ ruleengine/ruleengine.cpp \ ruleengine/rule.cpp \ @@ -205,6 +234,10 @@ SOURCES += nymeacore.cpp \ hardware/bluetoothlowenergy/bluetoothlowenergymanagerimplementation.cpp \ hardware/bluetoothlowenergy/bluetoothlowenergydeviceimplementation.cpp \ hardware/bluetoothlowenergy/bluetoothdiscoveryreplyimplementation.cpp \ + hardware/modbus/modbusrtuhardwareresourceimplementation.cpp \ + hardware/modbus/modbusrtumanager.cpp \ + hardware/modbus/modbusrtumasterimpl.cpp \ + hardware/modbus/modbusrtureplyimpl.cpp \ hardware/network/networkaccessmanagerimpl.cpp \ hardware/network/upnp/upnpdiscoveryimplementation.cpp \ hardware/network/upnp/upnpdiscoveryrequest.cpp \ diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index a746f8b8..7e6f7d3d 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -53,6 +53,9 @@ #include "zigbee/zigbeemanager.h" +#include "hardware/modbus/modbusrtumanager.h" +#include "hardware/serialport/serialportmonitor.h" + #include #include @@ -108,8 +111,14 @@ 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(m_serialPortMonitor, this); + qCDebug(dcCore) << "Creating Hardware Manager"; - m_hardwareManager = new HardwareManagerImplementation(m_platform, m_serverManager->mqttBroker(), m_zigbeeManager, this); + m_hardwareManager = new HardwareManagerImplementation(m_platform, m_serverManager->mqttBroker(), m_zigbeeManager, m_modbusRtuManager, this); qCDebug(dcCore) << "Creating Thing Manager (locale:" << m_configuration->locale() << ")"; m_thingManager = new ThingManagerImplementation(m_hardwareManager, m_configuration->locale(), this); @@ -645,6 +654,11 @@ ZigbeeManager *NymeaCore::zigbeeManager() const return m_zigbeeManager; } +ModbusRtuManager *NymeaCore::modbusRtuManager() const +{ + return m_modbusRtuManager; +} + void NymeaCore::gotEvent(const Event &event) { emit eventTriggered(event); diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index 3201b4d6..b67ac29a 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -67,6 +67,8 @@ class ExperienceManager; class ScriptEngine; class CloudManager; class ZigbeeManager; +class ModbusRtuManager; +class SerialPortMonitor; class NymeaCore : public QObject { @@ -107,6 +109,7 @@ public: TagsStorage *tagsStorage() const; Platform *platform() const; ZigbeeManager *zigbeeManager() const; + ModbusRtuManager *modbusRtuManager() const; static QStringList getAvailableLanguages(); static QStringList loggingFilters(); @@ -151,6 +154,8 @@ private: System *m_system; ExperienceManager *m_experienceManager; ZigbeeManager *m_zigbeeManager; + SerialPortMonitor *m_serialPortMonitor; + ModbusRtuManager *m_modbusRtuManager; QList m_executingRules; diff --git a/libnymea/hardware/modbus/modbusrtuhardwareresource.cpp b/libnymea/hardware/modbus/modbusrtuhardwareresource.cpp new file mode 100644 index 00000000..ddab12a7 --- /dev/null +++ b/libnymea/hardware/modbus/modbusrtuhardwareresource.cpp @@ -0,0 +1,38 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "modbusrtuhardwareresource.h" +#include "modbusrtumaster.h" + +ModbusRtuHardwareResource::ModbusRtuHardwareResource(QObject *parent) : + HardwareResource("Modbus RTU resource", parent) +{ + +} diff --git a/libnymea/hardware/modbus/modbusrtuhardwareresource.h b/libnymea/hardware/modbus/modbusrtuhardwareresource.h new file mode 100644 index 00000000..9be1a590 --- /dev/null +++ b/libnymea/hardware/modbus/modbusrtuhardwareresource.h @@ -0,0 +1,59 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 MODBUSRTUHARDWARERESOURCE_H +#define MODBUSRTUHARDWARERESOURCE_H + +#include +#include + +#include "hardwareresource.h" +#include "modbusrtumaster.h" + +class ModbusRtuHardwareResource : public HardwareResource +{ + Q_OBJECT +public: + virtual QList modbusRtuMasters() const = 0; + virtual bool hasModbusRtuMaster(const QUuid &modbusUuid) const = 0; + virtual ModbusRtuMaster *getModbusRtuMaster(const QUuid &modbusUuid) const = 0; + +protected: + explicit ModbusRtuHardwareResource(QObject *parent = nullptr); + virtual ~ModbusRtuHardwareResource() = default; + +signals: + void modbusRtuMasterAdded(const QUuid &modbusUuid); + void modbusRtuMasterRemoved(const QUuid &modbusUuid); + void modbusRtuMasterChanged(const QUuid &modbusUuid); + +}; + +#endif // MODBUSRTUHARDWARERESOURCE_H diff --git a/libnymea/hardware/modbus/modbusrtumaster.h b/libnymea/hardware/modbus/modbusrtumaster.h new file mode 100644 index 00000000..39ba94be --- /dev/null +++ b/libnymea/hardware/modbus/modbusrtumaster.h @@ -0,0 +1,95 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 MODBUSRTUMASTER_H +#define MODBUSRTUMASTER_H + +#include +#include +#include +#include + +#include "modbusrtureply.h" + +class ModbusRtuMaster : public QObject +{ + Q_OBJECT +public: + // Properties + virtual QUuid modbusUuid() const = 0; + virtual QString serialPort() const = 0; + virtual qint32 baudrate() const = 0; + virtual QSerialPort::Parity parity() const = 0; + virtual QSerialPort::DataBits dataBits() const = 0; + virtual QSerialPort::StopBits stopBits() = 0; + virtual int numberOfRetries() const = 0; + virtual int timeout() const = 0; + + virtual bool connected() const = 0; + + // Requests + virtual ModbusRtuReply *readCoil(int slaveAddress, int registerAddress, quint16 size = 1) = 0; + virtual ModbusRtuReply *readDiscreteInput(int slaveAddress, int registerAddress, quint16 size = 1) = 0; + virtual ModbusRtuReply *readInputRegister(int slaveAddress, int registerAddress, quint16 size = 1) = 0; + virtual ModbusRtuReply *readHoldingRegister(int slaveAddress, int registerAddress, quint16 size = 1) = 0; + + virtual ModbusRtuReply *writeCoils(int slaveAddress, int registerAddress, const QVector &values) = 0; + virtual ModbusRtuReply *writeHoldingRegisters(int slaveAddress, int registerAddress, const QVector &values) = 0; + +protected: + explicit ModbusRtuMaster(QObject *parent = nullptr) : QObject(parent) { }; + virtual ~ModbusRtuMaster() = default; + +signals: + void connectedChanged(bool connected); + void serialPortChanged(const QString &serialPort); + void baudrateChanged(quint32 baudrate); + void parityChanged(QSerialPort::Parity parity); + void dataBitsChanged(QSerialPort::DataBits dataBits); + void stopBitsChanged(QSerialPort::StopBits stopBits); + void numberOfRetriesChanged(int numberOfRetries); + void timeoutChanged(int timeout); + +}; + +inline QDebug operator<<(QDebug debug, ModbusRtuMaster *modbusRtuMaster) { + debug.nospace() << "ModbusRtuMaster(" << modbusRtuMaster->modbusUuid().toString(); + debug.nospace() << ", " << modbusRtuMaster->serialPort(); + debug.nospace() << ", BaudRate: " << modbusRtuMaster->baudrate(); + debug.nospace() << ", " << modbusRtuMaster->dataBits(); + debug.nospace() << ", " << modbusRtuMaster->stopBits(); + debug.nospace() << ", " << modbusRtuMaster->parity(); + debug.nospace() << ", Retries: " << modbusRtuMaster->numberOfRetries(); + debug.nospace() << ", Timeout: " << modbusRtuMaster->timeout() << "ms)"; + return debug.space(); +}; + + +#endif // MODBUSRTUMASTER_H diff --git a/libnymea/hardware/modbus/modbusrtureply.h b/libnymea/hardware/modbus/modbusrtureply.h new file mode 100644 index 00000000..46e5a86a --- /dev/null +++ b/libnymea/hardware/modbus/modbusrtureply.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 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 MODBUSRTUREPLY_H +#define MODBUSRTUREPLY_H + +#include +#include + +class ModbusRtuReply : public QObject +{ + Q_OBJECT +public: + enum Error { + NoError, + ReadError, + WriteError, + ConnectionError, + ConfigurationError, + TimeoutError, + ProtocolError, + ReplyAbortedError, + UnknownError + }; + Q_ENUM(Error) + + virtual bool isFinished() const = 0; + + virtual int slaveAddress() const = 0; + virtual int registerAddress() const = 0; + + virtual QString errorString() const = 0; + virtual ModbusRtuReply::Error error() const = 0; + + virtual QVector result() const = 0; + +protected: + explicit ModbusRtuReply(QObject *parent = nullptr) : QObject(parent) { }; + virtual ~ModbusRtuReply() = default; + +signals: + void finished(); + void errorOccurred(ModbusRtuReply::Error error); + +}; + +#endif // MODBUSRTUREPLY_H diff --git a/libnymea/hardwaremanager.h b/libnymea/hardwaremanager.h index 745a4ca9..4dab713e 100644 --- a/libnymea/hardwaremanager.h +++ b/libnymea/hardwaremanager.h @@ -44,6 +44,7 @@ class MqttProvider; class I2CManager; class ZigbeeHardwareResource; class HardwareResource; +class ModbusRtuHardwareResource; class HardwareManager : public QObject { @@ -63,6 +64,7 @@ public: virtual MqttProvider *mqttProvider() = 0; virtual I2CManager *i2cManager() = 0; virtual ZigbeeHardwareResource *zigbeeResource() = 0; + virtual ModbusRtuHardwareResource *modbusRtuResource() = 0; protected: void setResourceEnabled(HardwareResource* resource, bool enabled); diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 02e7b57f..41752d75 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -3,7 +3,7 @@ include(../nymea.pri) TARGET = nymea TEMPLATE = lib -QT += network bluetooth dbus +QT += network bluetooth dbus serialport QT -= gui DEFINES += LIBNYMEA_LIBRARY @@ -13,6 +13,9 @@ PKGCONFIG += nymea-zigbee nymea-mqtt QMAKE_LFLAGS += -fPIC HEADERS += \ + hardware/modbus/modbusrtuhardwareresource.h \ + hardware/modbus/modbusrtumaster.h \ + hardware/modbus/modbusrtureply.h \ hardware/zigbee/zigbeehandler.h \ hardware/zigbee/zigbeehardwareresource.h \ integrations/browseractioninfo.h \ @@ -108,6 +111,7 @@ HEADERS += \ experiences/experienceplugin.h \ SOURCES += \ + hardware/modbus/modbusrtuhardwareresource.cpp \ hardware/zigbee/zigbeehandler.cpp \ hardware/zigbee/zigbeehardwareresource.cpp \ integrations/browseractioninfo.cpp \ diff --git a/libnymea/nymeasettings.cpp b/libnymea/nymeasettings.cpp index 417c7472..e57c3a25 100644 --- a/libnymea/nymeasettings.cpp +++ b/libnymea/nymeasettings.cpp @@ -64,6 +64,10 @@ This role will create the \b{mqttpolicies.conf} file and is used to store the \l{MqttPolicy}{MqttPolicies}. \value SettingsRoleIOConnections This role will create the \b{ioconnections.conf} file and is used to store the \l{IOConnection}{IOConnections}. + \value SettingsRoleZigbee + This role will create the \b{zigbee.conf} file and is used to store the Zigbee networks. + \value SettingsRoleModbusRtu + This role will create the \b{modbusrtu.conf} file and is used to store the modbus RTU resources. */ @@ -83,7 +87,7 @@ NymeaSettings::NymeaSettings(const SettingsRole &role, QObject *parent): QString settingsPrefix = QCoreApplication::instance()->organizationName() + "/"; QString basePath; - if (!qgetenv("SNAP").isEmpty()) { + if (!qEnvironmentVariableIsEmpty("SNAP")) { basePath = QString(qgetenv("SNAP_DATA")) + "/"; settingsPrefix.clear(); // We don't want that in the snappy case... } else if (settingsPrefix == "nymea-test/") { @@ -125,6 +129,9 @@ NymeaSettings::NymeaSettings(const SettingsRole &role, QObject *parent): case SettingsRoleZigbee: fileName = "zigbee.conf"; break; + case SettingsRoleModbusRtu: + fileName = "modbusrtu.conf"; + break; } m_settings = new QSettings(basePath + settingsPrefix + fileName, QSettings::IniFormat, this); } @@ -157,7 +164,7 @@ QString NymeaSettings::settingsPath() QString path; QString organisationName = QCoreApplication::instance()->organizationName(); - if (!qgetenv("SNAP").isEmpty()) { + if (!qEnvironmentVariableIsEmpty("SNAP")) { path = QString(qgetenv("SNAP_DATA")); } else if (organisationName == "nymea-test") { path = "/tmp/" + organisationName; @@ -174,7 +181,7 @@ QString NymeaSettings::translationsPath() { QString organisationName = QCoreApplication::instance()->organizationName(); - if (!qgetenv("SNAP").isEmpty()) { + if (!qEnvironmentVariableIsEmpty("SNAP")) { return QString(qgetenv("SNAP") + "/usr/share/nymea/translations"); } else if (organisationName == "nymea-test") { return "/tmp/" + organisationName; @@ -188,7 +195,7 @@ QString NymeaSettings::storagePath() { QString path; QString organisationName = QCoreApplication::instance()->organizationName(); - if (!qgetenv("SNAP").isEmpty()) { + if (!qEnvironmentVariableIsEmpty("SNAP")) { path = QString(qgetenv("SNAP_DATA")); } else if (organisationName == "nymea-test") { path = "/tmp/" + organisationName; diff --git a/libnymea/nymeasettings.h b/libnymea/nymeasettings.h index 36301eb3..8c514b68 100644 --- a/libnymea/nymeasettings.h +++ b/libnymea/nymeasettings.h @@ -53,7 +53,9 @@ public: SettingsRoleMqttPolicies, SettingsRoleIOConnections, SettingsRoleZigbee, + SettingsRoleModbusRtu }; + Q_ENUM(SettingsRole) explicit NymeaSettings(const SettingsRole &role = SettingsRoleNone, QObject *parent = nullptr); ~NymeaSettings(); diff --git a/tests/auto/api.json b/tests/auto/api.json index f088c2c0..b7ded3e8 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -150,6 +150,16 @@ "MediaBrowserIconSoundCloud", "MediaBrowserIconRadioParadise" ], + "ModbusRtuError": [ + "ModbusRtuErrorNoError", + "ModbusRtuErrorNotAvailable", + "ModbusRtuErrorUuidNotFound", + "ModbusRtuErrorHardwareNotFound", + "ModbusRtuErrorResourceBusy", + "ModbusRtuErrorNotSupported", + "ModbusRtuErrorInvalidTimeoutValue", + "ModbusRtuErrorConnectionFailed" + ], "NetworkDeviceState": [ "NetworkDeviceStateUnknown", "NetworkDeviceStateUnmanaged", @@ -232,6 +242,27 @@ "ScriptMessageTypeLog", "ScriptMessageTypeWarning" ], + "SerialPortDataBits": [ + "SerialPortDataBitsData5", + "SerialPortDataBitsData6", + "SerialPortDataBitsData7", + "SerialPortDataBitsData8", + "SerialPortDataBitsUnknownDataBits" + ], + "SerialPortParity": [ + "SerialPortParityNoParity", + "SerialPortParityEvenParity", + "SerialPortParityOddParity", + "SerialPortParitySpaceParity", + "SerialPortParityMarkParity", + "SerialPortParityUnknownParity" + ], + "SerialPortStopBits": [ + "SerialPortStopBitsOneStop", + "SerialPortStopBitsOneAndHalfStop", + "SerialPortStopBitsTwoStop", + "SerialPortStopBitsUnknownStopBits" + ], "SetupMethod": [ "SetupMethodJustAdd", "SetupMethodDisplayPin", @@ -1491,6 +1522,66 @@ "offset": "Int" } }, + "ModbusRtu.AddModbusRtuMaster": { + "description": "Add a new modbus RTU master with the given configuration. The timeout value is in milli seconds and the minimum value is 10 ms.", + "params": { + "baudrate": "Uint", + "dataBits": "$ref:SerialPortDataBits", + "numberOfRetries": "Uint", + "parity": "$ref:SerialPortParity", + "serialPort": "String", + "stopBits": "$ref:SerialPortStopBits", + "timeout": "Uint" + }, + "returns": { + "modbusError": "$ref:ModbusRtuError", + "o:modbusUuid": "Uuid" + } + }, + "ModbusRtu.GetModbusRtuMasters": { + "description": "Get the list of configured modbus RTU masters.", + "params": { + }, + "returns": { + "modbusError": "$ref:ModbusRtuError", + "o:modbusRtuMasters": [ + "$ref:ModbusRtuMaster" + ] + } + }, + "ModbusRtu.GetSerialPorts": { + "description": "Get the list of available serial ports from the host system.", + "params": { + }, + "returns": { + "serialPorts": "$ref:SerialPorts" + } + }, + "ModbusRtu.ReconfigureModbusRtuMaster": { + "description": "Reconfigure the modbus RTU master with the given UUID and configuration.", + "params": { + "baudrate": "Uint", + "dataBits": "$ref:SerialPortDataBits", + "modbusUuid": "Uuid", + "numberOfRetries": "Uint", + "parity": "$ref:SerialPortParity", + "serialPort": "String", + "stopBits": "$ref:SerialPortStopBits", + "timeout": "Uint" + }, + "returns": { + "modbusError": "$ref:ModbusRtuError" + } + }, + "ModbusRtu.RemoveModbusRtuMaster": { + "description": "Remove the modbus RTU master with the given modbus UUID.", + "params": { + "modbusUuid": "Uuid" + }, + "returns": { + "modbusError": "$ref:ModbusRtuError" + } + }, "NetworkManager.ConnectWifiNetwork": { "description": "Connect to the wifi network with the given ssid and password.", "params": { @@ -2351,6 +2442,36 @@ "logEntry": "$ref:LogEntry" } }, + "ModbusRtu.ModbusRtuMasterAdded": { + "description": "Emitted whenever a new modbus RTU master has been added to the system.", + "params": { + "modbusRtuMaster": "$ref:ModbusRtuMaster" + } + }, + "ModbusRtu.ModbusRtuMasterChanged": { + "description": "Emitted whenever a modbus RTU master has been changed in the system.", + "params": { + "modbusRtuMaster": "$ref:ModbusRtuMaster" + } + }, + "ModbusRtu.ModbusRtuMasterRemoved": { + "description": "Emitted whenever a modbus RTU master has been removed from the system.", + "params": { + "modbusUuid": "Uuid" + } + }, + "ModbusRtu.SerialPortAdded": { + "description": "Emitted whenever a serial port has been added to the system.", + "params": { + "serialPort": "$ref:SerialPort" + } + }, + "ModbusRtu.SerialPortRemoved": { + "description": "Emitted whenever a serial port has been removed from the system.", + "params": { + "serialPort": "$ref:SerialPort" + } + }, "NetworkManager.NetworkStatusChanged": { "description": "Emitted whenever a status of a NetworkManager changes.", "params": { @@ -2765,6 +2886,17 @@ "r:source": "$ref:LoggingSource", "r:timestamp": "Uint" }, + "ModbusRtuMaster": { + "baudrate": "Uint", + "connected": "Bool", + "dataBits": "$ref:SerialPortDataBits", + "modbusUuid": "Uuid", + "numberOfRetries": "Uint", + "parity": "$ref:SerialPortParity", + "serialPort": "String", + "stopBits": "$ref:SerialPortStopBits", + "timeout": "Uint" + }, "MqttPolicy": { "allowedPublishTopicFilters": "StringList", "allowedSubscribeTopicFilters": "StringList", @@ -2892,6 +3024,15 @@ "Scripts": [ "$ref:Script" ], + "SerialPort": { + "r:description": "String", + "r:manufacturer": "String", + "r:serialNumber": "String", + "r:systemLocation": "String" + }, + "SerialPorts": [ + "$ref:SerialPort" + ], "ServerConfiguration": { "address": "String", "authenticationEnabled": "Bool", diff --git a/tests/auto/autotests.pri b/tests/auto/autotests.pri index e2572913..5d2153e6 100644 --- a/tests/auto/autotests.pri +++ b/tests/auto/autotests.pri @@ -2,6 +2,18 @@ QT += dbus testlib network sql websockets CONFIG += testcase CONFIG += link_pkgconfig +# Qt serial bus module is officially available since Qt 5.8 +# but not all platforms host the qt serialbus package. +# Let's check if the package exists, not the qt version +packagesExist(Qt5SerialBus) { + message("Building with QtSerialBus support.") + # Qt += serialbus + PKGCONFIG += Qt5SerialBus + DEFINES += WITH_QTSERIALBUS +} else { + message("Qt5SerialBus package not found. Building without QtSerialBus support.") +} + INCLUDEPATH += $$top_srcdir/libnymea \ $$top_srcdir/libnymea-core \ $$top_srcdir/tests/testlib/ \ diff --git a/tests/auto/jsonrpc/testjsonrpc.cpp b/tests/auto/jsonrpc/testjsonrpc.cpp index 813b7215..ddafd3c0 100644 --- a/tests/auto/jsonrpc/testjsonrpc.cpp +++ b/tests/auto/jsonrpc/testjsonrpc.cpp @@ -674,7 +674,7 @@ void TestJSONRPC::enableDisableNotifications_legacy() QStringList expectedNamespaces; if (enabled == "true") { - expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "Integrations" << "System" << "Rules" << "States" << "Logging" << "Tags" << "AppData" << "JSONRPC" << "Configuration" << "Events" << "Scripts" << "Users" << "Zigbee"; + expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "Integrations" << "System" << "Rules" << "States" << "Logging" << "Tags" << "AppData" << "JSONRPC" << "Configuration" << "Events" << "Scripts" << "Users" << "Zigbee" << "ModbusRtu"; } std::sort(expectedNamespaces.begin(), expectedNamespaces.end()); diff --git a/tests/testlib/testlib.pro b/tests/testlib/testlib.pro index 2c27f65e..b1768333 100644 --- a/tests/testlib/testlib.pro +++ b/tests/testlib/testlib.pro @@ -5,6 +5,14 @@ include(../../nymea.pri) QT += testlib dbus network sql websockets +# Qt serial bus module is officially available since Qt 5.8 +# but not all platforms host the qt serialbus package. +# Let's check if the package exists, not the qt version +packagesExist(Qt5SerialBus) { + Qt += serialbus + DEFINES += WITH_QTSERIALBUS +} + INCLUDEPATH += $$top_srcdir/libnymea \ $$top_srcdir/libnymea-core