Add ZigBee routing information to topology map

pull/875/head
Michael Zanetti 2022-09-07 12:20:03 +02:00
parent a12ea9775c
commit 4a06e05115
4 changed files with 299 additions and 28 deletions

View File

@ -424,4 +424,18 @@ void ZigbeeManager::updateNodeProperties(ZigbeeNode *node, const QVariantMap &no
neighbors.append(networkAddress);
}
node->commitNeighbors(neighbors);
QList<quint16> 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>();
ZigbeeNode::ZigbeeNodeRouteStatus routeStatus = static_cast<ZigbeeNode::ZigbeeNodeRouteStatus>(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);
}

View File

@ -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<quint16> toBeKept)
}
}
QList<ZigbeeNodeRoute *> 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<quint16> toBeKept)
{
QMutableListIterator<ZigbeeNodeRoute*> 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();
}
}

View File

@ -37,6 +37,7 @@
#include <QVariantMap>
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<ZigbeeNodeNeighbor*> neighbors READ neighbors NOTIFY neighborsChanged)
Q_PROPERTY(QList<ZigbeeNodeRoute*> 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<quint16> toBeKept);
QList<ZigbeeNodeRoute*> routes() const;
void addOrUpdateRoute(quint16 destinationAddress, quint16 nextHopAddress, ZigbeeNodeRouteStatus status, bool memoryConstrained, bool manyToOne);
void commitRoutes(QList<quint16> 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<ZigbeeNodeNeighbor*> m_neighbors;
bool m_neighborsDirty = false;
QList<ZigbeeNodeRoute*> 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

View File

@ -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,23 +178,31 @@ 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()
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 {
id: d
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