diff --git a/libnymea-app/libnymea-app-core.h b/libnymea-app/libnymea-app-core.h index 5e96c171..ff5d51ba 100644 --- a/libnymea-app/libnymea-app-core.h +++ b/libnymea-app/libnymea-app-core.h @@ -140,6 +140,9 @@ #include "energy/powerbalancelogs.h" #include "energy/thingpowerlogs.h" #include "pluginconfigmanager.h" +#include "zwave/zwavemanager.h" +#include "zwave/zwavenetwork.h" +#include "zwave/zwavenode.h" #include @@ -329,6 +332,13 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "ZigbeeNodes", "Get it from the ZigbeeNetwork"); qmlRegisterType(uri, 1, 0, "ZigbeeNodesProxy"); + qmlRegisterType(uri, 1, 0, "ZWaveManager"); + qmlRegisterUncreatableType(uri, 1, 0, "ZWaveNetworks", "Get it from ZWaveManager"); + qmlRegisterUncreatableType(uri, 1, 0, "ZWaveNetwork", "Get it from ZWaveNetworks"); + qmlRegisterUncreatableType(uri, 1, 0, "ZWaveNodes", "Get it from ZWaveNetwork"); + qmlRegisterUncreatableType(uri, 1, 0, "ZWaveNode", "Get it from ZWaveNodes"); + qmlRegisterType(uri, 1, 0, "ZWaveNodesProxy"); + qmlRegisterType(uri, 1, 0, "ModbusRtuManager"); qmlRegisterUncreatableType(uri, 1, 0, "ModbusRtuMaster", "Get it from the ModbusRtuMasters"); qmlRegisterUncreatableType(uri, 1, 0, "ModbusRtuMasters", "Get it from the ModbusRtuManager"); diff --git a/libnymea-app/libnymea-app.pri b/libnymea-app/libnymea-app.pri index 46dd3bb8..f67f930c 100644 --- a/libnymea-app/libnymea-app.pri +++ b/libnymea-app/libnymea-app.pri @@ -32,6 +32,9 @@ SOURCES += \ $$PWD/zigbee/zigbeenode.cpp \ $$PWD/zigbee/zigbeenodes.cpp \ $$PWD/zigbee/zigbeenodesproxy.cpp \ + $$PWD/zwave/zwavemanager.cpp \ + $$PWD/zwave/zwavenetwork.cpp \ + $$PWD/zwave/zwavenode.cpp \ $${PWD}/logging.cpp \ $${PWD}/applogcontroller.cpp \ $${PWD}/wifisetup/btwifisetup.cpp \ @@ -194,6 +197,9 @@ HEADERS += \ $$PWD/zigbee/zigbeenode.h \ $$PWD/zigbee/zigbeenodes.h \ $$PWD/zigbee/zigbeenodesproxy.h \ + $$PWD/zwave/zwavemanager.h \ + $$PWD/zwave/zwavenetwork.h \ + $$PWD/zwave/zwavenode.h \ $${PWD}/logging.h \ $${PWD}/applogcontroller.h \ $${PWD}/wifisetup/btwifisetup.h \ diff --git a/libnymea-app/types/serialports.cpp b/libnymea-app/types/serialports.cpp index 5a5805f9..2015a916 100644 --- a/libnymea-app/types/serialports.cpp +++ b/libnymea-app/types/serialports.cpp @@ -44,7 +44,7 @@ int SerialPorts::rowCount(const QModelIndex &parent) const QVariant SerialPorts::data(const QModelIndex &index, int role) const { switch (role) { - case RoleSystmLocation: + case RoleSystemLocation: return m_serialPorts.at(index.row())->systemLocation(); case RoleManufacturer: return m_serialPorts.at(index.row())->manufacturer(); @@ -59,7 +59,7 @@ QVariant SerialPorts::data(const QModelIndex &index, int role) const QHash SerialPorts::roleNames() const { QHash roles; - roles.insert(RoleSystmLocation, "systemLocation"); + roles.insert(RoleSystemLocation, "systemLocation"); roles.insert(RoleManufacturer, "manufacturer"); roles.insert(RoleDescription, "description"); roles.insert(RoleSerialNumber, "serialNumber"); diff --git a/libnymea-app/types/serialports.h b/libnymea-app/types/serialports.h index 7a2a8506..730fff43 100644 --- a/libnymea-app/types/serialports.h +++ b/libnymea-app/types/serialports.h @@ -43,7 +43,7 @@ class SerialPorts : public QAbstractListModel public: enum Roles { - RoleSystmLocation, + RoleSystemLocation, RoleManufacturer, RoleDescription, RoleSerialNumber diff --git a/libnymea-app/types/serialportsproxy.cpp b/libnymea-app/types/serialportsproxy.cpp index 8d6dc705..87e80a21 100644 --- a/libnymea-app/types/serialportsproxy.cpp +++ b/libnymea-app/types/serialportsproxy.cpp @@ -45,7 +45,10 @@ void SerialPortsProxy::setSerialPorts(SerialPorts *serialPorts) if (m_serialPorts == serialPorts) return; + setSourceModel(serialPorts); m_serialPorts = serialPorts; + emit serialPortsChanged(); + connect(m_serialPorts, &SerialPorts::countChanged, this, [this](){ emit countChanged(); }); @@ -53,8 +56,7 @@ void SerialPortsProxy::setSerialPorts(SerialPorts *serialPorts) sort(0, Qt::DescendingOrder); }); - setSourceModel(serialPorts); - setSortRole(SerialPorts::RoleSystmLocation); + setSortRole(SerialPorts::RoleSystemLocation); sort(0, Qt::DescendingOrder); emit countChanged(); } @@ -67,3 +69,34 @@ SerialPort *SerialPortsProxy::get(int index) const return nullptr; } +QString SerialPortsProxy::systemLocationFilter() const +{ + return m_systemLocationFilter; +} + +void SerialPortsProxy::setSystemLocationFilter(const QString &systemLocationFilter) +{ + if (m_systemLocationFilter != systemLocationFilter) { + m_systemLocationFilter = systemLocationFilter; + emit systemLocationFilterChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +bool SerialPortsProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + Q_UNUSED(sourceParent) + if (!m_serialPorts) { + return false; + } + + if (!m_systemLocationFilter.isEmpty()) { + SerialPort *serialPort = m_serialPorts->get(sourceRow); + if (serialPort->systemLocation() != m_systemLocationFilter) { + return false; + } + } + return true; +} + diff --git a/libnymea-app/types/serialportsproxy.h b/libnymea-app/types/serialportsproxy.h index a6f16d05..a279b4e9 100644 --- a/libnymea-app/types/serialportsproxy.h +++ b/libnymea-app/types/serialportsproxy.h @@ -41,6 +41,8 @@ class SerialPortsProxy : public QSortFilterProxyModel Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(SerialPorts* serialPorts READ serialPorts WRITE setSerialPorts NOTIFY serialPortsChanged) + Q_PROPERTY(QString systemLocationFilter READ systemLocationFilter WRITE setSystemLocationFilter NOTIFY systemLocationFilterChanged) public: explicit SerialPortsProxy(QObject *parent = nullptr); @@ -50,12 +52,22 @@ public: Q_INVOKABLE SerialPort* get(int index) const; + QString systemLocationFilter() const; + void setSystemLocationFilter(const QString &systemLocationFilter); + signals: + void serialPortsChanged(); void countChanged(); + void systemLocationFilterChanged(); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; private: SerialPorts *m_serialPorts = nullptr; + QString m_systemLocationFilter; + }; #endif // SERIALPORTSPROXY_H diff --git a/libnymea-app/zwave/zwavemanager.cpp b/libnymea-app/zwave/zwavemanager.cpp new file mode 100644 index 00000000..cc5ed09a --- /dev/null +++ b/libnymea-app/zwave/zwavemanager.cpp @@ -0,0 +1,336 @@ +#include "zwavemanager.h" + +#include +#include + +#include "types/serialports.h" +#include "types/serialport.h" +#include "zwavenetwork.h" +#include "zwavenode.h" + +#include "engine.h" +#include "logging.h" +NYMEA_LOGGING_CATEGORY(dcZWave, "ZWave") + +ZWaveManager::ZWaveManager(QObject *parent): + QObject{parent}, + m_serialPorts(new SerialPorts(this)), + m_networks(new ZWaveNetworks(this)) +{ + +} + +ZWaveManager::~ZWaveManager() +{ + if (m_engine) { + m_engine->jsonRpcClient()->unregisterNotificationHandler(this); + } +} + +void ZWaveManager::setEngine(Engine *engine) +{ + if (m_engine != engine) { + if (m_engine) { + m_engine->jsonRpcClient()->unregisterNotificationHandler(this); + } + + m_engine = engine; + emit engineChanged(); + init(); + } +} + +Engine *ZWaveManager::engine() const +{ + return m_engine; +} + +bool ZWaveManager::fetchingData() const +{ + return m_fetchingData; +} + +SerialPorts *ZWaveManager::serialPorts() const +{ + return m_serialPorts; +} + +ZWaveNetworks *ZWaveManager::networks() const +{ + return m_networks; +} + +int ZWaveManager::addNetwork(const QString &serialPort) +{ + QVariantMap params; + params.insert("serialPort", serialPort); + return m_engine->jsonRpcClient()->sendCommand("ZWave.AddNetwork", params, this, "addNetworkResponse"); +} + +int ZWaveManager::removeNetwork(const QUuid &networkUuid) +{ + QVariantMap params = {{"networkUuid", networkUuid}}; + return m_engine->jsonRpcClient()->sendCommand("ZWave.RemoveNetwork", params, this, "removeNetworkResponse"); +} + +void ZWaveManager::addNode(const QUuid &networkUuid) +{ + QVariantMap params = {{"networkUuid", networkUuid}}; + m_engine->jsonRpcClient()->sendCommand("ZWave.AddNode", params, this, "addNodeResponse"); +} + +void ZWaveManager::cancelPendingOperation(const QUuid &networkUuid) +{ + m_engine->jsonRpcClient()->sendCommand("ZWave.CancelPendingOperation", {{"networkUuid", networkUuid}}, this, "cancelPendingOperationResponse"); +} + +int ZWaveManager::softResetController(const QUuid &networkUuid) +{ + QVariantMap params = {{"networkUuid", networkUuid}}; + return m_engine->jsonRpcClient()->sendCommand("ZWave.SoftResetController", params, this, "softResetControllerResponse"); +} + +int ZWaveManager::factoryResetNetwork(const QUuid &networkUuid) +{ + QVariantMap params = {{"networkUuid", networkUuid}}; + return m_engine->jsonRpcClient()->sendCommand("ZWave.FactoryResetNetwork", params, this, "factoryResetNetworkResponse"); +} + +int ZWaveManager::removeNode(const QUuid &networkUuid) +{ + return m_engine->jsonRpcClient()->sendCommand("ZWave.RemoveNode", {{"networkUuid", networkUuid}}, this, "removeNodeResponse"); +} + +int ZWaveManager::removeFailedNode(const QUuid &networkUuid, int nodeId) +{ + return m_engine->jsonRpcClient()->sendCommand("ZWave.RemoveFailedNode", {{"networkUuid", networkUuid}, {"nodeId", nodeId}}, this, "removeFailedNodeResponse"); +} + + +void ZWaveManager::init() +{ + m_fetchingData = true; + emit fetchingDataChanged(); + + m_networks->clear(); + m_serialPorts->clear(); + + m_engine->jsonRpcClient()->registerNotificationHandler(this, "ZWave", "notificationReceived"); + + m_engine->jsonRpcClient()->sendCommand("ZWave.GetSerialPorts", this, "getSerialPortsResponse"); + m_engine->jsonRpcClient()->sendCommand("ZWave.GetNetworks", this, "getNetworksResponse"); + +} + +void ZWaveManager::getSerialPortsResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZWave()) << "Serial ports response:" << commandId << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + foreach (const QVariant &entryVariant, params.value("serialPorts").toList()) { + SerialPort *serialPort = SerialPort::unpackSerialPort(entryVariant.toMap(), this); + m_serialPorts->addSerialPort(serialPort); + } + qCDebug(dcZWave) << "Added" << m_serialPorts->rowCount() << "ports"; + +} + +void ZWaveManager::getNetworksResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZWave()) << "get networks response:" << commandId << params; + + foreach (const QVariant &entryVariant, params.value("networks").toList()) { + QVariantMap entry = entryVariant.toMap(); + ZWaveNetwork *network = unpackNetwork(entry); + m_networks->addNetwork(network); + + int id = m_engine->jsonRpcClient()->sendCommand("ZWave.GetNodes", {{"networkUuid", network->networkUuid()}}, this, "getNodesResponse"); + m_pendingGetNodeCalls.insert(id, network->networkUuid()); + } + + m_fetchingData = false; + emit fetchingDataChanged(); +} + +void ZWaveManager::addNetworkResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZWave()) << "Add network response" << commandId << params; + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZWaveError error = static_cast(errorEnum.keyToValue(params.value("zwaveError").toByteArray())); + emit addNetworkReply(commandId, error, params.value("networkUuid").toUuid()); +} + +void ZWaveManager::removeNetworkResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZWave()) << "Remove network response" << commandId << params; + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZWaveError error = static_cast(errorEnum.keyToValue(params.value("zwaveError").toByteArray())); + emit removeNetworkReply(commandId, error); +} + +void ZWaveManager::cancelPendingOperationResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZWave()) << "Cancel pending operation response" << commandId << params; + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZWaveError error = static_cast(errorEnum.keyToValue(params.value("zwaveError").toByteArray())); + emit cancelPendingOperationReply(commandId, error); +} + +void ZWaveManager::addNodeResponse(int commandId, const QVariantMap ¶ms) +{ + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZWaveError error = static_cast(errorEnum.keyToValue(params.value("zwaveError").toByteArray())); + emit addNodeReply(commandId, error); +} + +void ZWaveManager::softResetControllerResponse(int commandId, const QVariantMap ¶ms) +{ + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZWaveError error = static_cast(errorEnum.keyToValue(params.value("zwaveError").toByteArray())); + emit softResetControllerReply(commandId, error); +} + +void ZWaveManager::factoryResetNetworkResponse(int commandId, const QVariantMap ¶ms) +{ + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZWaveError error = static_cast(errorEnum.keyToValue(params.value("zwaveError").toByteArray())); + emit factoryResetNetworkReply(commandId, error); +} + +void ZWaveManager::getNodesResponse(int commandId, const QVariantMap ¶ms) +{ + QUuid networkUuid = m_pendingGetNodeCalls.value(commandId); + ZWaveNetwork *network = m_networks->getNetwork(networkUuid); + if (!network) { + qCWarning(dcZWave()) << "Received a getNodes response for a network we don't know!?"; + return; + } + + qCDebug(dcZWave()) << "GetNodes response:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + + foreach (const QVariant &entry, params.value("nodes").toList()) { + QVariantMap nodeMap = entry.toMap(); + network->addNode(unpackNode(nodeMap)); + } +} + +void ZWaveManager::removeNodeResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZWave()) << "Remove noderesponse" << commandId << params; + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZWaveError error = static_cast(errorEnum.keyToValue(params.value("zwaveError").toByteArray())); + emit removeNodeReply(commandId, error); +} + +void ZWaveManager::removeFailedNodeResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcZWave()) << "RemoveFailedNode response:" << commandId << params; + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZWaveError error = static_cast(errorEnum.keyToValue(params.value("zwaveError").toByteArray())); + emit removeFailedNodeReply(commandId, error); +} + +void ZWaveManager::notificationReceived(const QVariantMap &data) +{ + qCDebug(dcZWave) << "Notification received:" << data; + QString notification = data.value("notification").toString(); + + if (notification == "ZWave.NetworkAdded") { + ZWaveNetwork *network = unpackNetwork(data.value("params").toMap().value("network").toMap()); + m_networks->addNetwork(network); + + } else if (notification == "ZWave.NetworkRemoved") { + m_networks->removeNetwork(data.value("params").toMap().value("networkUuid").toUuid()); + + } else if (notification == "ZWave.NetworkChanged") { + QVariantMap networkMap = data.value("params").toMap().value("network").toMap(); + QUuid networkUuid = networkMap.value("networkUuid").toUuid(); + ZWaveNetwork *network = m_networks->getNetwork(networkUuid); + if (!network) { + qCWarning(dcZWave()) << "Received a NetworkChanged notification for a network we don't know."; + return; + } + unpackNetwork(networkMap, network); + + } else if (notification == "ZWave.NodeAdded") { + QVariantMap nodeMap = data.value("params").toMap().value("node").toMap(); + QUuid networkUuid = data.value("params").toMap().value("networkUuid").toUuid(); + ZWaveNetwork *network = m_networks->getNetwork(networkUuid); + if (!network) { + qCWarning(dcZWave()) << "Received a NodeAdded notification for a network we don't know."; + return; + } + network->addNode(unpackNode(nodeMap)); + + } else if (notification == "ZWave.NodeRemoved") { + quint8 nodeId = data.value("params").toMap().value("nodeId").toUInt(); + QUuid networkUuid = data.value("params").toMap().value("networkUuid").toUuid(); + ZWaveNetwork *network = m_networks->getNetwork(networkUuid); + if (!network) { + qCWarning(dcZWave()) << "Received a NodeRemoved notification for a network we don't know."; + return; + } + network->removeNode(nodeId); + + } else if (notification == "ZWave.NodeChanged") { + QVariantMap nodeMap = data.value("params").toMap().value("node").toMap(); + QUuid networkUuid = data.value("params").toMap().value("networkUuid").toUuid(); + ZWaveNetwork *network = m_networks->getNetwork(networkUuid); + if (!network) { + qCWarning(dcZWave()) << "Received a NodeChanged notification for a network we don't know."; + return; + } + ZWaveNode *node = network->nodes()->getNode(nodeMap.value("nodeId").toUInt()); + if (!node) { + qCWarning(dcZWave()) << "Received a NodeChanged notification for a node we don't know"; + return; + } + unpackNode(nodeMap, node); + } +} + +ZWaveNetwork *ZWaveManager::unpackNetwork(const QVariantMap &networkMap, ZWaveNetwork *network) +{ + if (!network) { + network = new ZWaveNetwork(networkMap.value("networkUuid").toUuid(), networkMap.value("serialPort").toString()); + } + + network->setHomeId(networkMap.value("homeId").toUInt()); + QMetaEnum stateEnum = QMetaEnum::fromType(); + network->setIsZWavePlus(networkMap.value("isZWavePlus").toBool()); + network->setIsPrimaryController(networkMap.value("isPrimaryController").toBool()); + network->setIsStaticUpdateController(networkMap.value("isStaticUpdateController").toBool()); + network->setWaitingForNodeAddition(networkMap.value("waitingForNodeAddition").toBool()); + network->setWaitingForNodeRemoval(networkMap.value("waitingForNodeRemoval").toBool()); + network->setNetworkState(static_cast(stateEnum.keyToValue(networkMap.value("networkState").toByteArray()))); + + return network; +} + +ZWaveNode *ZWaveManager::unpackNode(const QVariantMap &nodeMap, ZWaveNode *node) +{ + if (!node) { + node = new ZWaveNode(nodeMap.value("networkUuid").toUuid(), nodeMap.value("nodeId").toUInt()); + } + + node->setInitialized(nodeMap.value("initialized").toBool()); + node->setReachable(nodeMap.value("reachable").toBool()); + node->setFailed(nodeMap.value("failed").toBool()); + node->setSleeping(nodeMap.value("sleeping").toBool()); + + QMetaEnum nodeTypeEnum = QMetaEnum::fromType(); + node->setNodeType(static_cast(nodeTypeEnum.keyToValue(nodeMap.value("nodeType").toByteArray()))); + QMetaEnum deviceTypeEnum = QMetaEnum::fromType(); + node->setDeviceType(static_cast(deviceTypeEnum.keyToValue(nodeMap.value("deviceType").toByteArray()))); + + node->setName(nodeMap.value("name").toString()); + node->setManufacturerId(nodeMap.value("manufacturerId").toUInt()); + node->setManufacturerName(nodeMap.value("manufacturerName").toString()); + node->setProductId(nodeMap.value("productId").toUInt()); + node->setProductName(nodeMap.value("productName").toString()); + node->setProductType(nodeMap.value("productType").toUInt()); + node->setVersion(nodeMap.value("version").toUInt()); + + node->setIsZWavePlus(nodeMap.value("isZWavePlus").toBool()); + + + return node; +} diff --git a/libnymea-app/zwave/zwavemanager.h b/libnymea-app/zwave/zwavemanager.h new file mode 100644 index 00000000..7786c0d3 --- /dev/null +++ b/libnymea-app/zwave/zwavemanager.h @@ -0,0 +1,129 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef ZWAVEMANAGER_H +#define ZWAVEMANAGER_H + +#include +#include + +class Engine; +class JsonRpcClient; +class SerialPorts; +class ZWaveNetwork; +class ZWaveNetworks; +class ZWaveNode; + +class ZWaveManager : public QObject +{ + Q_OBJECT + Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged) + Q_PROPERTY(bool fetchingData READ fetchingData NOTIFY fetchingDataChanged) + + Q_PROPERTY(SerialPorts *serialPorts READ serialPorts CONSTANT) + Q_PROPERTY(ZWaveNetworks *networks READ networks CONSTANT) + +public: + enum ZWaveError { + ZWaveErrorNoError, + ZWaveErrorInUse, + ZWaveErrorNetworkUuidNotFound, + ZWaveErrorNodeIdNotFound, + ZWaveErrorTimeout, + ZWaveErrorBackendError + }; + Q_ENUM(ZWaveError) + + explicit ZWaveManager(QObject *parent = nullptr); + ~ZWaveManager(); + + void setEngine(Engine *engine); + Engine *engine() const; + + bool fetchingData() const; + + SerialPorts *serialPorts() const; + ZWaveNetworks *networks() const; + + Q_INVOKABLE int addNetwork(const QString &serialPort); + Q_INVOKABLE int removeNetwork(const QUuid &networkUuid); + Q_INVOKABLE void addNode(const QUuid &networkUuid); + Q_INVOKABLE void cancelPendingOperation(const QUuid &networkUuid); + Q_INVOKABLE int softResetController(const QUuid &networkUuid); + Q_INVOKABLE int factoryResetNetwork(const QUuid &networkUuid); +// Q_INVOKABLE void getNodes(const QUuid &networkUuid); + Q_INVOKABLE int removeNode(const QUuid &networkUuid); + Q_INVOKABLE int removeFailedNode(const QUuid &networkUuid, int nodeId); + +signals: + void engineChanged(); + void fetchingDataChanged(); + void addNetworkReply(int commandId, ZWaveManager::ZWaveError error, const QUuid &networkUuid); + void removeNetworkReply(int commandId, ZWaveManager::ZWaveError error); + void cancelPendingOperationReply(int commandId, ZWaveManager::ZWaveError error); + void softResetControllerReply(int commandId, ZWaveManager::ZWaveError error); + void factoryResetNetworkReply(int commandId, ZWaveManager::ZWaveError error); + void addNodeReply(int commandId, ZWaveManager::ZWaveError error); + void removeNodeReply(int commandId, ZWaveManager::ZWaveError error); + void removeFailedNodeReply(int commandId, ZWaveManager::ZWaveError error); + +private: + void init(); + + Q_INVOKABLE void getSerialPortsResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void getNetworksResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void getNodesResponse(int commandId, const QVariantMap ¶ms); + + Q_INVOKABLE void addNetworkResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void removeNetworkResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void cancelPendingOperationResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void softResetControllerResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void factoryResetNetworkResponse(int commandId, const QVariantMap ¶ms); + + Q_INVOKABLE void addNodeResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void removeNodeResponse(int commandId, const QVariantMap ¶ms); + Q_INVOKABLE void removeFailedNodeResponse(int commandId, const QVariantMap ¶ms); + + Q_INVOKABLE void notificationReceived(const QVariantMap &data); + +private: + Engine* m_engine = nullptr; + bool m_fetchingData = false; + SerialPorts *m_serialPorts = nullptr; + ZWaveNetworks *m_networks = nullptr; + + QHash m_pendingGetNodeCalls; + + ZWaveNetwork *unpackNetwork(const QVariantMap &networkMap, ZWaveNetwork *network = nullptr); + ZWaveNode *unpackNode(const QVariantMap &nodeMap, ZWaveNode *node = nullptr); + +}; + +#endif // ZWAVEMANAGER_H diff --git a/libnymea-app/zwave/zwavenetwork.cpp b/libnymea-app/zwave/zwavenetwork.cpp new file mode 100644 index 00000000..7c05b936 --- /dev/null +++ b/libnymea-app/zwave/zwavenetwork.cpp @@ -0,0 +1,225 @@ +#include "zwavenetwork.h" +#include "zwavenode.h" + +ZWaveNetwork::ZWaveNetwork(const QUuid &networkUuid, const QString &serialPort, QObject *parent): + QObject(parent), + m_networkUuid(networkUuid), + m_serialPort(serialPort), + m_nodes(new ZWaveNodes(this)) +{ + +} + +QUuid ZWaveNetwork::networkUuid() const +{ + return m_networkUuid; +} + +QString ZWaveNetwork::serialPort() const +{ + return m_serialPort; +} + +quint32 ZWaveNetwork::homeId() const +{ + return m_homeId; +} + +void ZWaveNetwork::setHomeId(quint32 homeId) +{ + if (m_homeId != homeId) { + m_homeId = homeId; + emit homeIdChanged(); + } +} + +bool ZWaveNetwork::isZWavePlus() const +{ + return m_isZWavePlus; +} + +void ZWaveNetwork::setIsZWavePlus(bool isZWavePlus) +{ + if (m_isZWavePlus != isZWavePlus) { + m_isZWavePlus = isZWavePlus; + emit isZWavePlusChanged(); + } +} + +bool ZWaveNetwork::isPrimaryController() const +{ + return m_isPrimaryController; +} + +void ZWaveNetwork::setIsPrimaryController(bool isPrimaryController) +{ + if (m_isPrimaryController != isPrimaryController) { + m_isPrimaryController = isPrimaryController; + emit isPrimaryControllerChanged(); + } +} + +bool ZWaveNetwork::isStaticUpdateController() const +{ + return m_isStaticUpdateController; +} + +void ZWaveNetwork::setIsStaticUpdateController(bool isStaticUpdateController) +{ + if (m_isStaticUpdateController != isStaticUpdateController) { + m_isStaticUpdateController = isStaticUpdateController; + emit isStaticUpdateControllerChanged(); + } +} + +bool ZWaveNetwork::waitingForNodeAddition() const +{ + return m_waitingForNodeAddition; +} + +void ZWaveNetwork::setWaitingForNodeAddition(bool waitingForNodeAddition) +{ + if (m_waitingForNodeAddition != waitingForNodeAddition) { + m_waitingForNodeAddition = waitingForNodeAddition; + emit waitingForNodeAdditionChanged(); + } +} + +bool ZWaveNetwork::waitingForNodeRemoval() const +{ + return m_waitingForNodeRemoval; +} + +void ZWaveNetwork::setWaitingForNodeRemoval(bool waitingForNodeRemoval) +{ + if (m_waitingForNodeRemoval != waitingForNodeRemoval) { + m_waitingForNodeRemoval = waitingForNodeRemoval; + emit waitingForNodeRemovalChanged(); + } +} + +ZWaveNetwork::ZWaveNetworkState ZWaveNetwork::networkState() const +{ + return m_networkState; +} + +void ZWaveNetwork::setNetworkState(ZWaveNetworkState networkState) +{ + if (m_networkState != networkState) { + m_networkState = networkState; + emit networkStateChanged(); + } +} + +ZWaveNodes *ZWaveNetwork::nodes() const +{ + return m_nodes; +} + +void ZWaveNetwork::addNode(ZWaveNode *node) +{ + m_nodes->addNode(node); +} + +void ZWaveNetwork::removeNode(quint8 nodeId) +{ + m_nodes->removeNode(nodeId); +} + +ZWaveNetworks::ZWaveNetworks(QObject *parent): + QAbstractListModel(parent) +{ + +} + +int ZWaveNetworks::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant ZWaveNetworks::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleUuid: + return m_list.at(index.row())->networkUuid(); + case RoleSerialPort: + return m_list.at(index.row())->serialPort(); + case RoleHomeId: + return m_list.at(index.row())->homeId(); + case RoleIsZWavePlus: + return m_list.at(index.row())->isZWavePlus(); + case RoleIsPrimaryController: + return m_list.at(index.row())->isPrimaryController(); + case RoleIsStaticUpdateController: + return m_list.at(index.row())->isStaticUpdateController(); + case RoleNetworkState: + return m_list.at(index.row())->networkState(); + } + return QVariant(); +} + +QHash ZWaveNetworks::roleNames() const +{ + return { + {RoleUuid, "networkUuid"}, + {RoleSerialPort, "serialPort"}, + {RoleHomeId, "homeId"}, + {RoleIsZWavePlus, "isZWavePlus"}, + {RoleIsPrimaryController, "isPrimaryController"}, + {RoleIsStaticUpdateController, "isStaticUpdateController"}, + {RoleNetworkState, "networkState"} + }; +} + +void ZWaveNetworks::clear() +{ + beginResetModel(); + qDeleteAll(m_list); + endResetModel(); +} + +void ZWaveNetworks::addNetwork(ZWaveNetwork *network) +{ + network->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(network); + endInsertRows(); + emit countChanged(); + + connect(network, &ZWaveNetwork::networkStateChanged, this, [this, network](){ + QModelIndex idx = index(m_list.indexOf(network)); + emit dataChanged(idx, idx, {RoleNetworkState}); + }); +} + +void ZWaveNetworks::removeNetwork(const QUuid &networkUuid) +{ + for (int i = 0; i < m_list.count(); i++) { + if (m_list.at(i)->networkUuid() == networkUuid) { + beginRemoveRows(QModelIndex(), i, i); + m_list.takeAt(i)->deleteLater(); + endRemoveRows(); + emit countChanged(); + return; + } + } +} + +ZWaveNetwork *ZWaveNetworks::get(int index) const +{ + if (index < 0 || index >= m_list.count()) { + return nullptr; + } + return m_list.at(index); +} + +ZWaveNetwork *ZWaveNetworks::getNetwork(const QUuid &networkUuid) +{ + foreach (ZWaveNetwork *network, m_list) { + if (network->networkUuid() == networkUuid) { + return network; + } + } + return nullptr; +} diff --git a/libnymea-app/zwave/zwavenetwork.h b/libnymea-app/zwave/zwavenetwork.h new file mode 100644 index 00000000..9af621b3 --- /dev/null +++ b/libnymea-app/zwave/zwavenetwork.h @@ -0,0 +1,124 @@ +#ifndef ZWAVENETWORK_H +#define ZWAVENETWORK_H + +#include +#include +#include + +class ZWaveNode; +class ZWaveNodes; + +class ZWaveNetwork : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid networkUuid READ networkUuid CONSTANT) + Q_PROPERTY(QString serialPort READ serialPort CONSTANT) + Q_PROPERTY(quint32 homeId READ homeId NOTIFY homeIdChanged) + Q_PROPERTY(bool isZWavePlus READ isZWavePlus NOTIFY isZWavePlusChanged) + Q_PROPERTY(bool isPrimaryController READ isPrimaryController NOTIFY isPrimaryControllerChanged) + Q_PROPERTY(bool isStaticUpdateController READ isStaticUpdateController NOTIFY isStaticUpdateControllerChanged) + Q_PROPERTY(bool waitingForNodeAddition READ waitingForNodeAddition NOTIFY waitingForNodeAdditionChanged) + Q_PROPERTY(bool waitingForNodeRemoval READ waitingForNodeRemoval NOTIFY waitingForNodeRemovalChanged) + Q_PROPERTY(ZWaveNetworkState networkState READ networkState NOTIFY networkStateChanged) + Q_PROPERTY(ZWaveNodes* nodes READ nodes CONSTANT) + +public: + enum ZWaveNetworkState { + ZWaveNetworkStateOffline, + ZWaveNetworkStateStarting, + ZWaveNetworkStateOnline, + ZWaveNetworkStateError + }; + Q_ENUM(ZWaveNetworkState) + explicit ZWaveNetwork(const QUuid &networkUuid, const QString &serialPort, QObject *parent = nullptr); + + QUuid networkUuid() const; + QString serialPort() const; + + quint32 homeId() const; + void setHomeId(quint32 homeId); + + bool isZWavePlus() const; + void setIsZWavePlus(bool isZWavePlus); + + bool isPrimaryController() const; + void setIsPrimaryController(bool isPrimaryController); + + bool isStaticUpdateController() const; + void setIsStaticUpdateController(bool isStaticUpdateController); + + bool waitingForNodeAddition() const; + void setWaitingForNodeAddition(bool waitingForNodeAddition); + + bool waitingForNodeRemoval() const; + void setWaitingForNodeRemoval(bool waitingForNodeRemoval); + + ZWaveNetworkState networkState() const; + void setNetworkState(ZWaveNetworkState networkState); + + ZWaveNodes* nodes() const; + + void addNode(ZWaveNode *node); + void removeNode(quint8 nodeId); + +signals: + void networkStateChanged(); + void homeIdChanged(); + void isZWavePlusChanged(); + void isPrimaryControllerChanged(); + void isStaticUpdateControllerChanged(); + void waitingForNodeAdditionChanged(); + void waitingForNodeRemovalChanged(); + +private: + QUuid m_networkUuid; + QString m_serialPort; + quint32 m_homeId = 0; + bool m_isZWavePlus = false; + bool m_isPrimaryController = false; + bool m_isStaticUpdateController = false; + bool m_waitingForNodeAddition = false; + bool m_waitingForNodeRemoval = false; + ZWaveNetworkState m_networkState = ZWaveNetworkStateOffline; + + ZWaveNodes* m_nodes = nullptr; +}; + + +class ZWaveNetworks: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + enum Roles { + RoleUuid, + RoleSerialPort, + RoleHomeId, + RoleIsZWavePlus, + RoleIsPrimaryController, + RoleIsStaticUpdateController, + RoleNetworkState + }; + Q_ENUM(Roles) + + ZWaveNetworks(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + void clear(); + void addNetwork(ZWaveNetwork *network); + void removeNetwork(const QUuid &networkUuid); + + Q_INVOKABLE ZWaveNetwork* get(int index) const; + Q_INVOKABLE ZWaveNetwork* getNetwork(const QUuid &networkUuid); + +signals: + void countChanged(); + +private: + QList m_list; +}; + +#endif // ZWAVENETWORK_H diff --git a/libnymea-app/zwave/zwavenode.cpp b/libnymea-app/zwave/zwavenode.cpp new file mode 100644 index 00000000..fa5fb593 --- /dev/null +++ b/libnymea-app/zwave/zwavenode.cpp @@ -0,0 +1,358 @@ +#include "zwavenode.h" +#include + +ZWaveNode::ZWaveNode(const QUuid &networkUuid, quint8 id, QObject *parent): + QObject{parent}, + m_nodeId(id), + m_networkUuid(networkUuid) +{ + +} + +quint8 ZWaveNode::nodeId() const +{ + return m_nodeId; +} + +QUuid ZWaveNode::networkUuid() const +{ + return m_networkUuid; +} + +ZWaveNode::ZWaveNodeType ZWaveNode::nodeType() const +{ + return m_nodeType; +} + +void ZWaveNode::setNodeType(ZWaveNodeType nodeType) +{ + if (m_nodeType != nodeType) { + m_nodeType = nodeType; + emit nodeTypeChanged(); + } +} + +ZWaveNode::ZWaveDeviceType ZWaveNode::deviceType() const +{ + return m_deviceType; +} + +void ZWaveNode::setDeviceType(ZWaveDeviceType deviceType) +{ + m_deviceType = deviceType; +} + +QString ZWaveNode::deviceTypeString() const +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + return metaEnum.valueToKey(m_deviceType); +} + +quint16 ZWaveNode::manufacturerId() const +{ + return m_manufacturerId; +} + +void ZWaveNode::setManufacturerId(quint16 manufacturerId) +{ + if (m_manufacturerId != manufacturerId) { + m_manufacturerId = manufacturerId; + emit manufacturerIdChanged(); + } +} + +QString ZWaveNode::manufacturerName() const +{ + return m_manufacturerName; +} + +void ZWaveNode::setManufacturerName(const QString &manufacturerName) +{ + if (m_manufacturerName != manufacturerName) { + m_manufacturerName = manufacturerName; + emit manufacturerNameChanged(); + } +} + +QString ZWaveNode::name() const +{ + return m_name; +} + +void ZWaveNode::setName(const QString &name) +{ + if (m_name != name) { + m_name = name; + emit nameChanged(); + } +} + +quint16 ZWaveNode::productId() const +{ + return m_productId; +} + +void ZWaveNode::setProductId(quint16 productId) +{ + if (m_productId != productId) { + m_productId = productId; + emit productIdChanged(); + } +} + +QString ZWaveNode::productName() const +{ + return m_productName; +} + +void ZWaveNode::setProductName(const QString &productName) +{ + if (m_productName != productName) { + m_productName = productName; + emit productNameChanged(); + } +} + +quint16 ZWaveNode::productType() const +{ + return m_productType; +} + +void ZWaveNode::setProductType(quint16 productType) +{ + if (m_productType != productType) { + m_productType = productType; + emit productTypeChanged(); + } +} + +quint8 ZWaveNode::version() const +{ + return m_version; +} + +void ZWaveNode::setVersion(quint8 version) +{ + if (m_version != version) { + m_version = version; + emit versionChanged(); + } +} + +bool ZWaveNode::isZWavePlus() const +{ + return m_isZWavePlus;; +} + +void ZWaveNode::setIsZWavePlus(bool isZWavePlus) +{ + if (m_isZWavePlus != isZWavePlus) { + m_isZWavePlus = isZWavePlus; + emit isZWavePlusChanged(); + } +} + +bool ZWaveNode::reachable() const +{ + return m_reachable; +} + +void ZWaveNode::setReachable(bool reachable) +{ + if (m_reachable != reachable) { + m_reachable = reachable; + emit reachableChanged(); + } +} + +bool ZWaveNode::failed() const +{ + return m_failed; +} + +void ZWaveNode::setFailed(bool failed) +{ + if (m_failed != failed) { + m_failed = failed; + emit failedChanged(); + } +} + +bool ZWaveNode::sleeping() const +{ + return m_sleeping; +} + +void ZWaveNode::setSleeping(bool sleeping) +{ + if (m_sleeping != sleeping) { + m_sleeping = sleeping; + emit sleepingChanged(); + } +} + +bool ZWaveNode::initialized() const +{ + return m_initialized; +} + +void ZWaveNode::setInitialized(bool initialized) +{ + if (m_initialized != initialized) { + m_initialized = initialized; + emit initializedChanged(); + } +} + + +ZWaveNodes::ZWaveNodes(QObject *parent): + QAbstractListModel(parent) +{ + +} + +int ZWaveNodes::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant ZWaveNodes::data(const QModelIndex &index, int role) const +{ + return QVariant(); +} + +QHash ZWaveNodes::roleNames() const +{ + return {{RoleId, "nodeId"}}; +} + +void ZWaveNodes::clear() +{ + beginResetModel(); + qDeleteAll(m_list); + endResetModel(); + emit countChanged(); +} + +void ZWaveNodes::addNode(ZWaveNode *node) +{ + node->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(node); + endInsertRows(); + emit countChanged(); +} + +void ZWaveNodes::removeNode(quint8 nodeId) +{ + for (int i = 0; i < m_list.count(); i++) { + if (m_list.at(i)->nodeId() == nodeId) { + beginRemoveRows(QModelIndex(), i, i); + m_list.takeAt(i)->deleteLater(); + endRemoveRows(); + emit countChanged(); + } + } +} + +ZWaveNode *ZWaveNodes::get(int index) const +{ + if (index < 0 || index >= m_list.count()) { + return nullptr; + } + return m_list.at(index); +} + +ZWaveNode *ZWaveNodes::getNode(quint8 nodeId) +{ + foreach (ZWaveNode *node, m_list) { + if (node->nodeId() == nodeId) { + return node; + } + } + return nullptr; +} + +ZWaveNodesProxy::ZWaveNodesProxy(QObject *parent): + QSortFilterProxyModel(parent) +{ + +} + +ZWaveNodes *ZWaveNodesProxy::zwaveNodes() const +{ + return m_nodes; +} + +void ZWaveNodesProxy::setZWaveNodes(ZWaveNodes *nodes) +{ + if (m_nodes != nodes) { + m_nodes = nodes; + emit zwaveNodesChanged(); + setSourceModel(nodes); + + } +} + +bool ZWaveNodesProxy::showController() const +{ + return m_showController; +} + +void ZWaveNodesProxy::setShowController(bool showController) +{ + if (m_showController != showController) { + m_showController = showController; + emit showControllerChanged(); + invalidateFilter(); + } +} + +bool ZWaveNodesProxy::showOnline() const +{ + return m_showOnline; +} + +void ZWaveNodesProxy::setShowOnline(bool showOnline) +{ + if (m_showOnline != showOnline) { + m_showOnline = showOnline; + emit showOnlineChanged(); + invalidateFilter(); + } +} + +bool ZWaveNodesProxy::showOffline() const +{ + return m_showOffline; +} + +void ZWaveNodesProxy::setShowOffline(bool showOffline) +{ + if (m_showOffline != showOffline) { + m_showOffline = showOffline; + emit showOfflineChanged(); + invalidateFilter(); + } +} + +bool ZWaveNodesProxy::newOnTop() const +{ + return m_newOnTop; +} + +void ZWaveNodesProxy::setNewOnTop(bool newOnTop) +{ + if (m_newOnTop != newOnTop) { + m_newOnTop = newOnTop; + emit newOnTopChanged(); + + // TODO: sorting! + } +} + +ZWaveNode *ZWaveNodesProxy::get(int index) const +{ + return m_nodes->get(mapToSource(this->index(index, 0)).row()); +} diff --git a/libnymea-app/zwave/zwavenode.h b/libnymea-app/zwave/zwavenode.h new file mode 100644 index 00000000..595255ae --- /dev/null +++ b/libnymea-app/zwave/zwavenode.h @@ -0,0 +1,286 @@ +#ifndef ZWAVENODE_H +#define ZWAVENODE_H + +#include +#include +#include +#include + +class ZWaveNode : public QObject +{ + Q_OBJECT + Q_PROPERTY(quint8 nodeId READ nodeId CONSTANT) + Q_PROPERTY(QUuid networkUuid READ networkUuid CONSTANT) + Q_PROPERTY(bool initialized READ initialized NOTIFY initializedChanged) + Q_PROPERTY(bool reachable READ reachable NOTIFY reachableChanged) + Q_PROPERTY(bool failed READ failed NOTIFY failedChanged) + Q_PROPERTY(bool sleeping READ sleeping NOTIFY sleepingChanged) + Q_PROPERTY(ZWaveNodeType nodeType READ nodeType NOTIFY nodeTypeChanged) + Q_PROPERTY(ZWaveDeviceType deviceType READ deviceType NOTIFY deviceTypeChanged) + Q_PROPERTY(QString deviceTypeString READ deviceTypeString NOTIFY deviceTypeChanged) + Q_PROPERTY(quint16 manufacturerId READ manufacturerId NOTIFY manufacturerIdChanged) + Q_PROPERTY(QString manufacturerName READ manufacturerName NOTIFY manufacturerNameChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(quint16 productId READ productId NOTIFY productIdChanged) + Q_PROPERTY(QString productName READ productName NOTIFY productNameChanged) + Q_PROPERTY(quint16 productType READ productType NOTIFY productTypeChanged) + Q_PROPERTY(quint8 version READ version NOTIFY versionChanged) + Q_PROPERTY(bool isZWavePlus READ isZWavePlus NOTIFY isZWavePlusChanged) + +public: + enum ZWaveNodeType { + ZWaveNodeTypeUnknown = 0x00, + ZWaveNodeTypeController = 0x01, + ZWaveNodeTypeStaticController = 0x02, + ZWaveNodeTypeSlave = 0x03, + ZWaveNodeTypeRoutingSlave = 0x04, + }; + Q_ENUM(ZWaveNodeType) + + enum ZWaveDeviceType { + ZWaveDeviceTypeUnknown = 0x0000, + ZWaveDeviceTypeCentralController = 0x0100, + ZWaveDeviceTypeDisplaySimple = 0x0200, + ZWaveDeviceTypeDoorLockKeypad = 0x0300, + ZWaveDeviceTypeFanSwitch = 0x0400, + ZWaveDeviceTypeGateway = 0x0500, + ZWaveDeviceTypeLightDimmerSwitch = 0x0600, + ZWaveDeviceTypeOnOffPowerSwitch = 0x0700, + ZWaveDeviceTypePowerStrip = 0x0800, + ZWaveDeviceTypeRemoteControlAV = 0x0900, + ZWaveDeviceTypeRemoteControlMultiPurpose = 0x0a00, + ZWaveDeviceTypeRemoteControlSimple = 0x0b00, + ZWaveDeviceTypeKeyFob = 0x0b01, + ZWaveDeviceTypeSensorNotification = 0x0c00, + ZWaveDeviceTypeSmokeAlarmSensor = 0x0c01, + ZWaveDeviceTypeCOAlarmSensor = 0x0c02, + ZWaveDeviceTypeCO2AlarmSensor = 0x0c03, + ZWaveDeviceTypeHeatAlarmSensor = 0x0c04, + ZWaveDeviceTypeWaterAlarmSensor = 0x0c05, + ZWaveDeviceTypeAccessControlSensor = 0x0c06, + ZWaveDeviceTypeHomeSecuritySensor = 0x0c07, + ZWaveDeviceTypePowerManagementSensor = 0x0c08, + ZWaveDeviceTypeSystemSensor = 0x0c09, + ZWaveDeviceTypeEmergencyAlarmSensor = 0x0c0a, + ZWaveDeviceTypeClockSensor = 0x0c0b, + ZWaveDeviceTypeMultiDeviceAlarmSensor = 0x0cff, + ZWaveDeviceTypeMultilevelSensor = 0x0d00, + ZWaveDeviceTypeAirTemperatureSensor = 0x0d01, + ZWaveDeviceTypeGeneralPurposeSensor = 0x0d02, + ZWaveDeviceTypeLuminanceSensor = 0x0d03, + ZWaveDeviceTypePowerSensor = 0x0d04, + ZWaveDeviceTypeHumiditySensor = 0x0d05, + ZWaveDeviceTypeVelocitySensor = 0x0d06, + ZWaveDeviceTypeDirectionSensor = 0x0d07, + ZWaveDeviceTypeAtmosphericPressureSensor = 0x0d08, + ZWaveDeviceTypeBarometricPressureSensor = 0x0d09, + ZWaveDeviceTypeSolarRadiationSensor = 0x0d0a, + ZWaveDeviceTypeDewPointSensor = 0x0d0b, + ZWaveDeviceTypeRainRateSensor = 0x0d0c, + ZWaveDeviceTypeTideLevelSensor = 0x0d0d, + ZWaveDeviceTypeWeightSensor = 0x0d0e, + ZWaveDeviceTypeVoltageSensor = 0x0d0f, + ZWaveDeviceTypeCurrentSensor = 0x0d10, + ZWaveDeviceTypeCO2LevelSensor = 0x0d11, + ZWaveDeviceTypeAirFlowSensor = 0x0d12, + ZWaveDeviceTypeTankCapacitySensor = 0x0d13, + ZWaveDeviceTypeDistanceSensor = 0x0d14, + ZWaveDeviceTypeAnglePositionSensor = 0x0d15, + ZWaveDeviceTypeRotationSensor = 0x0d16, + ZWaveDeviceTypeWaterTemperatureSensor = 0x0d17, + ZWaveDeviceTypeSoilTemperatureSensor = 0x0d18, + ZWaveDeviceTypeSeismicIntensitySensor = 0x0d19, + ZWaveDeviceTypeSeismicMagnitudeSensor = 0x0d1a, + ZWaveDeviceTypeUltraVioletSensor = 0x0d1b, + ZWaveDeviceTypeElectricalResistivitySensor = 0x0d1c, + ZWaveDeviceTypeElectricalConductivitySensor = 0x0d1d, + ZWaveDeviceTypeLoudnessSensor = 0x0d1e, + ZWaveDeviceTypeMoistureSensor = 0x0d1f, + ZWaveDeviceTypeFrequencySensor = 0x0d20, + ZWaveDeviceTypeTimeSensor = 0x0d21, + ZWaveDeviceTypeTargetTemperatureSensor = 0x0d22, + ZWaveDeviceTypeMultiDeviceSensor = 0x0dff, + ZWaveDeviceTypeSetTopBox = 0x0e00, + ZWaveDeviceTypeSiren = 0x0f00, + ZWaveDeviceTypeSubEnergyMeter = 0x1000, + ZWaveDeviceTypeSubSystemController = 0x1100, + ZWaveDeviceTypeThermostatHVAC = 0x1200, + ZWaveDeviceTypeThermostatSetback = 0x1300, + ZWaveDeviceTypeTV = 0x1400, + ZWaveDeviceTypeValveOpenClose = 0x1500, + ZWaveDeviceTypeWallController = 0x1600, + ZWaveDeviceTypeWholeHomeMeterSimple = 0x1700, + ZWaveDeviceTypeWindowCoveringNoPosEndpoint = 0x1800, + ZWaveDeviceTypeWindowCoveringEndpointAware = 0x1900, + ZWaveDeviceTypeWindowCoveringPositionEndpointAware = 0x1a00, + }; + Q_ENUM(ZWaveDeviceType) + + explicit ZWaveNode(const QUuid &networkUuid, quint8 id, QObject *parent = nullptr); + + QUuid networkUuid() const; + quint8 nodeId() const; + + bool initialized() const; + void setInitialized(bool initialized); + + bool reachable() const; + void setReachable(bool reachable); + + bool failed() const; + void setFailed(bool failed); + + bool sleeping() const; + void setSleeping(bool sleeping); + + ZWaveNodeType nodeType() const; + void setNodeType(ZWaveNodeType nodeType); + + ZWaveDeviceType deviceType() const; + void setDeviceType(ZWaveDeviceType deviceType); + + QString deviceTypeString() const; + +// PlusDeviceType plusDeviceType() const; + + quint16 manufacturerId() const; + void setManufacturerId(quint16 manufacturerId); + + QString manufacturerName() const; + void setManufacturerName(const QString &manufacturerName); + + QString name() const; + void setName(const QString &name); + + quint16 productId() const; + void setProductId(quint16 productId); + + QString productName() const; + void setProductName(const QString &productName); + + quint16 productType() const; + void setProductType(quint16 productType); + + quint8 version() const; + void setVersion(quint8 version); + + bool isZWavePlus() const; + void setIsZWavePlus(bool isZWavePlus); + +signals: + void initializedChanged(); + void reachableChanged(); + void failedChanged(); + void sleepingChanged(); + void nodeTypeChanged(); + void deviceTypeChanged(); + void plusDeviceTypeChanged(); + void manufacturerIdChanged(); + + void manufacturerNameChanged(); + void nameChanged(); + void productIdChanged(); + void productNameChanged(); + void productTypeChanged(); + void versionChanged(); + void isZWavePlusChanged(); + +private: + quint8 m_nodeId = 0; + QUuid m_networkUuid; + + bool m_initialized = false; + bool m_reachable = false; + bool m_failed = false; + bool m_sleeping = false; + + ZWaveNodeType m_nodeType = ZWaveNodeTypeUnknown; + ZWaveDeviceType m_deviceType = ZWaveDeviceTypeUnknown; + quint16 m_manufacturerId = 0; + QString m_manufacturerName; + QString m_name; + quint16 m_productId = 0; + QString m_productName; + quint16 m_productType = 0; + quint8 m_version = 0; + bool m_isZWavePlus = false; +}; + +class ZWaveNodes: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + enum Roles { + RoleId + }; + Q_ENUM(Roles) + + ZWaveNodes(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + void clear(); + void addNode(ZWaveNode *node); + void removeNode(quint8 nodeId); + + Q_INVOKABLE ZWaveNode *get(int index) const; + Q_INVOKABLE ZWaveNode *getNode(quint8 nodeId); + +signals: + void countChanged(); + +private: + QList m_list; +}; + +class ZWaveNodesProxy: public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(ZWaveNodes* zwaveNodes READ zwaveNodes WRITE setZWaveNodes NOTIFY zwaveNodesChanged) + Q_PROPERTY(bool showController READ showController WRITE setShowController NOTIFY showControllerChanged) + Q_PROPERTY(bool showOnline READ showOnline WRITE setShowOnline NOTIFY showOnlineChanged) + Q_PROPERTY(bool showOffline READ showOffline WRITE setShowOffline NOTIFY showOfflineChanged) + + Q_PROPERTY(bool newOnTop READ newOnTop WRITE setNewOnTop NOTIFY newOnTopChanged) + +public: + ZWaveNodesProxy(QObject *parent = nullptr); + + ZWaveNodes* zwaveNodes() const; + void setZWaveNodes(ZWaveNodes* nodes); + + bool showController() const; + void setShowController(bool showController); + + bool showOnline() const; + void setShowOnline(bool showOnline); + + bool showOffline() const; + void setShowOffline(bool showOffline); + + bool newOnTop() const; + void setNewOnTop(bool newOnTop); + + Q_INVOKABLE ZWaveNode *get(int index) const; + +signals: + void countChanged(); + void zwaveNodesChanged(); + void showControllerChanged(); + void showOnlineChanged(); + void showOfflineChanged(); + void newOnTopChanged(); + +private: + ZWaveNodes* m_nodes; + bool m_showController = true; + bool m_showOnline = true; + bool m_showOffline = true; + bool m_newOnTop = false; +}; + +#endif // ZWAVENODE_H diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 56d4b97f..36397a87 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -274,5 +274,8 @@ ui/images/zigbee/TI.svg ui/images/car.svg ui/images/sensors/fire.svg + ui/images/z-wave.svg + ui/images/zwave/z-wave-plus-wide.svg + ui/images/zwave/z-wave-wide.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 6bfee513..6a04eac4 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -276,5 +276,9 @@ ui/delegates/StateDelegate.qml ui/system/PackageDetailsPage.qml ui/system/AdvancedConnectionInterfacesPage.qml + ui/system/zwave/ZWaveSettingsPage.qml + ui/system/zwave/ZWaveAddNetworkPage.qml + ui/system/zwave/ZWaveNetworkPage.qml + ui/system/zwave/ZWaveNetworkSettingsPage.qml diff --git a/nymea-app/ui/SettingsPage.qml b/nymea-app/ui/SettingsPage.qml index f0da310f..9d3d600f 100644 --- a/nymea-app/ui/SettingsPage.qml +++ b/nymea-app/ui/SettingsPage.qml @@ -121,6 +121,15 @@ Page { onClicked: pageStack.push(Qt.resolvedUrl("system/ZigbeeSettingsPage.qml")) } + SettingsTile { + Layout.fillWidth: true + iconSource: "../images/z-wave.svg" + text: qsTr("Z-Wave") + subText: qsTr("Configure Z-Wave networks") + visible: engine.jsonRpcClient.ensureServerVersion("6.1") && NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.zigbeeSettingsEnabled + onClicked: pageStack.push(Qt.resolvedUrl("system/zwave/ZWaveSettingsPage.qml")) + } + SettingsTile { Layout.fillWidth: true iconSource: "../images/modbus.svg" diff --git a/nymea-app/ui/components/ColorIcon.qml b/nymea-app/ui/components/ColorIcon.qml index 9262b4b8..fcc1dad8 100644 --- a/nymea-app/ui/components/ColorIcon.qml +++ b/nymea-app/ui/components/ColorIcon.qml @@ -37,6 +37,8 @@ Item { id: icon width: size height: size + implicitHeight: image.implicitHeight + implicitWidth: image.implicitWidth property alias name: icon.source property string source diff --git a/nymea-app/ui/images/z-wave.svg b/nymea-app/ui/images/z-wave.svg new file mode 100644 index 00000000..75ee2d71 --- /dev/null +++ b/nymea-app/ui/images/z-wave.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/nymea-app/ui/images/zwave/z-wave-plus-wide.svg b/nymea-app/ui/images/zwave/z-wave-plus-wide.svg new file mode 100644 index 00000000..47b925fb --- /dev/null +++ b/nymea-app/ui/images/zwave/z-wave-plus-wide.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/zwave/z-wave-wide.svg b/nymea-app/ui/images/zwave/z-wave-wide.svg new file mode 100644 index 00000000..f2e4aa0f --- /dev/null +++ b/nymea-app/ui/images/zwave/z-wave-wide.svg @@ -0,0 +1,49 @@ + + + + + + + + diff --git a/nymea-app/ui/system/ZigbeeNetworkPage.qml b/nymea-app/ui/system/ZigbeeNetworkPage.qml index 64763a05..5ea24ec6 100644 --- a/nymea-app/ui/system/ZigbeeNetworkPage.qml +++ b/nymea-app/ui/system/ZigbeeNetworkPage.qml @@ -312,7 +312,7 @@ SettingsPageBase { } - tertiaryIconColor: node.reachable ? Style.iconColor : "red" + tertiaryIconColor: node.reachable ? Style.iconColor : Style.red Connections { target: node diff --git a/nymea-app/ui/system/zwave/ZWaveAddNetworkPage.qml b/nymea-app/ui/system/zwave/ZWaveAddNetworkPage.qml new file mode 100644 index 00000000..7f6d4fcf --- /dev/null +++ b/nymea-app/ui/system/zwave/ZWaveAddNetworkPage.qml @@ -0,0 +1,105 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Nymea 1.0 + +import "../../components" + +SettingsPageBase { + id: root + title: qsTr("Add a new Z-Wave network") + busy: d.pendingCallId != -1 + + property ZWaveManager zwaveManager: null + + signal done(); + + QtObject { + id: d + property int pendingCallId: -1 + } + + Connections { + target: root.zwaveManager + onAddNetworkReply: { + if (commandId == d.pendingCallId) { + d.pendingCallId = -1 + var props = {}; + switch (error) { + case ZWaveManager.ZWaveErrorNoError: + root.done(); + break; + case ZWaveManager.ZWaveErrorInUse: + props.text = qsTr("The selected adapter is already in use."); + break; + case ZWaveManager.ZWaveErrorBackendError: + props.text = qsTr("An error happened in the ZWave backend."); + break; + default: + props.errorCode = error; + } + var comp = Qt.createComponent("../components/ErrorDialog.qml") + var popup = comp.createObject(app, props) + popup.open(); + } + } + } + + SettingsPageSectionHeader { + text: qsTr("Available serial ports") + visible: root.zwaveManager.serialPorts.count > 0 + } + + Label { + Layout.fillWidth: true; Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins + wrapMode: Text.WordWrap + font.pixelSize: app.smallFont + text: qsTr("Please verify that the Z-Wave adapter is properly connected to a serial port and select the appropriate port.") + visible: root.zwaveManager.serialPorts.count > 0 + } + + Repeater { + id: unrecognizedRepeater + model: root.zwaveManager.serialPorts + + delegate: NymeaSwipeDelegate { + Layout.fillWidth: true +// property ZigbeeAdapter adapter: root.zwaveManager.serialPorts.get(index) + iconName: "../images/stock_usb.svg" + text: model.description + " - " + model.serialPort + onClicked: { + d.pendingCallId = root.zwaveManager.addNetwork(model.systemLocation) + } + } + } +} diff --git a/nymea-app/ui/system/zwave/ZWaveNetworkPage.qml b/nymea-app/ui/system/zwave/ZWaveNetworkPage.qml new file mode 100644 index 00000000..f15b6354 --- /dev/null +++ b/nymea-app/ui/system/zwave/ZWaveNetworkPage.qml @@ -0,0 +1,587 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "qrc:/ui/components" +import Nymea 1.0 + +SettingsPageBase { + id: root + + property ZWaveManager zwaveManager: null + property ZWaveNetwork network: null + + header: NymeaHeader { + text: qsTr("Z-Wave network") + backButtonVisible: true + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "/ui/images/help.svg" + text: qsTr("Help") + onClicked: { + var popup = zigbeeHelpDialog.createObject(app) + popup.open() + } + } + + HeaderButton { + imageSource: "/ui/images/configure.svg" + text: qsTr("Network settings") + onClicked: { + var page = pageStack.push(Qt.resolvedUrl("ZWaveNetworkSettingsPage.qml"), { zwaveManager: zwaveManager, network: network }) + page.exit.connect(function() { + pageStack.pop(root, StackView.Immediate) + pageStack.pop() + }) + } + } + } + + busy: d.pendingCommandId != -1 + + QtObject { + id: d + property var addRemoveNodeDialog: null + property int pendingCommandId: -1 + function removeFailedNode(networkUuid, nodeId) { + d.pendingCommandId = root.zwaveManager.removeFailedNode(networkUuid, nodeId) + } + } + + Connections { + target: root.zwaveManager + onAddNodeReply: { + if (commandId == d.pendingCommandId) { + d.pendingCommandId = -1 + processStatusCode(error) + } + } + onRemoveNodeReply: { + if (commandId == d.pendingCommandId) { + d.pendingCommandId = -1 + processStatusCode(error) + } + } + onRemoveFailedNodeReply: { + if (commandId == d.pendingCommandId) { + d.pendingCommandId = -1 + processStatusCode(error) + } + } + function processStatusCode(error) { + var props = {}; + switch (error) { + case ZWaveManager.ZWaveErrorNoError: + return; + case ZWaveManager.ZWaveErrorBackendError: + props.text = qsTr("Un unexpected error happened in the Z-Wave backend."); + break; + case ZWaveManager.ZWaveErrorInUse: + props.text = qsTr("The operation could not be started because the Z-Wave network is busy. Please try again later."); + break; + default: + props.text = qsTr("An unexpected error happened. Status code: %1").arg(error); + props.errorCode = error; + } + var comp = Qt.createComponent("/ui/components/ErrorDialog.qml") + var popup = comp.createObject(app, props) + popup.open(); + } + } + + Connections { + target: root.network + onWaitingForNodeAdditionChanged: { + if (root.network.waitingForNodeAddition) { + var props = { + title: qsTr("Include Z-Wave device"), + text: qsTr("The Z-Wave network is now accepting new devices for inclusion. Please start the pairing procedure from the Z-Wave device you want to add to the network. Check the device manual for further details.") + } + d.addRemoveNodeDialog = addRemoveNodeDialogComponent.createObject(app, props) + d.addRemoveNodeDialog.open(); + } else { + if (d.addRemoveNodeDialog) { + d.addRemoveNodeDialog.close() + } + } + } + onWaitingForNodeRemovalChanged: { + if (root.network.waitingForNodeRemoval) { + var props = { + title: qsTr("Exclude Z-Wave device"), + text: qsTr("The Z-Wave network is now accepting devices for exclusion. Please start the pairing procedure from the Z-Wave device you want to remove from the network. Check the device manual for further details.") + } + d.addRemoveNodeDialog = addRemoveNodeDialogComponent.createObject(app, props) + d.addRemoveNodeDialog.open(); + } else { + if (d.addRemoveNodeDialog) { + d.addRemoveNodeDialog.close() + } + } + } + } + + SettingsPageSectionHeader { + text: qsTr("Network") + } + + ColumnLayout { + spacing: Style.margins + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.fillWidth: true + text: qsTr("Network state:") + } + + Label { + text: { + switch (network.networkState) { + case ZWaveNetwork.ZWaveNetworkStateOnline: + return qsTr("Online") + case ZWaveNetwork.ZWaveNetworkStateOffline: + return qsTr("Offline") + case ZWaveNetwork.ZWaveNetworkStateStarting: + return qsTr("Starting") + case ZWaveNetwork.ZWaveNetworkStateError: + return qsTr("Error") + } + } + } + + Led { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + state: { + switch (network.networkState) { + case ZWaveNetwork.ZWaveNetworkStateOnline: + return "on" + case ZWaveNetwork.ZWaveNetworkStateOffline: + return "off" + case ZWaveNetwork.ZWaveNetworkStateStarting: + return "orange" + case ZWaveNetwork.ZWaveNetworkStateError: + return "red" + } + } + } + } + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.fillWidth: true + text: qsTr("Controller type") + } + + Label { + text: network.isPrimaryController ? qsTr("Primary") : qsTr("Secondary") + (network.isStaticUpdateController ? ", " + qsTr("Static") : "") + } + } + } + + SettingsPageSectionHeader { + text: qsTr("Device management") + visible: network.isPrimaryController + } + + ColumnLayout { + spacing: Style.margins + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + visible: network.isPrimaryController + + Button { + Layout.fillWidth: true + text: qsTr("Add a new device") + enabled: network.networkState === ZWaveNetwork.ZWaveNetworkStateOnline + onClicked: { + zwaveManager.cancelPendingOperation(network.networkUuid) + d.pendingCommandId = zwaveManager.addNode(network.networkUuid) + } + } + Button { + Layout.fillWidth: true + text: qsTr("Remove a device") + enabled: network.networkState === ZWaveNetwork.ZWaveNetworkStateOnline + onClicked: { + zwaveManager.cancelPendingOperation(network.networkUuid) + d.pendingCommandId = zwaveManager.removeNode(network.networkUuid) + } + } + } + + SettingsPageSectionHeader { + text: qsTr("Connected devices") + } + + Label { + Layout.fillWidth: true + Layout.margins: Style.margins + horizontalAlignment: Text.AlignHCenter + text: qsTr("There are no ZigBee devices connected yet. Open the network for new devices to join and start the pairing procedure from the ZigBee device. Please refer to the devices manual for more information on how to start the pairing.") + wrapMode: Text.WordWrap + visible: nodesModel.count === 0 + } + + Repeater { + model: ZWaveNodesProxy { + id: nodesModel + zwaveNodes: root.network.nodes + showController: false + newOnTop: true + } + delegate: NymeaSwipeDelegate { + id: nodeDelegate + readonly property ZWaveNode node: nodesModel.get(index) + + ThingsProxy { + id: nodeThings + engine: _engine + paramsFilter: {"networkUuid": nodeDelegate.node.networkUuid, "nodeId": nodeDelegate.node.nodeId} + } + readonly property Thing nodeThing: nodeThings.count >= 1 ? nodeThings.get(0) : null + property int signalStrength: node ? Math.round(node.lqi * 100.0 / 255.0) : 0 + + Layout.fillWidth: true + text: node.productName + " - " + node.manufacturerName// nodeThing ? nodeThing.name : node.model + subText: node.state == ZigbeeNode.ZigbeeNodeStateInitializing ? + qsTr("Initializing...") + : nodeThings.count == 1 ? nodeThing.name : + nodeThings.count > 1 ? qsTr("%1 things").arg(nodeThings.count) : qsTr("Unrecognized device") + iconName: nodeThing ? app.interfacesToIcon(nodeThing.thingClass.interfaces) : "/ui/images/z-wave.svg" + iconColor: busy ? Style.tileOverlayColor + : node.failed ? Style.red + : nodeThing != null ? Style.accentColor + : Style.iconColor + progressive: false + + busy: !node.initialized + + canDelete: node.failed + onDeleteClicked: { + var dialog = removeZWaveNodeDialogComponent.createObject(app, {zwaveNode: node}) + dialog.open() + } + + secondaryIconName: node && !node.sleeping ? "/ui/images/system-suspend.svg" : "" + + tertiaryIconName: { + if (!node || !node.reachable) + return "/ui/images/connections/nm-signal-00.svg" + + if (signalStrength <= 25) + return "/ui/images/connections/nm-signal-25.svg" + + if (signalStrength <= 50) + return "/ui/images/connections/nm-signal-50.svg" + + if (signalStrength <= 75) + return "/ui/images/connections/nm-signal-75.svg" + + if (signalStrength <= 100) + return "/ui/images/connections/nm-signal-100.svg" + } + + + tertiaryIconColor: node.reachable ? Style.iconColor : Style.red + + Connections { + target: node + onLastSeenChanged: communicationIndicatorLedTimer.start() + } + + Timer { + id: communicationIndicatorLedTimer + interval: 200 + } + additionalItem: ColorIcon { + size: Style.smallIconSize + anchors.verticalCenter: parent.verticalCenter + name: { + print("node type:", node.nodeType) + switch (node.nodeType) { + case ZWaveNode.ZWaveNodeTypeController: + case ZWaveNode.ZWaveNodeTypeStaticController: + return "/ui/images/z-wave.svg" + case ZWaveNode.ZWaveNodeTypeRoutingSlave: + return "/ui/images/zigbee-router.svg" + case ZWaveNode.ZWaveNodeTypeSlave: + return "/ui/images/zigbee-enddevice.svg" + } + } + color: communicationIndicatorLedTimer.running ? Style.accentColor : Style.iconColor + } + + onClicked: { + var popup = nodeInfoComponent.createObject(app, {node: node, nodeThings: nodeThings}) + popup.open() + } + } + } + + Component { + id: nodeInfoComponent + MeaDialog { + id: nodeInfoDialog + property ZWaveNode node: null + property ThingsProxy nodeThings: null + readonly property Thing nodeThing: nodeThings.count > 0 ? nodeThings.get(0) : null + header: Item { + implicitHeight: headerRow.height + implicitWidth: parent.width + RowLayout { + id: headerRow + anchors { left: parent.left; right: parent.right; top: parent.top; margins: Style.margins } + ColorIcon { + id: headerColorIcon + Layout.preferredHeight: Style.bigIconSize + Layout.preferredWidth: Style.bigIconSize * 1.5 + color: Style.accentColor + name: nodeInfoDialog.node.isZWavePlus ? "/ui/images/zwave/z-wave-plus-wide.svg" : "/ui/images/zwave/z-wave-wide.svg" + } + ColumnLayout { + Layout.margins: Style.margins + Label { + Layout.fillWidth: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: nodeInfoDialog.node.productName + } + Label { + Layout.fillWidth: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: nodeInfoDialog.node.manufacturerName + } + } + } + } + + standardButtons: Dialog.NoButton + + GridLayout { + columns: 2 + Label { + text: qsTr("Node ID:") + font: Style.smallFont + } + Label { + Layout.fillWidth: true + text: nodeInfoDialog.node.nodeId + font: Style.smallFont + horizontalAlignment: Text.AlignRight + } + Label { + text: qsTr("Device type:") + font: Style.smallFont + } + Label { + Layout.fillWidth: true + text: nodeInfoDialog.node.deviceTypeString.replace(/ZWaveDeviceType/, "") + font: Style.smallFont + horizontalAlignment: Text.AlignRight + } + Label { + text: qsTr("Z-Wave plus:") + font: Style.smallFont + } + Label { + Layout.fillWidth: true + text: nodeInfoDialog.node.isZWavePlus ? qsTr("Yes") : qsTr("No") + font: Style.smallFont + horizontalAlignment: Text.AlignRight + } + Label { + text: qsTr("Signal strength:") + font: Style.smallFont + } + Label { + Layout.fillWidth: true + text: (nodeInfoDialog.node.lqi * 100 / 255).toFixed(0) + " %" + font: Style.smallFont + horizontalAlignment: Text.AlignRight + } + Label { + text: qsTr("Version:") + font: Style.smallFont + } + Label { + Layout.fillWidth: true + text: nodeInfoDialog.node.version + font: Style.smallFont + horizontalAlignment: Text.AlignRight + } + } + + SettingsPageSectionHeader { + text: qsTr("Associated things") + Layout.leftMargin: 0 + Layout.rightMargin: 0 + } + + Repeater { + model: nodeInfoDialog.nodeThings + delegate: RowLayout { + id: thingDelegate + property Thing thing: nodeInfoDialog.nodeThings.get(index) + Layout.fillWidth: true + ColorIcon { + size: Style.iconSize + source: app.interfacesToIcon(thing.thingClass.interfaces) + color: Style.accentColor + } + TextField { + text: thingDelegate.thing.name + Layout.fillWidth: true + onEditingFinished: engine.thingManager.editThing(thingDelegate.thing.id, text) + } + } + } + + RowLayout { + Layout.fillWidth: true + + Button { + // size: Style.iconSize + visible: node && node.type !== ZigbeeNode.ZigbeeNodeTypeCoordinator + // imageSource: "/ui/images/delete.svg" + text: qsTr("Remove") + Layout.alignment: Qt.AlignLeft + onClicked: { + var dialog = removeZigbeeNodeDialogComponent.createObject(app, {zigbeeNode: node}) + dialog.open() + nodeInfoDialog.close() + } + } + Item { + Layout.fillWidth: true + } + + Button { + text: qsTr("OK") + onClicked: nodeInfoDialog.close() + Layout.alignment: Qt.AlignRight + } + } + } + } + + Component { + id: removeZWaveNodeDialogComponent + + MeaDialog { + id: removeZWaveNodeDialog + + property ZWaveNode zwaveNode + + headerIcon: "/ui/images/zigbee.svg" + title: qsTr("Remove Z-Wave node") + " " + (zwaveNode ? zwaveNode.name != "" ? zwaveNode.name : zwaveNode.productName : "") + text: qsTr("Are you sure you want to remove this node from the network?") + standardButtons: Dialog.Ok | Dialog.Cancel + + Label { + text: qsTr("Please note that if this node has been assigned to a thing, it will also be removed from the system.") + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + onAccepted: { + zwaveManager.cancelPendingOperation(network.networkUuid) + d.removeFailedNode(zwaveNode.networkUuid, zwaveNode.nodeId) + } + } + } + + Component { + id: zigbeeHelpDialog + + MeaDialog { + id: dialog + title: qsTr("ZigBee network help") + + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/zigbee-router.svg" + } + + Label { + text: qsTr("ZigBee router") + } + } + + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/zigbee-enddevice.svg" + } + + Label { + text: qsTr("ZigBee end device") + } + } + + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/system-suspend.svg" + } + + Label { + text: qsTr("Sleepy device") + } + } + } + } + + Component { + id: addRemoveNodeDialogComponent + MeaDialog { + standardButtons: Dialog.Cancel + onRejected: { + zwaveManager.cancelPendingOperation(network.networkUuid) + } + } + } +} diff --git a/nymea-app/ui/system/zwave/ZWaveNetworkSettingsPage.qml b/nymea-app/ui/system/zwave/ZWaveNetworkSettingsPage.qml new file mode 100644 index 00000000..7d19a196 --- /dev/null +++ b/nymea-app/ui/system/zwave/ZWaveNetworkSettingsPage.qml @@ -0,0 +1,183 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "qrc:/ui/components" +import Nymea 1.0 + +SettingsPageBase { + id: root + + property ZWaveManager zwaveManager: null + property ZWaveNetwork network: null + + signal exit() + + busy: d.pendingCommandId != -1 + + header: NymeaHeader { + text: qsTr("Z-Wave network settings") + backButtonVisible: true + onBackPressed: pageStack.pop() + + } + + QtObject { + id: d + property int pendingCommandId: -1 + } + + Connections { + target: root.zwaveManager + onRemoveNetworkReply: { + if (commandId === d.pendingCommandId) { + d.pendingCommandId = -1; + if (!processError(error)) { + root.exit(); + } + } + } + + onFactoryResetNetworkReply: { + if (commandId === d.pendingCommandId) { + d.pendingCommandId = -1; + if (!processError(error)) { + root.exit(); + } + } + } + onSoftResetControllerReply: { + if (commandId === d.pendingCommandId) { + d.pendingCommandId = -1; + processError(error) + } + } + + function processError(error) { + var props = {}; + switch (error) { + case ZWaveManager.ZWaveErrorNoError: + return false; + case ZWaveManager.ZWaveErrorBackendError: + props.text = qsTr("An error happened in the ZWave backend."); + break; + default: + props.errorCode = error; + } + var comp = Qt.createComponent("../components/ErrorDialog.qml") + var popup = comp.createObject(app, props) + popup.open(); + return true + } + } + + SettingsPageSectionHeader { + text: qsTr("Network information") + } + + NymeaItemDelegate { + Layout.fillWidth: true + text: qsTr("Network state") + subText: { + switch (root.network.networkState) { + case ZWaveNetwork.ZWaveNetworkStateOnline: + return qsTr("The network is online") + case ZWaveNetwork.ZWaveNetworkStateOffline: + return qsTr("The network is offline") + case ZWaveNetwork.ZWaveNetworkStateStarting: + return qsTr("The network is starting...") + case ZWaveNetwork.ZWaveNetworkStateError: + return qsTr("The network is in an error state.") + } + } + + progressive: false + } + + NymeaItemDelegate { + Layout.fillWidth: true + text: qsTr("Home ID:") + subText: root.network ? "0x" + network.homeId.toString(16).toUpperCase() : "" + progressive: false + } + + SettingsPageSectionHeader { + text: qsTr("Hardware information") + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Serial port") + subText: root.network ? root.network.serialPort : "" + progressive: false + prominentSubText: false + } + + + SettingsPageSectionHeader { + text: qsTr("Manage network") + } + + ColumnLayout { + + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Reboot controller") + onClicked: { + d.pendingCommandId = root.zwaveManager.softResetController(root.network.networkUuid) + } + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Remove network") + onClicked: { + d.pendingCommandId = root.zwaveManager.removeNetwork(root.network.networkUuid) + } + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Factory reset controller") + onClicked: { + d.pendingCommandId = root.zwaveManager.factoryResetNetwork(root.network.networkUuid) + } + } + } +} diff --git a/nymea-app/ui/system/zwave/ZWaveSettingsPage.qml b/nymea-app/ui/system/zwave/ZWaveSettingsPage.qml new file mode 100644 index 00000000..cf999b85 --- /dev/null +++ b/nymea-app/ui/system/zwave/ZWaveSettingsPage.qml @@ -0,0 +1,196 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "qrc:/ui/components" +import Nymea 1.0 + +SettingsPageBase { + id: root + header: NymeaHeader { + text: qsTr("Z-Wave") + backButtonVisible: true + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "../images/add.svg" + text: qsTr("Add Z-Wave network") + onClicked: { + addNetwork() + } + } + } + + function addNetwork() { + var addPage = pageStack.push(Qt.resolvedUrl("ZWaveAddNetworkPage.qml"), {zwaveManager: zwaveManager}) + addPage.done.connect(function() {pageStack.pop(root)}) + } + + ZWaveManager { + id: zwaveManager + engine: _engine + } + + + Item { + Layout.fillWidth: true + Layout.preferredHeight: root.height + visible: zwaveManager.fetchingData || zwaveManager.networks.count == 0 + + BusyIndicator { + anchors.centerIn: parent + visible: zwaveManager.fetchingData + running: visible + } + + EmptyViewPlaceholder { + visible: !zwaveManager.fetchingData && zwaveManager.networks.count == 0 + width: parent.width - app.margins * 2 + anchors.centerIn: parent + title: qsTr("Z-Wave") + text: qsTr("There are no Z-Wave networks set up yet. In order to use Z-Wave, create a Z-Wave network.") + imageSource: "/ui/images/z-wave.svg" + buttonText: qsTr("Add network") + onButtonClicked: { + addNetwork() + } + } + } + + + ColumnLayout { + Layout.margins: app.margins / 2 + Repeater { + model: zwaveManager.networks + delegate: BigTile { + id: networkDelegate + Layout.fillWidth: true + interactive: false + property ZWaveNetwork network: zwaveManager.networks.get(index) + + onClicked: pageStack.push(Qt.resolvedUrl("ZWaveNetworkPage.qml"), { zwaveManager: zwaveManager, network: networkDelegate.network }) + + header: RowLayout { + Image { + source: "/ui/images/zwave/z-wave" + (network.isZWavePlus ? "-plus" : "") + "-wide.svg" + Layout.preferredHeight: Style.iconSize + // ssw : w = ssh : h + Layout.preferredWidth: sourceSize.width * height / sourceSize.height + } + + Label { + Layout.fillWidth: true + text: model.isZWavePlus ? qsTr("Z-Wave Plus network") : qsTr("Z-Wave network") +// font: Style.largeFont + } + } + + contentItem: ColumnLayout { + spacing: app.margins + + + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("Network state:") + } + Label { + text: { + switch (model.networkState) { + case ZWaveNetwork.ZWaveNetworkStateOnline: + return qsTr("Online") + case ZWaveNetwork.ZWaveNetworkStateOffline: + return qsTr("Offline") + case ZWaveNetwork.ZWaveNetworkStateStarting: + return qsTr("Starting") + case ZWaveNetwork.ZWaveNetworkStateError: + return qsTr("Error") + } + } + } + + Led { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + state: { + switch (model.networkState) { + case ZWaveNetwork.ZWaveNetworkStateOnline: + return "on" + case ZWaveNetwork.ZWaveNetworkStateOffline: + return "off" + case ZWaveNetwork.ZWaveNetworkStateStarting: + return "orange" + case ZWaveNetwork.ZWaveNetworkStateError: + return "red" + } + } + } + } + + RowLayout { + Label { + text: qsTr("Adapter:") + } + + Label { + Layout.fillWidth: true + text: adaptersProxy.count > 0 ? adaptersProxy.get(0).description : "" + elide: Text.ElideRight + + SerialPortsProxy { + id: adaptersProxy + serialPorts: zwaveManager.serialPorts + systemLocationFilter: networkDelegate.network.serialPort + } + } + } + + Label { + Layout.fillWidth: true + text: offlineNodes.count == 0 + ? qsTr("%n device(s)", "", Math.max(0, networkDelegate.network.nodes.count - 1)) // -1 for coordinator node + : qsTr("%n device(s) (%1 disconnected)", "", Math.max(networkDelegate.network.nodes.count - 1)).arg(offlineNodes.count) + + ZWaveNodesProxy { + id: offlineNodes + zwaveNodes: networkDelegate.network.nodes + showController: false + showOnline: false + } + } + } + } + } + } +} +