diff --git a/libnymea-app/zigbee/zigbeemanager.cpp b/libnymea-app/zigbee/zigbeemanager.cpp index 504098e7..8005379a 100644 --- a/libnymea-app/zigbee/zigbeemanager.cpp +++ b/libnymea-app/zigbee/zigbeemanager.cpp @@ -151,6 +151,11 @@ int ZigbeeManager::removeNode(const QUuid &networkUuid, const QString &ieeeAddre return m_engine->jsonRpcClient()->sendCommand("Zigbee.RemoveNode", params, this, "removeNodeResponse"); } +void ZigbeeManager::refreshNeighborTables(const QUuid &networkUuid) +{ + m_engine->jsonRpcClient()->sendCommand("Zigbee.RefreshNeighborTables", {{"networkUuid", networkUuid}}); +} + void ZigbeeManager::init() { m_fetchingData = true; @@ -378,7 +383,8 @@ void ZigbeeManager::fillNetworkData(ZigbeeNetwork *network, const QVariantMap &n network->setPermitJoiningDuration(networkMap.value("permitJoiningDuration").toUInt()); network->setPermitJoiningRemaining(networkMap.value("permitJoiningRemaining").toUInt()); network->setBackend(networkMap.value("backend").toString()); - network->setNetworkState(ZigbeeNetwork::stringToZigbeeNetworkState(networkMap.value("networkState").toString())); + QMetaEnum networkStateEnum = QMetaEnum::fromType(); + network->setNetworkState(static_cast(networkStateEnum.keyToValue(networkMap.value("networkState").toByteArray()))); } void ZigbeeManager::addOrUpdateNode(ZigbeeNetwork *network, const QVariantMap &nodeMap) @@ -413,7 +419,8 @@ void ZigbeeManager::updateNodeProperties(ZigbeeNode *node, const QVariantMap &no ZigbeeNode::ZigbeeNodeRelationship relationship = static_cast(relationshipEnum.keyToValue(neighborMap.value("relationship").toByteArray().data())); quint8 lqi = neighborMap.value("lqi").toUInt(); quint8 depth = neighborMap.value("depth").toUInt(); - node->addOrUpdateNeighbor(networkAddress, relationship, lqi, depth); + bool permitJoining = neighborMap.value("permitJoining").toBool(); + node->addOrUpdateNeighbor(networkAddress, relationship, lqi, depth, permitJoining); neighbors.append(networkAddress); } node->commitNeighbors(neighbors); diff --git a/libnymea-app/zigbee/zigbeemanager.h b/libnymea-app/zigbee/zigbeemanager.h index 96c2306e..38624695 100644 --- a/libnymea-app/zigbee/zigbeemanager.h +++ b/libnymea-app/zigbee/zigbeemanager.h @@ -95,6 +95,7 @@ public: Q_INVOKABLE void factoryResetNetwork(const QUuid &networkUuid); Q_INVOKABLE void getNodes(const QUuid &networkUuid); Q_INVOKABLE int removeNode(const QUuid &networkUuid, const QString &ieeeAddress); + Q_INVOKABLE void refreshNeighborTables(const QUuid &networkUuid); signals: void engineChanged(); diff --git a/libnymea-app/zigbee/zigbeenetwork.cpp b/libnymea-app/zigbee/zigbeenetwork.cpp index 102bd62c..327ee03f 100644 --- a/libnymea-app/zigbee/zigbeenetwork.cpp +++ b/libnymea-app/zigbee/zigbeenetwork.cpp @@ -253,22 +253,3 @@ void ZigbeeNetwork::setNetworkState(ZigbeeNetwork::ZigbeeNetworkState networkSta m_networkState = networkState; emit networkStateChanged(); } - -ZigbeeNetwork::ZigbeeNetworkState ZigbeeNetwork::stringToZigbeeNetworkState(const QString &networkStateString) -{ - if (networkStateString == "ZigbeeNetworkStateOffline") { - return ZigbeeNetworkStateOffline; - } else if (networkStateString == "ZigbeeNetworkStateStarting") { - return ZigbeeNetworkStateStarting; - } else if (networkStateString == "ZigbeeNetworkStateUpdating") { - return ZigbeeNetworkStateUpdating; - } else if (networkStateString == "ZigbeeNetworkStateOnline") { - return ZigbeeNetworkStateOnline; - } else if (networkStateString == "ZigbeeNetworkStateError") { - return ZigbeeNetworkStateError; - } else { - return ZigbeeNetworkStateError; - } -} - - diff --git a/libnymea-app/zigbee/zigbeenetwork.h b/libnymea-app/zigbee/zigbeenetwork.h index 2d5fdba8..645bb501 100644 --- a/libnymea-app/zigbee/zigbeenetwork.h +++ b/libnymea-app/zigbee/zigbeenetwork.h @@ -115,8 +115,6 @@ public: ZigbeeNetworkState networkState() const; void setNetworkState(ZigbeeNetworkState networkState); - static ZigbeeNetworkState stringToZigbeeNetworkState(const QString &networkStateString); - signals: void networkUuidChanged(); void enabledChanged(); diff --git a/libnymea-app/zigbee/zigbeenode.cpp b/libnymea-app/zigbee/zigbeenode.cpp index 65cde069..7bef3e4c 100644 --- a/libnymea-app/zigbee/zigbeenode.cpp +++ b/libnymea-app/zigbee/zigbeenode.cpp @@ -194,7 +194,7 @@ QList ZigbeeNode::neighbors() const return m_neighbors; } -void ZigbeeNode::addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelationship relationship, quint8 lqi, quint8 depth) +void ZigbeeNode::addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelationship relationship, quint8 lqi, quint8 depth, bool permitJoining) { foreach (ZigbeeNodeNeighbor *neighbor, m_neighbors) { if (neighbor->networkAddress() == networkAddress) { @@ -206,6 +206,10 @@ void ZigbeeNode::addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelations neighbor->setLqi(lqi); m_neighborsDirty = true; } + if (neighbor->permitJoining() != permitJoining) { + neighbor->setPermitJoining(permitJoining); + m_neighborsDirty = true; + } return; } } @@ -214,6 +218,7 @@ void ZigbeeNode::addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelations neighbor->setLqi(lqi); neighbor->setDepth(depth); m_neighbors.append(neighbor); + m_neighborsDirty = true; } void ZigbeeNode::commitNeighbors(QList toBeKept) @@ -308,3 +313,16 @@ void ZigbeeNodeNeighbor::setDepth(quint8 depth) emit depthChanged(); } } + +bool ZigbeeNodeNeighbor::permitJoining() const +{ + return m_permitJoining; +} + +void ZigbeeNodeNeighbor::setPermitJoining(bool permitJoining) +{ + if (m_permitJoining != permitJoining) { + m_permitJoining = permitJoining; + emit permitJoiningChanged(); + } +} diff --git a/libnymea-app/zigbee/zigbeenode.h b/libnymea-app/zigbee/zigbeenode.h index 9f1fe83e..f43c6203 100644 --- a/libnymea-app/zigbee/zigbeenode.h +++ b/libnymea-app/zigbee/zigbeenode.h @@ -116,7 +116,7 @@ public: void setLastSeen(const QDateTime &lastSeen); QList neighbors() const; - void addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelationship relationship, quint8 lqi, quint8 depth); + void addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelationship relationship, quint8 lqi, quint8 depth, bool permitJoining); void commitNeighbors(QList toBeKept); static ZigbeeNodeState stringToNodeState(const QString &nodeState); @@ -159,6 +159,7 @@ class ZigbeeNodeNeighbor: public QObject Q_PROPERTY(ZigbeeNode::ZigbeeNodeRelationship relationship READ relationship NOTIFY relationshipChanged) Q_PROPERTY(quint8 lqi READ lqi NOTIFY lqiChanged) Q_PROPERTY(quint8 depth READ depth NOTIFY depthChanged) + Q_PROPERTY(bool permitJoining READ permitJoining NOTIFY permitJoiningChanged) public: ZigbeeNodeNeighbor(quint16 networkAddress, QObject *parent); @@ -174,16 +175,21 @@ public: quint8 depth() const; void setDepth(quint8 depth); + bool permitJoining() const; + void setPermitJoining(bool permitJoining); + signals: void relationshipChanged(); void lqiChanged(); void depthChanged(); + void permitJoiningChanged(); private: quint16 m_networkAddress; ZigbeeNode::ZigbeeNodeRelationship m_relationship; quint8 m_lqi = 0; quint8 m_depth = 0; + bool m_permitJoining = false; }; #endif // ZIGBEENODE_H diff --git a/libnymea-app/zigbee/zigbeenodes.cpp b/libnymea-app/zigbee/zigbeenodes.cpp index a6a3aa81..bea5595b 100644 --- a/libnymea-app/zigbee/zigbeenodes.cpp +++ b/libnymea-app/zigbee/zigbeenodes.cpp @@ -149,6 +149,8 @@ void ZigbeeNodes::addNode(ZigbeeNode *node) endInsertRows(); emit countChanged(); + + emit nodeAdded(node); } void ZigbeeNodes::removeNode(const QString &ieeeAddress) @@ -159,6 +161,7 @@ void ZigbeeNodes::removeNode(const QString &ieeeAddress) m_nodes.takeAt(i)->deleteLater(); endRemoveRows(); emit countChanged(); + emit nodeRemoved(ieeeAddress); return; } } diff --git a/libnymea-app/zigbee/zigbeenodes.h b/libnymea-app/zigbee/zigbeenodes.h index d8d1df24..d6d8d686 100644 --- a/libnymea-app/zigbee/zigbeenodes.h +++ b/libnymea-app/zigbee/zigbeenodes.h @@ -76,6 +76,8 @@ public: signals: void countChanged(); + void nodeAdded(ZigbeeNode *node); + void nodeRemoved(const QString &ieeeAddress); protected: QList m_nodes; diff --git a/nymea-app/ui/system/zigbee/ZigbeeNetworkSettingsPage.qml b/nymea-app/ui/system/zigbee/ZigbeeNetworkSettingsPage.qml index c2c7196b..a16d30d5 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeNetworkSettingsPage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeNetworkSettingsPage.qml @@ -92,7 +92,9 @@ SettingsPageBase { NymeaItemDelegate { Layout.fillWidth: true text: qsTr("Network topology") - onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeTopologyPage.qml"), {network: root.network}) + onClicked: { + pageStack.push(Qt.resolvedUrl("ZigbeeTopologyPage.qml"), {zigbeeManager: root.zigbeeManager, network: root.network}) + } } SettingsPageSectionHeader { diff --git a/nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml b/nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml index 41de6a08..6535e959 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml @@ -13,6 +13,7 @@ Page { onBackPressed: pageStack.pop() } + property ZigbeeManager zigbeeManager: null property ZigbeeNetwork network: null readonly property int nodeDistance: 150 @@ -21,13 +22,24 @@ Page { Component.onCompleted: { - generateNodeList() - canvas.requestPaint() - flickable.contentX = (flickable.contentWidth - flickable.width) / 2 - flickable.contentY = (flickable.contentHeight - flickable.height) / 2 + zigbeeManager.refreshNeighborTables(network.networkUuid) + + reload(); + for (var i = 0; i < network.nodes.count; i++) { + network.nodes.get(i).neighborsChanged.connect(function() {root.reload()}) + } + } + + Connections { + target: root.network.nodes + onNodeAdded: { + root.reload() + node.neighborsChanged.connect(function() {root.reload()}); + } } function generateNodeList() { + d.nodeItems = [] var coordinator = {} var routers = [] var endDevices = [] @@ -88,6 +100,30 @@ Page { } } } + + var unconnectedNodes = [] + for (var i = 0; i < network.nodes.count; i++) { + var node = network.nodes.get(i) + if (node.type == ZigbeeNode.ZigbeeNodeTypeEndDevice && handledEndDevices.indexOf(node.networkAddress) < 0) { + print("Adding unconnected node:","0x" + node.networkAddress.toString(16)) + unconnectedNodes.push(node) + } + } + var cellWidth = root.nodeSize * 2 + var cellHeight = root.nodeSize * 2 + var maxColumns = (root.width - Style.bigMargins * 2) / cellWidth + var columns = Math.min(unconnectedNodes.length, maxColumns) + var rowWidth = columns * cellWidth + print("columns:", columns, "maxCols", maxColumns) + for (var i = 0; i < unconnectedNodes.length; i++) { + var node = unconnectedNodes[i] + var column = i % columns; + var row = Math.floor(i / columns) + var x = cellWidth * column + cellWidth / 2 - rowWidth / 2 + var y = Style.margins + cellHeight * row + root.nodeSize - root.height / 3 + var point = root.mapToItem(canvas, x, y) + d.nodeItems.push(createNodeItem(node, point.x, point.y, 0)) + } } @@ -126,19 +162,35 @@ Page { thing: thing } - print("creared node", thing ? thing.name : "", " at", x, y) +// print("creared node", thing ? thing.name : "", " at", x, y) d.adjustSize(x, y) return nodeItem } + function reload() { + print("Reloading network map") + while (d.nodeItems.length > 0) { + var nodeItem = d.nodeItems.shift() + nodeItem.image.destroy(); + } +// d.selectedNodeItem= null + d.minX = 0 + d.minY = 0 + d.maxX = 0 + d.maxY = 0 + d.size = 0 + generateNodeList(); + canvas.requestPaint() + flickable.contentX = (flickable.contentWidth - flickable.width) / 2 + flickable.contentY = (flickable.contentHeight - flickable.height) / 2 + } + QtObject { id: d - property var nodeTree: ({}) - property var handledNodes: [] - property var nodeItems: [] - property var selectedNodeItem: null + property int selectedNodeAddress: -1 + readonly property ZigbeeNode selectedNode: selectedNodeAddress >= 0 ? network.nodes.getNodeByNetworkAddress(selectedNodeAddress) : null property int minX: 0 property int minY: 0 @@ -180,7 +232,7 @@ Page { clip: true onPaint: { - print("**** height:", canvas.height, "width", canvas.width) +// print("**** height:", canvas.height, "width", canvas.width) var ctx = getContext("2d"); ctx.reset(); @@ -209,7 +261,7 @@ Page { for (var k = 0; k < d.nodeItems.length; k++) { if (d.nodeItems[k].node.networkAddress == neighbor.networkAddress) { var toNodeItem = d.nodeItems[k] - if (nodeItem === d.selectedNodeItem || toNodeItem === d.selectedNodeItem) { + if (nodeItem.node.networkAddress === d.selectedNodeAddress || toNodeItem.node.networkAddress === d.selectedNodeAddress) { if (selected) { paintEdge(ctx, nodeItem, d.nodeItems[k], neighbor.lqi, true) } @@ -227,11 +279,11 @@ Page { function paintNode(ctx, nodeItem) { ctx.save() ctx.beginPath(); - ctx.fillStyle = Style.tileBackgroundColor - ctx.strokeStyle = nodeItem === d.selectedNodeItem ? Style.accentColor : Style.foregroundColor + ctx.fillStyle = nodeItem.node.networkAddress === d.selectedNodeAddress ? Style.tileOverlayColor : Style.tileBackgroundColor + ctx.strokeStyle = nodeItem.node.networkAddress === d.selectedNodeAddress ? Style.accentColor : Style.tileBackgroundColor ctx.arc(root.scale * nodeItem.x, root.scale * nodeItem.y, root.scale * root.nodeSize / 2, 0, 2 * Math.PI); ctx.fill(); - // ctx.stroke(); +// ctx.stroke(); ctx.fillStyle = Style.foregroundColor ctx.font = "" + Style.extraSmallFont.pixelSize + "px Ubuntu"; var text = "" @@ -267,7 +319,7 @@ Page { ctx.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, 1) } else { ctx.lineWidth = 1 - var alpha = d.selectedNodeItem ? .2 : 1 + var alpha = d.selectedNodeAddress >= 0 ? .2 : 1 ctx.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, alpha) } ctx.beginPath(); @@ -287,13 +339,13 @@ Page { print("clicked:", mouseX, mouseY) var translatedMouseX = mouseX - canvas.width / 2 var translatedMouseY = mouseY - canvas.height / 2 - d.selectedNodeItem = null + d.selectedNodeAddress = -1 for (var i = 0; i < d.nodeItems.length; i++) { var nodeItem = d.nodeItems[i] // print("nodeItem at:", root.scale * nodeItem.x, root.scale * nodeItem.y) if (Math.abs(root.scale * nodeItem.x - translatedMouseX) < (root.scale * root.nodeSize / 2) && Math.abs(root.scale * nodeItem.y - translatedMouseY) < (root.scale * root.nodeSize / 2)) { - d.selectedNodeItem = nodeItem; + d.selectedNodeAddress = nodeItem.node.networkAddress; print("sleecting", nodeItem.node.networkAddress) } } @@ -302,33 +354,37 @@ Page { } } } - - } BigTile { - visible: d.selectedNodeItem + visible: d.selectedNodeAddress >= 0 anchors { top: parent.top right: parent.right margins: Style.margins } - width: 200 + width: 260 header: RowLayout { width: parent.width - Style.smallMargins spacing: Style.smallMargins Label { Layout.fillWidth: true elide: Text.ElideRight - text: !d.selectedNodeItem + ThingsProxy { + id: selectedThingsProxy + engine: _engine + paramsFilter: {"ieeeAddress": d.selectedNode ? d.selectedNode.ieeeAddress : "---"} + } + + text: d.selectedNodeAddress < 0 ? "" - : d.selectedNodeItem.node.networkAddress === 0 + : d.selectedNodeAddress === 0 ? Configuration.systemName - : d.selectedNodeItem.thing - ? d.selectedNodeItem.thing.name - : d.selectedNodeItem.node.model + : selectedThingsProxy.count > 0 + ? selectedThingsProxy.get(0).name + : network.nodes.getNodeByNetworkAddress(d.selectedNode).model } ColorIcon { size: Style.smallIconSize @@ -354,18 +410,23 @@ Page { size: Style.smallIconSize name: "/ui/images/things.svg" } + ColorIcon { + size: Style.smallIconSize + name: "/ui/images/add.svg" + } } contentItem: ListView { + id: tableListView spacing: app.margins implicitHeight: Math.min(root.height / 4, count * Style.smallIconSize) clip: true - model: d.selectedNodeItem ? d.selectedNodeItem.node.neighbors.length : 0 + model: d.selectedNode ? d.selectedNode.neighbors.length : 0 delegate: RowLayout { id: neighborTableDelegate - width: parent.width - property ZigbeeNodeNeighbor neighbor: d.selectedNodeItem.node.neighbors[index] + width: tableListView.width + property ZigbeeNodeNeighbor neighbor: d.selectedNode.neighbors[index] property ZigbeeNode neighborNode: root.network.nodes.getNodeByNetworkAddress(neighbor.networkAddress) property Thing neighborNodeThing: { for (var i = 0; i < engine.thingManager.things.count; i++) { @@ -401,9 +462,12 @@ Page { text: neighborTableDelegate.neighbor.depth horizontalAlignment: Text.AlignRight } + ColorIcon { + size: Style.smallIconSize + name: "add" + opacity: neighborTableDelegate.neighbor.permitJoining ? 1 : 0 + } } - } } - }