Some improvements in the zigbee topology map

This commit is contained in:
Michael Zanetti 2022-09-05 17:36:27 +02:00
parent 160cd0c4de
commit 76f52acb33
10 changed files with 139 additions and 57 deletions

View File

@ -151,6 +151,11 @@ int ZigbeeManager::removeNode(const QUuid &networkUuid, const QString &ieeeAddre
return m_engine->jsonRpcClient()->sendCommand("Zigbee.RemoveNode", params, this, "removeNodeResponse"); 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() void ZigbeeManager::init()
{ {
m_fetchingData = true; m_fetchingData = true;
@ -378,7 +383,8 @@ void ZigbeeManager::fillNetworkData(ZigbeeNetwork *network, const QVariantMap &n
network->setPermitJoiningDuration(networkMap.value("permitJoiningDuration").toUInt()); network->setPermitJoiningDuration(networkMap.value("permitJoiningDuration").toUInt());
network->setPermitJoiningRemaining(networkMap.value("permitJoiningRemaining").toUInt()); network->setPermitJoiningRemaining(networkMap.value("permitJoiningRemaining").toUInt());
network->setBackend(networkMap.value("backend").toString()); network->setBackend(networkMap.value("backend").toString());
network->setNetworkState(ZigbeeNetwork::stringToZigbeeNetworkState(networkMap.value("networkState").toString())); QMetaEnum networkStateEnum = QMetaEnum::fromType<ZigbeeNetwork::ZigbeeNetworkState>();
network->setNetworkState(static_cast<ZigbeeNetwork::ZigbeeNetworkState>(networkStateEnum.keyToValue(networkMap.value("networkState").toByteArray())));
} }
void ZigbeeManager::addOrUpdateNode(ZigbeeNetwork *network, const QVariantMap &nodeMap) 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<ZigbeeNode::ZigbeeNodeRelationship>(relationshipEnum.keyToValue(neighborMap.value("relationship").toByteArray().data())); ZigbeeNode::ZigbeeNodeRelationship relationship = static_cast<ZigbeeNode::ZigbeeNodeRelationship>(relationshipEnum.keyToValue(neighborMap.value("relationship").toByteArray().data()));
quint8 lqi = neighborMap.value("lqi").toUInt(); quint8 lqi = neighborMap.value("lqi").toUInt();
quint8 depth = neighborMap.value("depth").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); neighbors.append(networkAddress);
} }
node->commitNeighbors(neighbors); node->commitNeighbors(neighbors);

View File

@ -95,6 +95,7 @@ public:
Q_INVOKABLE void factoryResetNetwork(const QUuid &networkUuid); Q_INVOKABLE void factoryResetNetwork(const QUuid &networkUuid);
Q_INVOKABLE void getNodes(const QUuid &networkUuid); Q_INVOKABLE void getNodes(const QUuid &networkUuid);
Q_INVOKABLE int removeNode(const QUuid &networkUuid, const QString &ieeeAddress); Q_INVOKABLE int removeNode(const QUuid &networkUuid, const QString &ieeeAddress);
Q_INVOKABLE void refreshNeighborTables(const QUuid &networkUuid);
signals: signals:
void engineChanged(); void engineChanged();

View File

@ -253,22 +253,3 @@ void ZigbeeNetwork::setNetworkState(ZigbeeNetwork::ZigbeeNetworkState networkSta
m_networkState = networkState; m_networkState = networkState;
emit networkStateChanged(); 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;
}
}

View File

@ -115,8 +115,6 @@ public:
ZigbeeNetworkState networkState() const; ZigbeeNetworkState networkState() const;
void setNetworkState(ZigbeeNetworkState networkState); void setNetworkState(ZigbeeNetworkState networkState);
static ZigbeeNetworkState stringToZigbeeNetworkState(const QString &networkStateString);
signals: signals:
void networkUuidChanged(); void networkUuidChanged();
void enabledChanged(); void enabledChanged();

View File

