diff --git a/libnymea-app/zigbee/zigbeemanager.cpp b/libnymea-app/zigbee/zigbeemanager.cpp index 8005379a..f6d8b146 100644 --- a/libnymea-app/zigbee/zigbeemanager.cpp +++ b/libnymea-app/zigbee/zigbeemanager.cpp @@ -424,4 +424,18 @@ void ZigbeeManager::updateNodeProperties(ZigbeeNode *node, const QVariantMap &no neighbors.append(networkAddress); } node->commitNeighbors(neighbors); + + QList routes; + foreach (const QVariant &route, nodeMap.value("routingTableRecords").toList()) { + QVariantMap routeMap = route.toMap(); + quint16 destinationAddress = routeMap.value("destinationAddress").toUInt(); + quint16 nextHopAddress = routeMap.value("nextHopAddress").toUInt(); + QMetaEnum routeStatusEnum = QMetaEnum::fromType(); + ZigbeeNode::ZigbeeNodeRouteStatus routeStatus = static_cast(routeStatusEnum.keyToValue(routeMap.value("status").toByteArray().data())); + bool memoryConstrained = routeMap.value("memoryConstrained").toBool(); + bool manyToOne = routeMap.value("manyToOne").toBool(); + node->addOrUpdateRoute(destinationAddress, nextHopAddress, routeStatus, memoryConstrained, manyToOne); + routes.append(destinationAddress); + } + node->commitRoutes(routes); } diff --git a/libnymea-app/zigbee/zigbeenode.cpp b/libnymea-app/zigbee/zigbeenode.cpp index 7bef3e4c..471ad9fd 100644 --- a/libnymea-app/zigbee/zigbeenode.cpp +++ b/libnymea-app/zigbee/zigbeenode.cpp @@ -210,12 +210,17 @@ void ZigbeeNode::addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelations neighbor->setPermitJoining(permitJoining); m_neighborsDirty = true; } + if (neighbor->depth() != depth) { + neighbor->setDepth(depth); + m_neighborsDirty = true; + } return; } } ZigbeeNodeNeighbor *neighbor = new ZigbeeNodeNeighbor(networkAddress, this); neighbor->setRelationship(relationship); neighbor->setLqi(lqi); + neighbor->setPermitJoining(permitJoining); neighbor->setDepth(depth); m_neighbors.append(neighbor); m_neighborsDirty = true; @@ -239,6 +244,61 @@ void ZigbeeNode::commitNeighbors(QList toBeKept) } } +QList ZigbeeNode::routes() const +{ + return m_routes; +} + +void ZigbeeNode::addOrUpdateRoute(quint16 destinationAddress, quint16 nextHopAddress, ZigbeeNodeRouteStatus status, bool memoryConstrained, bool manyToOne) +{ + foreach (ZigbeeNodeRoute *route, m_routes) { + if (route->destinationAddress() == destinationAddress) { + if (route->nextHopAddress() != nextHopAddress) { + route->setNextHopAddress(nextHopAddress); + m_routesDirty = true; + } + if (route->status() != status) { + route->setStatus(status); + m_routesDirty = true; + } + if (route->memoryConstrained() != memoryConstrained) { + route->setMemoryConstrained(memoryConstrained); + m_routesDirty = true; + } + if (route->manyToOne() != manyToOne) { + route->setManyToOne(manyToOne); + m_routesDirty = true; + } + return; + } + } + ZigbeeNodeRoute *route = new ZigbeeNodeRoute(destinationAddress, this); + route->setNextHopAddress(nextHopAddress); + route->setStatus(status); + route->setMemoryConstrained(memoryConstrained); + route->setManyToOne(manyToOne); + m_routes.append(route); + m_routesDirty = true; +} + +void ZigbeeNode::commitRoutes(QList toBeKept) +{ + QMutableListIterator iter(m_routes); + + while (iter.hasNext()) { + ZigbeeNodeRoute *route = iter.next(); + if (!toBeKept.contains(route->destinationAddress())) { + iter.remove(); + route->deleteLater(); + m_routesDirty = true; + } + } + if (m_routesDirty) { + emit routesChanged(); + m_routesDirty = false; + } +} + ZigbeeNode::ZigbeeNodeState ZigbeeNode::stringToNodeState(const QString &nodeState) { if (nodeState == "ZigbeeNodeStateUninitialized") { @@ -326,3 +386,67 @@ void ZigbeeNodeNeighbor::setPermitJoining(bool permitJoining) emit permitJoiningChanged(); } } + +ZigbeeNodeRoute::ZigbeeNodeRoute(quint16 destinationAddress, QObject *parent): + QObject(parent), + m_destinationAddress(destinationAddress) +{ + +} + +quint16 ZigbeeNodeRoute::destinationAddress() const +{ + return m_destinationAddress; +} + +quint16 ZigbeeNodeRoute::nextHopAddress() const +{ + return m_nextHopAddress; +} + +void ZigbeeNodeRoute::setNextHopAddress(quint16 nextHopAddress) +{ + if (m_nextHopAddress != nextHopAddress) { + m_nextHopAddress = nextHopAddress; + emit nextHopAddressChanged(); + } +} + +ZigbeeNode::ZigbeeNodeRouteStatus ZigbeeNodeRoute::status() const +{ + return m_status; +} + +void ZigbeeNodeRoute::setStatus(ZigbeeNode::ZigbeeNodeRouteStatus status) +{ + if (m_status != status) { + m_status = status; + emit statusChanged(); + } +} + +bool ZigbeeNodeRoute::memoryConstrained() const +{ + return m_memoryConstrained; +} + +void ZigbeeNodeRoute::setMemoryConstrained(bool memoryConstrained) +{ + if (m_memoryConstrained != memoryConstrained) { + m_memoryConstrained = memoryConstrained; + emit memoryConstrainedChanged(); + } +} + +bool ZigbeeNodeRoute::manyToOne() const +{ + return m_manyToOne; +} + +void ZigbeeNodeRoute::setManyToOne(bool manyToOne) +{ + if (m_manyToOne != manyToOne) { + m_manyToOne = manyToOne; + emit manyToOneChanged(); + } +} diff --git a/libnymea-app/zigbee/zigbeenode.h b/libnymea-app/zigbee/zigbeenode.h index f43c6203..a73f1386 100644 --- a/libnymea-app/zigbee/zigbeenode.h +++ b/libnymea-app/zigbee/zigbeenode.h @@ -37,6 +37,7 @@ #include class ZigbeeNodeNeighbor; +class ZigbeeNodeRoute; class ZigbeeNode : public QObject { @@ -54,6 +55,7 @@ class ZigbeeNode : public QObject Q_PROPERTY(uint lqi READ lqi WRITE setLqi NOTIFY lqiChanged) Q_PROPERTY(QDateTime lastSeen READ lastSeen WRITE setLastSeen NOTIFY lastSeenChanged) Q_PROPERTY(QList neighbors READ neighbors NOTIFY neighborsChanged) + Q_PROPERTY(QList routes READ routes NOTIFY routesChanged) public: enum ZigbeeNodeType { @@ -80,6 +82,15 @@ public: }; Q_ENUM(ZigbeeNodeRelationship) + enum ZigbeeNodeRouteStatus { + ZigbeeNodeRouteStatusActive, + ZigbeeNodeRouteStatusDiscoveryUnderway, + ZigbeeNodeRouteStatusDiscoveryFailed, + ZigbeeNodeRouteStatusInactive, + ZigbeeNodeRouteStatusValidationUnderway + }; + Q_ENUM(ZigbeeNodeRouteStatus) + explicit ZigbeeNode(const QUuid &networkUuid, const QString &ieeeAddress, QObject *parent = nullptr); QUuid networkUuid() const; @@ -119,6 +130,10 @@ public: void addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelationship relationship, quint8 lqi, quint8 depth, bool permitJoining); void commitNeighbors(QList toBeKept); + QList routes() const; + void addOrUpdateRoute(quint16 destinationAddress, quint16 nextHopAddress, ZigbeeNodeRouteStatus status, bool memoryConstrained, bool manyToOne); + void commitRoutes(QList toBeKept); + static ZigbeeNodeState stringToNodeState(const QString &nodeState); static ZigbeeNodeType stringToNodeType(const QString &nodeType); @@ -134,6 +149,7 @@ signals: void lqiChanged(uint lqi); void lastSeenChanged(const QDateTime &lastSeen); void neighborsChanged(); + void routesChanged(); private: QUuid m_networkUuid; @@ -150,6 +166,8 @@ private: QDateTime m_lastSeen; QList m_neighbors; bool m_neighborsDirty = false; + QList m_routes; + bool m_routesDirty = false; }; class ZigbeeNodeNeighbor: public QObject @@ -192,4 +210,44 @@ private: bool m_permitJoining = false; }; +class ZigbeeNodeRoute: public QObject +{ + Q_OBJECT + Q_PROPERTY(quint16 destinationAddress READ destinationAddress CONSTANT) + Q_PROPERTY(quint16 nextHopAddress READ nextHopAddress NOTIFY nextHopAddressChanged) + Q_PROPERTY(ZigbeeNode::ZigbeeNodeRouteStatus status READ status NOTIFY statusChanged) + Q_PROPERTY(bool memoryConstrained READ memoryConstrained NOTIFY memoryConstrainedChanged) + Q_PROPERTY(bool manyToOne READ manyToOne NOTIFY manyToOneChanged) + +public: + ZigbeeNodeRoute(quint16 destinationAddress, QObject *parent); + + quint16 destinationAddress() const; + + quint16 nextHopAddress() const; + void setNextHopAddress(quint16 nextHopAddress); + + ZigbeeNode::ZigbeeNodeRouteStatus status() const; + void setStatus(ZigbeeNode::ZigbeeNodeRouteStatus status); + + bool memoryConstrained() const; + void setMemoryConstrained(bool memoryConstrained); + + bool manyToOne() const; + void setManyToOne(bool manyToOne); + +signals: + void nextHopAddressChanged(); + void statusChanged(); + void memoryConstrainedChanged(); + void manyToOneChanged(); + +private: + quint16 m_destinationAddress; + quint16 m_nextHopAddress; + ZigbeeNode::ZigbeeNodeRouteStatus m_status = ZigbeeNode::ZigbeeNodeRouteStatusInactive; + bool m_memoryConstrained = false; + bool m_manyToOne = false; +}; + #endif // ZIGBEENODE_H diff --git a/nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml b/nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml index 6535e959..e88e14a9 100644 --- a/nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml +++ b/nymea-app/ui/system/zigbee/ZigbeeTopologyPage.qml @@ -16,7 +16,7 @@ Page { property ZigbeeManager zigbeeManager: null property ZigbeeNetwork network: null - readonly property int nodeDistance: 150 + readonly property int nodeDistance: Style.iconSize * 2 readonly property int nodeSize: Style.iconSize + Style.margins readonly property double scale: 1 @@ -26,7 +26,12 @@ Page { reload(); for (var i = 0; i < network.nodes.count; i++) { - network.nodes.get(i).neighborsChanged.connect(function() {root.reload()}) + network.nodes.get(i).neighborsChanged.connect(root.reload) + } + } + Component.onDestruction: { + for (var i = 0; i < network.nodes.count; i++) { + network.nodes.get(i).neighborsChanged.disconnect(root.reload) } } @@ -34,12 +39,12 @@ Page { target: root.network.nodes onNodeAdded: { root.reload() - node.neighborsChanged.connect(function() {root.reload()}); + node.neighborsChanged.connect(root.reload); } } function generateNodeList() { - d.nodeItems = [] + var nodeItems = [] var coordinator = {} var routers = [] var endDevices = [] @@ -60,19 +65,20 @@ Page { var startAngle = -90 - var x = root.nodeDistance * Math.cos(startAngle * Math.PI / 180) - var y = root.nodeDistance * Math.sin(startAngle * Math.PI / 180) - d.nodeItems.push(createNodeItem(coordinator, x, y, startAngle)) + var routersCircumference = Math.max(5, routers.length) * (root.nodeSize + root.nodeDistance) * root.scale + var distanceFromCenter = routersCircumference / 2 / Math.PI + + routers.unshift(coordinator) var handledEndDevices = [] - var angle = 360 / (routers.length + 1); + var angle = 360 / routers.length; for (var i = 0; i < routers.length; i++) { var router = routers[i] - var nodeAngle = startAngle + angle * (i + 1); - var x = root.nodeDistance * Math.cos(nodeAngle * Math.PI / 180) - var y = root.nodeDistance * Math.sin(nodeAngle * Math.PI / 180) - d.nodeItems.push(createNodeItem(routers[i], x, y, nodeAngle)); + var nodeAngle = startAngle + angle * i; + var x = distanceFromCenter * Math.cos(nodeAngle * Math.PI / 180) + var y = distanceFromCenter * Math.sin(nodeAngle * Math.PI / 180) + nodeItems.push(createNodeItem(routers[i], x, y, nodeAngle)); var neighborCounter = 0; @@ -88,13 +94,12 @@ Page { } handledEndDevices.push(neighborNode.networkAddress) - var neighborAngle = nodeAngle + neighborCounter * 8 - var neighborDistance = root.nodeDistance * 1.5 * root.scale + neighborCounter * root.nodeSize * 0.75 * root.scale + var neighborDistance = (distanceFromCenter + root.nodeDistance + root.nodeSize) * root.scale + neighborCounter * root.nodeDistance * .5 * root.scale x = neighborDistance * Math.cos(neighborAngle * Math.PI / 180) y = neighborDistance * Math.sin(neighborAngle * Math.PI / 180) - d.nodeItems.push(createNodeItem(neighborNode, x, y, angle)) + nodeItems.push(createNodeItem(neighborNode, x, y, angle)) neighborCounter++ } @@ -120,10 +125,10 @@ Page { 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)) + var y = Style.margins + cellHeight * row + root.nodeSize - canvas.height / 2 + nodeItems.push(createNodeItem(node, x, y, 0)) } + d.nodeItems = nodeItems } @@ -173,16 +178,13 @@ Page { 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 + print("repainting", flickable.contentX, flickable.contentY) + if (flickable.contentX == 0 && flickable.contentY == 0) { + flickable.contentX = (flickable.contentWidth - flickable.width) / 2 + flickable.contentY = (flickable.contentHeight - flickable.height) / 2 + } } QtObject { @@ -190,6 +192,17 @@ Page { property var nodeItems: [] property int selectedNodeAddress: -1 + readonly property var selectedNodeItem: { + print("selected", selectedNodeAddress) + for (var i = 0; i < nodeItems.length; i++) { + print("checking", nodeItems[i].node.networkAddress) + if (nodeItems[i].node.networkAddress === selectedNodeAddress) { + return nodeItems[i] + } + } + return null + } + readonly property ZigbeeNode selectedNode: selectedNodeAddress >= 0 ? network.nodes.getNodeByNetworkAddress(selectedNodeAddress) : null property int minX: 0 @@ -249,11 +262,69 @@ Page { for (var i = 0; i < d.nodeItems.length; i++) { paintEdges(ctx, d.nodeItems[i], true) } + if (d.selectedNodeItem) { + paintRoute(ctx, d.selectedNodeItem) + } for (var i = 0; i < d.nodeItems.length; i++) { paintNode(ctx, d.nodeItems[i]) } } + function paintRoute(ctx, nodeItem) { + var node = nodeItem.node + var nextHop = -1 + if (node.type === ZigbeeNode.ZigbeeNodeTypeRouter) { + for (var i = 0; i < node.routes.length; i++) { + if (node.routes[i].destinationAddress === 0) { + nextHop = node.routes[i].nextHopAddress + break; + } + } + } else if (node.type === ZigbeeNode.ZigbeeNodeTypeEndDevice) { + for (var i = 0; i < network.nodes.count; i++) { + for (var j = 0; j < network.nodes.get(i).neighbors.length; j++) { + if (network.nodes.get(i).neighbors[j].networkAddress === node.networkAddress) { + nextHop = network.nodes.get(i).networkAddress + break; + } + } + } + } + + print("next hop", nextHop) + if (nextHop == -1) { + return; + } + var toNodeItem = null + for (var i = 0; i < d.nodeItems.length; i++) { + if (d.nodeItems[i].node.networkAddress == nextHop) { + toNodeItem = d.nodeItems[i] + break; + } + } + if (!toNodeItem) { + return; + } + + ctx.save() + + ctx.lineWidth = 2 + ctx.setLineDash([4, 4]) + ctx.strokeStyle = Style.blue + + ctx.beginPath(); + ctx.moveTo(scale * nodeItem.x, scale * nodeItem.y) + ctx.lineTo(scale * toNodeItem.x, scale * toNodeItem.y) + + ctx.stroke(); + ctx.closePath() + ctx.setLineDash([1,0]) + ctx.restore(); + + paintRoute(ctx, toNodeItem) + + } + function paintEdges(ctx, nodeItem, selected) { for (var i = 0; i < nodeItem.node.neighbors.length; i++) { var neighbor = nodeItem.node.neighbors[i] @@ -346,7 +417,11 @@ Page { 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.selectedNodeAddress = nodeItem.node.networkAddress; - print("sleecting", nodeItem.node.networkAddress) + print("selecting", nodeItem.node.networkAddress) + for (var j = 0; j < nodeItem.node.routes.length; j++) { + var route = nodeItem.node.routes[j] + print("route:", route.destinationAddress, "via", route.nextHopAddress) + } } } @@ -418,7 +493,7 @@ Page { contentItem: ListView { id: tableListView - spacing: app.margins +// spacing: app.margins implicitHeight: Math.min(root.height / 4, count * Style.smallIconSize) clip: true model: d.selectedNode ? d.selectedNode.neighbors.length : 0