From 4f6e6b9dea1fe991227e5c4531523235e3cfc466 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 19 Oct 2022 23:14:53 +0200 Subject: [PATCH] Cleanup and fixes for Zigbee settings --- libnymea-app/zigbee/zigbeemanager.cpp | 19 ++ libnymea-app/zigbee/zigbeemanager.h | 3 + libnymea-app/zigbee/zigbeenodesproxy.cpp | 3 +- nymea-app/ui/components/MeaDialog.qml | 5 +- .../ui/system/zigbee/ZigbeeNetworkPage.qml | 5 +- .../zigbee/ZigbeeNetworkSettingsPage.qml | 13 +- nymea-app/ui/system/zigbee/ZigbeeNodePage.qml | 227 ++++++++++++------ .../ui/system/zigbee/ZigbeeSettingsPage.qml | 12 +- .../thingconfiguration/ConfigureThingPage.qml | 2 +- nymea-app/ui/utils/NymeaUtils.qml | 12 +- 10 files changed, 212 insertions(+), 89 deletions(-) diff --git a/libnymea-app/zigbee/zigbeemanager.cpp b/libnymea-app/zigbee/zigbeemanager.cpp index 58589fc8..3b32a1ae 100644 --- a/libnymea-app/zigbee/zigbeemanager.cpp +++ b/libnymea-app/zigbee/zigbeemanager.cpp @@ -170,6 +170,19 @@ int ZigbeeManager::createBinding(const QUuid &networkUuid, const QString &source return m_engine->jsonRpcClient()->sendCommand("Zigbee.CreateBinding", params, this, "createBindingResponse"); } +int ZigbeeManager::createGroupBinding(const QUuid &networkUuid, const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, quint16 destinationGroupAddress) +{ + QVariantMap params = { + {"networkUuid", networkUuid}, + {"sourceAddress", sourceAddress}, + {"sourceEndpointId", sourceEndpointId}, + {"clusterId", clusterId}, + {"destinationGroupAddress", destinationGroupAddress} + }; + qCDebug(dcZigbee()) << "Creating binding for:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + return m_engine->jsonRpcClient()->sendCommand("Zigbee.CreateBinding", params, this, "createBindingResponse"); +} + int ZigbeeManager::removeBinding(const QUuid &networkUuid, ZigbeeNodeBinding *binding) { QVariantMap params = { @@ -256,6 +269,9 @@ void ZigbeeManager::addNetworkResponse(int commandId, const QVariantMap ¶ms) void ZigbeeManager::removeNetworkResponse(int commandId, const QVariantMap ¶ms) { qCDebug(dcZigbee()) << "Zigbee remove network response" << commandId << params; + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZigbeeError error = static_cast(errorEnum.keyToValue(params.value("zigbeeError").toByteArray())); + emit removeNetworkReply(commandId, error); } void ZigbeeManager::setPermitJoinResponse(int commandId, const QVariantMap ¶ms) @@ -266,6 +282,9 @@ void ZigbeeManager::setPermitJoinResponse(int commandId, const QVariantMap ¶ void ZigbeeManager::factoryResetNetworkResponse(int commandId, const QVariantMap ¶ms) { qCDebug(dcZigbee()) << "Zigbee factory reset network response" << commandId << params; + QMetaEnum errorEnum = QMetaEnum::fromType(); + ZigbeeError error = static_cast(errorEnum.keyToValue(params.value("zigbeeError").toByteArray())); + emit factoryResetNetworkReply(commandId, error); } void ZigbeeManager::getNodesResponse(int commandId, const QVariantMap ¶ms) diff --git a/libnymea-app/zigbee/zigbeemanager.h b/libnymea-app/zigbee/zigbeemanager.h index 4f0878e9..46c169df 100644 --- a/libnymea-app/zigbee/zigbeemanager.h +++ b/libnymea-app/zigbee/zigbeemanager.h @@ -114,6 +114,7 @@ public: Q_INVOKABLE int removeNode(const QUuid &networkUuid, const QString &ieeeAddress); Q_INVOKABLE void refreshNeighborTables(const QUuid &networkUuid); Q_INVOKABLE int createBinding(const QUuid &networkUuid, const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, const QString &destinationAddress, quint8 destinationEndpointId); + Q_INVOKABLE int createGroupBinding(const QUuid &networkUuid, const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, quint16 destinationGroupAddress); Q_INVOKABLE int removeBinding(const QUuid &networkUuid, ZigbeeNodeBinding *binding); signals: @@ -121,6 +122,8 @@ signals: void fetchingDataChanged(); void availableBackendsChanged(); void addNetworkReply(int commandId, ZigbeeError error, const QUuid &networkUuid); + void removeNetworkReply(int commandId, ZigbeeError error); + void factoryResetNetworkReply(int commandId, ZigbeeError error); void removeNodeReply(int commandId, ZigbeeError error); void createBindingReply(int commandId, ZigbeeError error); void removeBindingReply(int commandId, ZigbeeError error); diff --git a/libnymea-app/zigbee/zigbeenodesproxy.cpp b/libnymea-app/zigbee/zigbeenodesproxy.cpp index 12cbce56..1563839b 100644 --- a/libnymea-app/zigbee/zigbeenodesproxy.cpp +++ b/libnymea-app/zigbee/zigbeenodesproxy.cpp @@ -67,7 +67,8 @@ void ZigbeeNodesProxy::setZigbeeNodes(ZigbeeNodes *zigbeeNodes) emit countChanged(); }); connect(m_zigbeeNodes, &ZigbeeNodes::dataChanged, this, [this](const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/, const QVector &roles = QVector()){ - if (roles.contains(ZigbeeNodes::RoleReachable) && (!m_showOffline || !m_showOnline)) { + if ((roles.contains(ZigbeeNodes::RoleReachable) && (!m_showOffline || !m_showOnline)) + || (roles.contains(ZigbeeNodes::RoleType) && !m_showCoordinator)) { invalidateFilter(); emit countChanged(); } diff --git a/nymea-app/ui/components/MeaDialog.qml b/nymea-app/ui/components/MeaDialog.qml index 6f46a258..53c00c6a 100644 --- a/nymea-app/ui/components/MeaDialog.qml +++ b/nymea-app/ui/components/MeaDialog.qml @@ -56,7 +56,10 @@ Dialog { parent: app.overlay anchors.fill: parent z: -1 - onPressed: mouse.accepted = true + onPressed: { + print("Dialog: eating mouse press") + mouse.accepted = true + } } header: Item { diff --git a/nymea-app/ui/system/zigbee/ZigbeeNetworkPage.qml b/nymea-app/ui/system/zigbee/ZigbeeNetworkPage.qml index 8f60a8d1..bdadf4f5 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeNetworkPage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeNetworkPage.qml @@ -41,6 +41,8 @@ SettingsPageBase { property ZigbeeManager zigbeeManager: null property ZigbeeNetwork network: null + signal exit() + header: NymeaHeader { text: qsTr("ZigBee network") backButtonVisible: true @@ -61,8 +63,7 @@ SettingsPageBase { onClicked: { var page = pageStack.push(Qt.resolvedUrl("ZigbeeNetworkSettingsPage.qml"), { zigbeeManager: zigbeeManager, network: network }) page.exit.connect(function() { - pageStack.pop(root, StackView.Immediate) - pageStack.pop() + root.exit() }) } } diff --git a/nymea-app/ui/system/zigbee/ZigbeeNetworkSettingsPage.qml b/nymea-app/ui/system/zigbee/ZigbeeNetworkSettingsPage.qml index 3f453f63..66aefe68 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeNetworkSettingsPage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeNetworkSettingsPage.qml @@ -43,6 +43,15 @@ SettingsPageBase { signal exit() + Connections { + target: zigbeeManager + onFactoryResetNetworkReply: { + busy = false; +// if (error != ZigbeeManager.ZigbeeErrorNoError) { +// } + } + } + header: NymeaHeader { text: qsTr("ZigBee network settings") backButtonVisible: true @@ -165,8 +174,9 @@ SettingsPageBase { }); popup.open(); popup.accepted.connect(function() { - root.zigbeeManager.removeNetwork(root.network.networkUuid) + popup.destroy(); root.exit() + root.zigbeeManager.removeNetwork(root.network.networkUuid) }) } } @@ -189,6 +199,7 @@ SettingsPageBase { popup.open(); popup.accepted.connect(function() { root.zigbeeManager.factoryResetNetwork(root.network.networkUuid) + root.busy = true; }) } } diff --git a/nymea-app/ui/system/zigbee/ZigbeeNodePage.qml b/nymea-app/ui/system/zigbee/ZigbeeNodePage.qml index 7471a6ef..d330cb51 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeNodePage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeNodePage.qml @@ -180,12 +180,12 @@ SettingsPageBase { subText: qsTr("Version") } -// NymeaItemDelegate { -// Layout.fillWidth: true -// text: qsTr("Device endpoints") -// subText: qsTr("Show detailed information about the node") -// onClicked: pageStack.push(endpointsPageComponent) -// } + NymeaItemDelegate { + Layout.fillWidth: true + text: qsTr("Device endpoints") + subText: qsTr("Show detailed information about the node") + onClicked: pageStack.push(endpointsPageComponent) + } SettingsPageSectionHeader { text: qsTr("Associated things") @@ -338,43 +338,67 @@ SettingsPageBase { Component { id: endpointsPageComponent SettingsPageBase { - title: qsTr("Device endpoints") + title: qsTr("Node descriptor") - Repeater { - model: root.node.endpoints - delegate: ColumnLayout { - id: endpointDelegate - property ZigbeeNodeEndpoint endpoint: root.node.endpoints[index] - Label { - Layout.fillWidth: true - text: "- " + qsTr("Endpoint %1").arg(endpointDelegate.endpoint.endpointId) + header: NymeaHeader { + text: qsTr("ZigBee node descriptor") + backButtonVisible: true + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "/ui/images/edit-copy.svg" + text: qsTr("Copy") + onClicked: { + PlatformHelper.toClipBoard(descriptorText.text) + ToolTip.show(qsTr("Copied to clipboard"), 1000); } - Label { - Layout.fillWidth: true - text: " " + qsTr("Input clusters") + } + } + + + TextArea { + id: descriptorText + readOnly: true + Layout.fillWidth: true + leftPadding: Style.margins + rightPadding: Style.margins + topPadding: Style.margins + bottomPadding: Style.margins + font.family: "Monospace" + + text: { + var ret = root.node.manufacturer + "\n" + ret += root.node.model + "\n" + ret += "RxOnWhileIdle: " + root.node.rxOnWhenIdle + "\n" + ret += "Basic cluster version: " + root.node.version + "\n" + + if (root.node.endpoints.length > 0) { + ret += "Endpoints\n"; } - Repeater { - model: endpointDelegate.endpoint.inputClusters - delegate: Label { - Layout.fillWidth: true - property ZigbeeCluster cluster: endpointDelegate.endpoint.inputClusters[index] - text: " - " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")" + for (var i = 0; i < root.node.endpoints.length; i++) { + var endpoint = root.node.endpoints[i] + var isLastEp = i == root.node.endpoints.length - 1; + var hasInputClusters = endpoint.inputClusters.length > 0 + var hasOutputClusters = endpoint.outputClusters.length > 0 + ret += (isLastEp ? "└" : "├") + (hasInputClusters || hasOutputClusters ? "┬" : "─") + " " + endpoint.endpointId + "\n" + ret += (isLastEp ? " " : "│") + (hasOutputClusters ? "├" : "└") + (hasInputClusters ? "┬" : "─") + " Input clusters\n" + for (var j = 0; j < endpoint.inputClusters.length; j++) { + var cluster = endpoint.inputClusters[j] + var isLast = j == endpoint.inputClusters.length - 1 + ret += (isLastEp ? " " : "│") + (hasOutputClusters ? "│" : " ") + (isLast ? "└" : "├") + "─ 0x" + NymeaUtils.pad(cluster.clusterId, 4, 16) + " " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")\n" } - } - Label { - Layout.fillWidth: true - text: " " + qsTr("Output clusters") - } - - Repeater { - model: endpointDelegate.endpoint.outputClusters - delegate: Label { - Layout.fillWidth: true - property ZigbeeCluster cluster: endpointDelegate.endpoint.outputClusters[index] - text: " - " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")" + if (hasOutputClusters) { + ret += (isLastEp ? " " : "│") + "└" + (hasOutputClusters ? "┬" : "─") +" Output clusters\n" + for (var j = 0; j < endpoint.outputClusters.length; j++) { + var cluster = endpoint.outputClusters[j] + var isLast = j == endpoint.outputClusters.length - 1 + ret += (isLastEp ? " " : "│") + " " + (isLast ? "└" : "├") + "─ 0x" + NymeaUtils.pad(cluster.clusterId, 4, 16) + " " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")\n" + } } + } + return ret; } } } @@ -400,14 +424,32 @@ SettingsPageBase { onCurrentEndpointChanged: print("source endpoint changed", currentEndpoint.endpointId) } Label { - text: qsTr("Target node") + text: qsTr("Target") Layout.fillWidth: true } + + RowLayout { + // Groups not properly implemented yet. hiding it for now + visible: false + RadioButton { + id: nodeRadioButton + text: qsTr("Node") + checked: true + Layout.fillWidth: true + } + RadioButton { + id: groupRadioButton + text: qsTr("Group") + Layout.fillWidth: true + } + } + ComboBox { id: destinationNodeComboBox Layout.fillWidth: true Layout.preferredHeight: Style.delegateHeight model: network.nodes + visible: nodeRadioButton.checked property ZigbeeNode currentNode: network.nodes.get(currentIndex) property Thing currentNodeThing: currentNode && currentDestinationNodeThings.count > 0 ? currentDestinationNodeThings.get(0) : null ThingsProxy { @@ -483,11 +525,13 @@ SettingsPageBase { } } Label { + visible: nodeRadioButton.checked text: qsTr("Destination endpoint") Layout.fillWidth: true } ComboBox { id: destinationEndpointComboBox + visible: nodeRadioButton.checked Layout.fillWidth: true model: destinationNodeComboBox.currentNode.endpoints textRole: "endpointId" @@ -495,6 +539,18 @@ SettingsPageBase { property ZigbeeNodeEndpoint currentEndpoint: destinationNodeComboBox.currentNode.endpoints[currentIndex] } + Label { + text: qsTr("Group address") + visible: groupRadioButton.checked + } + + TextField { + id: groupAddressTextField + visible: groupRadioButton.checked + Layout.fillWidth: true + text: "0" + } + Label { text: qsTr("Cluster") Layout.fillWidth: true @@ -509,44 +565,51 @@ SettingsPageBase { } model: { var ret = [] - print("updating clusters", sourceEndpointComboBox.currentEndpoint, destinationNodeComboBox.currentNode, destinationEndpointComboBox.currentEndpoint) - if (!sourceEndpointComboBox.currentEndpoint || !destinationNodeComboBox.currentNode || !destinationEndpointComboBox.currentEndpoint) { - return ret; - } - - if (destinationNodeComboBox.currentNode == root.coordinatorNode) { - for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) { - var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i] - ret.push(outputCluster) + if (nodeRadioButton.checked) { + print("updating clusters", sourceEndpointComboBox.currentEndpoint, destinationNodeComboBox.currentNode, destinationEndpointComboBox.currentEndpoint) + if (!sourceEndpointComboBox.currentEndpoint || !destinationNodeComboBox.currentNode || !destinationEndpointComboBox.currentEndpoint) { + return ret; } - for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) { - var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i] - ret.push(inputCluster) + + if (destinationNodeComboBox.currentNode == root.coordinatorNode) { + for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) { + var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i] + ret.push(outputCluster) + } + for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) { + var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i] + ret.push(inputCluster) + } + } else { + for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) { + var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i] + print("source has cluster", outputCluster.clusterId) + for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.inputClusters.length; j++) { + var inputCluster = destinationEndpointComboBox.currentEndpoint.inputClusters[j] + print("destination has cluster", inputCluster.clusterId) + if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) { + ret.push(outputCluster); + break; + } + } + } + for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) { + var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i] + print("source has cluster", inputCluster.clusterId) + for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.outputClusters.length; j++) { + var outputCluster = destinationEndpointComboBox.currentEndpoint.outputClusters[j] + print("destination has cluster", outputCluster.clusterId) + if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) { + ret.push(inputCluster); + break; + } + } + } } } else { - for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) { - var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i] - print("source has cluster", outputCluster.clusterId) - for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.inputClusters.length; j++) { - var inputCluster = destinationEndpointComboBox.currentEndpoint.inputClusters[j] - print("destination has cluster", inputCluster.clusterId) - if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) { - ret.push(outputCluster); - break; - } - } - } for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) { var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i] - print("source has cluster", inputCluster.clusterId) - for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.outputClusters.length; j++) { - var outputCluster = destinationEndpointComboBox.currentEndpoint.outputClusters[j] - print("destination has cluster", outputCluster.clusterId) - if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) { - ret.push(inputCluster); - break; - } - } + ret.push(inputCluster); } } @@ -556,15 +619,23 @@ SettingsPageBase { displayText: currentValue.clusterName() } - onAccepted: { - d.pendingCommandId = root.zigbeeManager.createBinding( - root.network.networkUuid, - root.node.ieeeAddress, - sourceEndpointComboBox.currentEndpoint.endpointId, - clusterComboBox.currentCluster.clusterId, - destinationNodeComboBox.currentNode.ieeeAddress, - destinationEndpointComboBox.currentEndpoint.endpointId) + if (nodeRadioButton.checked) { + d.pendingCommandId = root.zigbeeManager.createBinding( + root.network.networkUuid, + root.node.ieeeAddress, + sourceEndpointComboBox.currentEndpoint.endpointId, + clusterComboBox.currentCluster.clusterId, + destinationNodeComboBox.currentNode.ieeeAddress, + destinationEndpointComboBox.currentEndpoint.endpointId) + } else { + d.pendingCommandId = root.zigbeeManager.createGroupBinding( + root.network.networkUuid, + root.node.ieeeAddress, + sourceEndpointComboBox.currentEndpoint.endpointId, + clusterComboBox.currentCluster.clusterId, + groupAddressTextField.text) + } if (!root.node.rxOnWhenIdle) { d.wakeupDialog = wakeupDialogComponent.createObject(root) diff --git a/nymea-app/ui/system/zigbee/ZigbeeSettingsPage.qml b/nymea-app/ui/system/zigbee/ZigbeeSettingsPage.qml index 04e31a4e..fa9b0462 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeSettingsPage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeSettingsPage.qml @@ -56,6 +56,10 @@ SettingsPageBase { addPage.done.connect(function() {pageStack.pop(root)}) } + function returnToMain() { + pageStack.pop(root) + } + ZigbeeManager { id: zigbeeManager engine: _engine @@ -98,7 +102,13 @@ SettingsPageBase { interactive: false property ZigbeeNetwork network: zigbeeManager.networks.get(index) - onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeNetworkPage.qml"), { zigbeeManager: zigbeeManager, network: networkDelegate.network }) + onClicked: { + var page = pageStack.push(Qt.resolvedUrl("ZigbeeNetworkPage.qml"), { zigbeeManager: zigbeeManager, network: networkDelegate.network }) + page.exit.connect(function() { + print("exiting") + root.returnToMain() + }) + } header: RowLayout { ColorIcon { diff --git a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml index 9e18f15c..e2d12fef 100644 --- a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml +++ b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml @@ -144,7 +144,7 @@ SettingsPageBase { progressive: false onClicked: { PlatformHelper.toClipBoard(root.thing.id.toString().replace(/[{}]/g, "")); - ToolTip.show(qsTr("ID copied to clipboard"), 500); + ToolTip.show(qsTr("ID copied to clipboard"), 1000); } } diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml index a1dc1ddd..b0079729 100644 --- a/nymea-app/ui/utils/NymeaUtils.qml +++ b/nymea-app/ui/utils/NymeaUtils.qml @@ -6,14 +6,18 @@ import QtCharts 2.2 Item { id: root - function pad(num, size) { + function pad(num, size, base) { + if (base == undefined) { + base = 10 + } + var trimmedNum = Math.floor(num) var decimals = num - trimmedNum - var trimmedStr = "" + trimmedNum - var str = "000000000" + trimmedNum; + var trimmedStr = "" + trimmedNum.toString(16) + var str = "000000000" + trimmedStr str = str.substr(str.length - Math.max(size, trimmedStr.length)); if (decimals !== 0) { - str += "." + (num - trimmedNum); + str += "." + decimals.toString(base); } return str; }