@ -194,7 +194,7 @@ QList<ZigbeeNodeNeighbor *> ZigbeeNode::neighbors() const
return m_neighbors; 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) { foreach (ZigbeeNodeNeighbor *neighbor, m_neighbors) {
if (neighbor->networkAddress() == networkAddress) { if (neighbor->networkAddress() == networkAddress) {
@ -206,6 +206,10 @@ void ZigbeeNode::addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelations
neighbor->setLqi(lqi); neighbor->setLqi(lqi);
m_neighborsDirty = true; m_neighborsDirty = true;
} }
if (neighbor->permitJoining() != permitJoining) {
neighbor->setPermitJoining(permitJoining);
m_neighborsDirty = true;
}
return; return;
} }
} }
@ -214,6 +218,7 @@ void ZigbeeNode::addOrUpdateNeighbor(quint16 networkAddress, ZigbeeNodeRelations
neighbor->setLqi(lqi); neighbor->setLqi(lqi);
neighbor->setDepth(depth); neighbor->setDepth(depth);
m_neighbors.append(neighbor); m_neighbors.append(neighbor);
m_neighborsDirty = true;
} }
void ZigbeeNode::commitNeighbors(QList<quint16> toBeKept) void ZigbeeNode::commitNeighbors(QList<quint16> toBeKept)
@ -308,3 +313,16 @@ void ZigbeeNodeNeighbor::setDepth(quint8 depth)
emit depthChanged(); emit depthChanged();
} }
} }
bool ZigbeeNodeNeighbor::permitJoining() const
{
return m_permitJoining;
}
void ZigbeeNodeNeighbor::setPermitJoining(bool permitJoining)
{
if (m_permitJoining != permitJoining) {
m_permitJoining = permitJoining;
emit permitJoiningChanged();
}
}

View File

@ -116,7 +116,7 @@ public:
void setLastSeen(const QDateTime &lastSeen); void setLastSeen(const QDateTime &lastSeen);
QList<ZigbeeNodeNeighbor*> neighbors() const; QList<ZigbeeNodeNeighbor*> 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<quint16> toBeKept); void commitNeighbors(QList<quint16> toBeKept);
static ZigbeeNodeState stringToNodeState(const QString &nodeState); 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(ZigbeeNode::ZigbeeNodeRelationship relationship READ relationship NOTIFY relationshipChanged)
Q_PROPERTY(quint8 lqi READ lqi NOTIFY lqiChanged) Q_PROPERTY(quint8 lqi READ lqi NOTIFY lqiChanged)
Q_PROPERTY(quint8 depth READ depth NOTIFY depthChanged) Q_PROPERTY(quint8 depth READ depth NOTIFY depthChanged)
Q_PROPERTY(bool permitJoining READ permitJoining NOTIFY permitJoiningChanged)
public: public:
ZigbeeNodeNeighbor(quint16 networkAddress, QObject *parent); ZigbeeNodeNeighbor(quint16 networkAddress, QObject *parent);
@ -174,16 +175,21 @@ public:
quint8 depth() const; quint8 depth() const;
void setDepth(quint8 depth); void setDepth(quint8 depth);
bool permitJoining() const;
void setPermitJoining(bool permitJoining);
signals: signals:
void relationshipChanged(); void relationshipChanged();
void lqiChanged(); void lqiChanged();
void depthChanged(); void depthChanged();
void permitJoiningChanged();
private: private:
quint16 m_networkAddress; quint16 m_networkAddress;
ZigbeeNode::ZigbeeNodeRelationship m_relationship; ZigbeeNode::ZigbeeNodeRelationship m_relationship;
quint8 m_lqi = 0; quint8 m_lqi = 0;
quint8 m_depth = 0; quint8 m_depth = 0;
bool m_permitJoining = false;
}; };
#endif // ZIGBEENODE_H #endif // ZIGBEENODE_H

View File

@ -149,6 +149,8 @@ void ZigbeeNodes::addNode(ZigbeeNode *node)
endInsertRows(); endInsertRows();
emit countChanged(); emit countChanged();
emit nodeAdded(node);
} }
void ZigbeeNodes::removeNode(const QString &ieeeAddress) void ZigbeeNodes::removeNode(const QString &ieeeAddress)
@ -159,6 +161,7 @@ void ZigbeeNodes::removeNode(const QString &ieeeAddress)
m_nodes.takeAt(i)->deleteLater(); m_nodes.takeAt(i)->deleteLater();
endRemoveRows(); endRemoveRows();
emit countChanged(); emit countChanged();
emit nodeRemoved(ieeeAddress);
return; return;
} }
} }

View File

@ -76,6 +76,8 @@ public:
signals: signals:
void countChanged(); void countChanged();
void nodeAdded(ZigbeeNode *node);
void nodeRemoved(const QString &ieeeAddress);
protected: protected:
QList<ZigbeeNode *> m_nodes; QList<ZigbeeNode *> m_nodes;

View File

@ -92,7 +92,9 @@ SettingsPageBase {
NymeaItemDelegate { NymeaItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Network topology") 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 { SettingsPageSectionHeader {

View File

@ -13,6 +13,7 @@ Page {
onBackPressed: pageStack.pop() onBackPressed: pageStack.pop()
} }
property ZigbeeManager zigbeeManager: null
property ZigbeeNetwork network: null property ZigbeeNetwork network: null
readonly property int nodeDistance: 150 readonly property int nodeDistance: 150
@ -21,13 +22,24 @@ Page {
Component.onCompleted: { Component.onCompleted: {
generateNodeList() zigbeeManager.refreshNeighborTables(network.networkUuid)
canvas.requestPaint()
flickable.contentX = (flickable.contentWidth - flickable.width) / 2 reload();
flickable.contentY = (flickable.contentHeight - flickable.height) / 2 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() { function generateNodeList() {
d.nodeItems = []
var coordinator = {} var coordinator = {}
var routers = [] var routers = []
var endDevices = [] 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 thing: thing
} }
print("creared node", thing ? thing.name : "", " at", x, y) // print("creared node", thing ? thing.name : "", " at", x, y)
d.adjustSize(x, y) d.adjustSize(x, y)
return nodeItem 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 { QtObject {
id: d id: d
property var nodeTree: ({})
property var handledNodes: []
property var nodeItems: [] 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 minX: 0
property int minY: 0 property int minY: 0
@ -180,7 +232,7 @@ Page {
clip: true clip: true
onPaint: { onPaint: {
print("**** height:", canvas.height, "width", canvas.width) // print("**** height:", canvas.height, "width", canvas.width)
var ctx = getContext("2d"); var ctx = getContext("2d");
ctx.reset(); ctx.reset();
@ -209,7 +261,7 @@ Page {
for (var k = 0; k < d.nodeItems.length; k++) { for (var k = 0; k < d.nodeItems.length; k++) {
if (d.nodeItems[k].node.networkAddress == neighbor.networkAddress) { if (d.nodeItems[k].node.networkAddress == neighbor.networkAddress) {
var toNodeItem = d.nodeItems[k] 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) { if (selected) {
paintEdge(ctx, nodeItem, d.nodeItems[k], neighbor.lqi, true) paintEdge(ctx, nodeItem, d.nodeItems[k], neighbor.lqi, true)
} }
@ -227,11 +279,11 @@ Page {
function paintNode(ctx, nodeItem) { function paintNode(ctx, nodeItem) {
ctx.save() ctx.save()
ctx.beginPath(); ctx.beginPath();
ctx.fillStyle = Style.tileBackgroundColor ctx.fillStyle = nodeItem.node.networkAddress === d.selectedNodeAddress ? Style.tileOverlayColor : Style.tileBackgroundColor
ctx.strokeStyle = nodeItem === d.selectedNodeItem ? Style.accentColor : Style.foregroundColor 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.arc(root.scale * nodeItem.x, root.scale * nodeItem.y, root.scale * root.nodeSize / 2, 0, 2 * Math.PI);
ctx.fill(); ctx.fill();
// ctx.stroke(); // ctx.stroke();
ctx.fillStyle = Style.foregroundColor ctx.fillStyle = Style.foregroundColor
ctx.font = "" + Style.extraSmallFont.pixelSize + "px Ubuntu"; ctx.font = "" + Style.extraSmallFont.pixelSize + "px Ubuntu";
var text = "" var text = ""
@ -267,7 +319,7 @@ Page {
ctx.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, 1) ctx.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, 1)
} else { } else {
ctx.lineWidth = 1 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.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, alpha)
} }
ctx.beginPath(); ctx.beginPath();
@ -287,13 +339,13 @@ Page {
print("clicked:", mouseX, mouseY) print("clicked:", mouseX, mouseY)
var translatedMouseX = mouseX - canvas.width / 2 var translatedMouseX = mouseX - canvas.width / 2
var translatedMouseY = mouseY - canvas.height / 2 var translatedMouseY = mouseY - canvas.height / 2
d.selectedNodeItem = null d.selectedNodeAddress = -1
for (var i = 0; i < d.nodeItems.length; i++) { for (var i = 0; i < d.nodeItems.length; i++) {
var nodeItem = d.nodeItems[i] var nodeItem = d.nodeItems[i]
// print("nodeItem at:", root.scale * nodeItem.x, root.scale * nodeItem.y) // print("nodeItem at:", root.scale * nodeItem.x, root.scale * nodeItem.y)
if (Math.abs(root.scale * nodeItem.x - translatedMouseX) < (root.scale * root.nodeSize / 2) 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)) { && 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) print("sleecting", nodeItem.node.networkAddress)
} }
} }
@ -302,33 +354,37 @@ Page {
} }
} }
} }
} }
BigTile { BigTile {
visible: d.selectedNodeItem visible: d.selectedNodeAddress >= 0
anchors { anchors {
top: parent.top top: parent.top
right: parent.right right: parent.right
margins: Style.margins margins: Style.margins
} }
width: 200 width: 260
header: RowLayout { header: RowLayout {
width: parent.width - Style.smallMargins width: parent.width - Style.smallMargins
spacing: Style.smallMargins spacing: Style.smallMargins
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
elide: Text.ElideRight 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 ? Configuration.systemName
: d.selectedNodeItem.thing : selectedThingsProxy.count > 0
? d.selectedNodeItem.thing.name ? selectedThingsProxy.get(0).name
: d.selectedNodeItem.node.model : network.nodes.getNodeByNetworkAddress(d.selectedNode).model
} }
ColorIcon { ColorIcon {
size: Style.smallIconSize size: Style.smallIconSize
@ -354,18 +410,23 @@ Page {
size: Style.smallIconSize size: Style.smallIconSize
name: "/ui/images/things.svg" name: "/ui/images/things.svg"
} }
ColorIcon {
size: Style.smallIconSize
name: "/ui/images/add.svg"
}
} }
contentItem: ListView { contentItem: ListView {
id: tableListView
spacing: app.margins spacing: app.margins
implicitHeight: Math.min(root.height / 4, count * Style.smallIconSize) implicitHeight: Math.min(root.height / 4, count * Style.smallIconSize)
clip: true clip: true
model: d.selectedNodeItem ? d.selectedNodeItem.node.neighbors.length : 0 model: d.selectedNode ? d.selectedNode.neighbors.length : 0
delegate: RowLayout { delegate: RowLayout {
id: neighborTableDelegate id: neighborTableDelegate
width: parent.width width: tableListView.width
property ZigbeeNodeNeighbor neighbor: d.selectedNodeItem.node.neighbors[index] property ZigbeeNodeNeighbor neighbor: d.selectedNode.neighbors[index]
property ZigbeeNode neighborNode: root.network.nodes.getNodeByNetworkAddress(neighbor.networkAddress) property ZigbeeNode neighborNode: root.network.nodes.getNodeByNetworkAddress(neighbor.networkAddress)
property Thing neighborNodeThing: { property Thing neighborNodeThing: {
for (var i = 0; i < engine.thingManager.things.count; i++) { for (var i = 0; i < engine.thingManager.things.count; i++) {
@ -401,9 +462,12 @@ Page {
text: neighborTableDelegate.neighbor.depth text: neighborTableDelegate.neighbor.depth
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
} }
ColorIcon {
size: Style.smallIconSize
name: "add"
opacity: neighborTableDelegate.neighbor.permitJoining ? 1 : 0
}
} }
} }
} }
} }