Optimizations and improvements in the Zigbee topology map

This commit is contained in:
Michael Zanetti 2022-09-11 01:39:55 +02:00
parent 5d053592a3
commit 7fae0763b3

View File

@ -27,6 +27,8 @@ Page {
readonly property int nodeDistance: Style.iconSize * 2 readonly property int nodeDistance: Style.iconSize * 2
readonly property int nodeSize: Style.iconSize + Style.margins readonly property int nodeSize: Style.iconSize + Style.margins
property double scale: 1 property double scale: 1
readonly property double minScale: 0.5
readonly property double maxScale: 1.5
Component.onCompleted: { Component.onCompleted: {
@ -51,8 +53,59 @@ Page {
} }
} }
function reload() {
print("Reloading network map")
generateNodeList();
generateEdges();
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
}
}
function reloadDelayed() {
reloadTimer.start()
}
Timer {
id: reloadTimer
interval: 500
repeat: false
running: false
onTriggered: reload()
}
QtObject {
id: d
property var nodeItems: ({})
property var edges: ({})
property int selectedNodeAddress: -1
readonly property var selectedNodeItem: nodeItems[selectedNodeAddress]
readonly property ZigbeeNode selectedNode: selectedNodeAddress >= 0 ? network.nodes.getNodeByNetworkAddress(selectedNodeAddress) : null
property int minX: 0
property int minY: 0
property int maxX: 0
property int maxY: 0
property int size: 0
function adjustSize(x, y) {
minX = Math.min(minX, x)
minY = Math.min(minY, y)
maxX = Math.max(maxX, x)
maxY = Math.max(maxY, y)
var minWidth = Math.max(-minX, maxX) * 2 * root.maxScale
var minHeight = Math.max(-minY, maxY) * 2 * root.maxScale
size = Math.max(minWidth, minHeight) + root.nodeSize + Style.hugeMargins * 2
}
}
function generateNodeList() { function generateNodeList() {
var nodeItems = [] var nodeItems = ({})
var coordinator = {} var coordinator = {}
var routers = [] var routers = []
var endDevices = [] var endDevices = []
@ -86,7 +139,7 @@ Page {
var nodeAngle = startAngle + angle * i; var nodeAngle = startAngle + angle * i;
var x = distanceFromCenter * Math.cos(nodeAngle * Math.PI / 180) var x = distanceFromCenter * Math.cos(nodeAngle * Math.PI / 180)
var y = distanceFromCenter * Math.sin(nodeAngle * Math.PI / 180) var y = distanceFromCenter * Math.sin(nodeAngle * Math.PI / 180)
nodeItems.push(createNodeItem(routers[i], x, y, nodeAngle)); nodeItems[router.networkAddress] = createNodeItem(router, x, y, nodeAngle);
var neighborCounter = 0; var neighborCounter = 0;
@ -107,7 +160,7 @@ Page {
x = neighborDistance * Math.cos(neighborAngle * Math.PI / 180) x = neighborDistance * Math.cos(neighborAngle * Math.PI / 180)
y = neighborDistance * Math.sin(neighborAngle * Math.PI / 180) y = neighborDistance * Math.sin(neighborAngle * Math.PI / 180)
nodeItems.push(createNodeItem(neighborNode, x, y, angle)) nodeItems[neighborNode.networkAddress] = createNodeItem(neighborNode, x, y, angle)
neighborCounter++ neighborCounter++
} }
@ -132,43 +185,43 @@ Page {
var node = unconnectedNodes[i] var node = unconnectedNodes[i]
var column = i % columns; var column = i % columns;
var row = Math.floor(i / columns) var row = Math.floor(i / columns)
var x = cellWidth * column + cellWidth / 2 - rowWidth / 2 var x = nodeItems[coordinator.networkAddress].x + (column + 0.5) * cellWidth - rowWidth / 2
var y = nodeItems[0].y - root.nodeSize * (5 + rows) + cellHeight * row var y = nodeItems[coordinator.networkAddress].y - root.nodeSize * (5 + rows) + cellHeight * row
nodeItems.push(createNodeItem(node, x, y, 0)) nodeItems[node.networkAddress] = createNodeItem(node, x, y, 0)
} }
for (var i = 0; i < d.nodeItems.length; i++) { for (var networkAddress in d.nodeItems) {
if (nodeItems.indexOf(d.nodeItems[i]) < 0) { if (!nodeItems.hasOwnProperty(networkAddress)) {
d.nodeItems[i].image.destroy(); d.nodeItems[networkAddress].image.destroy();
} }
} }
d.nodeItems = nodeItems d.nodeItems = nodeItems
} }
function createNodeItem(node, x, y, angle) { function createNodeItem(node, x, y, angle) {
d.adjustSize(x, y) d.adjustSize(x, y)
for (var i = 0; i < d.nodeItems.length; i++) { if (d.nodeItems.hasOwnProperty(node.networkAddress)) {
if (d.nodeItems[i].node == node) { d.nodeItems[node.networkAddress].x = x;
d.nodeItems[i].x = x; d.nodeItems[node.networkAddress].y = y;
d.nodeItems[i].y = y; d.nodeItems[node.networkAddress].image.x = Qt.binding(function() { return x * root.scale + (canvas.width - Style.iconSize * root.scale) / 2});
d.nodeItems[i].angle = angle; d.nodeItems[node.networkAddress].image.y = Qt.binding(function() { return y * root.scale + (canvas.height - Style.iconSize * root.scale) / 2});
return d.nodeItems[i]
} d.nodeItems[node.networkAddress].angle = angle;
return d.nodeItems[node.networkAddress]
} }
var icon = "/ui/images/zigbee.svg" var icon = "/ui/images/zigbee.svg"
var thing = null var thing = null
if (node.networkAddress == 0) { if (node.networkAddress === 0) {
icon = "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle) icon = "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
} else { } else {
for (var i = 0; i < engine.thingManager.things.count; i++) { for (var i = 0; i < engine.thingManager.things.count; i++) {
var t = engine.thingManager.things.get(i) var t = engine.thingManager.things.get(i)
// print("checking thing", t.name) // print("checking thing", t.name)
var param = t.paramByName("ieeeAddress") var param = t.paramByName("ieeeAddress")
if (param && param.value == node.ieeeAddress) { if (param && param.value === node.ieeeAddress) {
thing = t; thing = t;
break; break;
} }
@ -197,60 +250,43 @@ Page {
return nodeItem return nodeItem
} }
function reload() { function generateEdges() {
print("Reloading network map") var edges = ({})
generateNodeList(); for (var networkAddress in d.nodeItems) {
canvas.requestPaint() var fromNodeItem = d.nodeItems[networkAddress]
// print("repainting", flickable.contentX, flickable.contentY) var fromAddress = fromNodeItem.node.networkAddress
if (flickable.contentX == 0 && flickable.contentY == 0) { for (var i = 0; i < fromNodeItem.node.neighbors.length; i++) {
flickable.contentX = (flickable.contentWidth - flickable.width) / 2 var neighbor = fromNodeItem.node.neighbors[i]
flickable.contentY = (flickable.contentHeight - flickable.height) / 2 // print("have neighbor", neighbor.networkAddress)
} var toAddress = neighbor.networkAddress
} if (!d.nodeItems.hasOwnProperty(toAddress)) {
continue;
}
var toNodeItem = d.nodeItems[toAddress]
function reloadDelayed() { var edgeKey = fromAddress < toAddress ? fromAddress + "+" + toAddress : toAddress + "+" + fromAddress;
reloadTimer.start() if (edges.hasOwnProperty(edgeKey)) {
} continue;
}
Timer { var fromLqi = neighbor.lqi
id: reloadTimer var toLqi = fromLqi
interval: 500 for (var j = 0; j < toNodeItem.node.neighbors.length; j++) {
repeat: false if (toNodeItem.node.neighbors[j].networkAddress === fromAddress) {
running: false toLqi = toNodeItem.node.neighbors[j].lqi
onTriggered: reload() break;
} }
}
QtObject { edges[edgeKey] = {
id: d fromNodeItem: fromNodeItem,
property var nodeItems: [] toNodeItem: toNodeItem,
fromLqi: fromLqi,
property int selectedNodeAddress: -1 toLqi: toLqi
readonly property var selectedNodeItem: {
for (var i = 0; i < nodeItems.length; i++) {
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
property int minY: 0
property int maxX: 0
property int maxY: 0
property int size: 0
function adjustSize(x, y) {
minX = Math.min(minX, x)
minY = Math.min(minY, y)
maxX = Math.max(maxX, x)
maxY = Math.max(maxY, y)
var minWidth = Math.max(-minX, maxX) * 2
var minHeight = Math.max(-minY, maxY) * 2
size = Math.max(minWidth, minHeight) + root.nodeSize + Style.hugeMargins * 2
} }
d.edges = edges
} }
Component { Component {
@ -274,7 +310,6 @@ Page {
clip: true clip: true
onPaint: { onPaint: {
// print("**** height:", canvas.height, "width", canvas.width)
var ctx = getContext("2d"); var ctx = getContext("2d");
ctx.reset(); ctx.reset();
@ -285,17 +320,15 @@ Page {
} }
function paintNodeList(ctx) { function paintNodeList(ctx) {
for (var i = 0; i < d.nodeItems.length; i++) { for (var edgeKey in d.edges) {
paintEdges(ctx, d.nodeItems[i], false) paintEdge(ctx, d.edges[edgeKey], false)
}
for (var i = 0; i < d.nodeItems.length; i++) {
paintEdges(ctx, d.nodeItems[i], true)
} }
if (d.selectedNodeItem) { if (d.selectedNodeItem) {
paintRoutes(ctx, d.selectedNodeItem) paintRoutes(ctx, d.selectedNodeItem)
} }
for (var i = 0; i < d.nodeItems.length; i++) { for (var networkAddress in d.nodeItems) {
paintNode(ctx, d.nodeItems[i]) paintNode(ctx, d.nodeItems[networkAddress])
} }
} }
@ -321,63 +354,20 @@ Page {
} }
} }
function paintRoute(ctx, nodeItem, nextHopAddress) { function paintRoute(ctx, fromNodeItem, nextHopAddress) {
// print("next hop", nextHopAddress) if (!d.nodeItems.hasOwnProperty(nextHopAddress)) {
if (nextHopAddress == -1) {
return;
}
var toNodeItem = null
for (var i = 0; i < d.nodeItems.length; i++) {
if (d.nodeItems[i].node.networkAddress == nextHopAddress) {
toNodeItem = d.nodeItems[i]
break;
}
}
if (!toNodeItem) {
return; return;
} }
var toNodeItem = d.nodeItems[nextHopAddress]
var fromAddress = fromNodeItem.node.networkAddress
ctx.save() var edgeKey = fromAddress < nextHopAddress ? fromAddress + "+" + nextHopAddress : nextHopAddress + "+" + fromAddress
paintEdge(ctx, d.edges[edgeKey], true)
ctx.lineWidth = 2
ctx.setLineDash([4, 4])
ctx.strokeStyle = Style.blue
ctx.beginPath();
ctx.moveTo(root.scale * nodeItem.x, root.scale * nodeItem.y)
ctx.lineTo(root.scale * toNodeItem.x, root.scale * toNodeItem.y)
ctx.stroke();
ctx.closePath()
ctx.setLineDash([1,0])
ctx.restore();
paintRoutes(ctx, toNodeItem) paintRoutes(ctx, toNodeItem)
} }
function paintEdges(ctx, nodeItem, selected) {
for (var i = 0; i < nodeItem.node.neighbors.length; i++) {
var neighbor = nodeItem.node.neighbors[i]
// print("ege from", nodeItem.node.networkAddress, "to", neighbor, "LQI", neighbor.lqi, "depth:", neighbor.depth)
for (var k = 0; k < d.nodeItems.length; k++) {
if (d.nodeItems[k].node.networkAddress == neighbor.networkAddress) {
var toNodeItem = d.nodeItems[k]
if (nodeItem.node.networkAddress === d.selectedNodeAddress || toNodeItem.node.networkAddress === d.selectedNodeAddress) {
if (selected) {
paintEdge(ctx, nodeItem, d.nodeItems[k], neighbor.lqi, true)
}
} else {
if (!selected) {
paintEdge(ctx, nodeItem, d.nodeItems[k], neighbor.lqi, false)
}
}
continue
}
}
}
}
function paintNode(ctx, nodeItem) { function paintNode(ctx, nodeItem) {
ctx.save() ctx.save()
ctx.beginPath(); ctx.beginPath();
@ -400,40 +390,52 @@ Page {
var textSize = ctx.measureText(text) var textSize = ctx.measureText(text)
// ctx.fillText(text, scale * (nodeItem.x ), scale * (nodeItem.y )) // ctx.fillText(text, scale * (nodeItem.x ), scale * (nodeItem.y ))
ctx.fillText(text, nodeItem.x * root.scale - (root.scale * textSize.width / 2), nodeItem.y * root.scale + (root.scale * root.nodeSize / 2 + Style.extraSmallFont.pixelSize)) ctx.fillText(text, nodeItem.x * root.scale - (textSize.width / 2), nodeItem.y * root.scale + (root.scale * root.nodeSize / 2 + Style.extraSmallFont.pixelSize))
ctx.closePath(); ctx.closePath();
ctx.restore(); ctx.restore();
} }
function paintEdge(ctx, fromNodeItem, toNodeItem, lqi, selected) { function paintEdge(ctx, edge, forceSelection) {
ctx.save() ctx.save();
var percent = lqi / 255; var haveSelection = d.selectedNodeItem !== undefined
var goodColor = Style.green var fromSelected = edge.fromNodeItem === d.selectedNodeItem
var badColor = Style.red var toSelected = edge.toNodeItem === d.selectedNodeItem
var resultRed = badColor.r + percent * (goodColor.r - badColor.r); var fromPercent = 1.0 * edge.fromLqi / 255;
var resultGreen = badColor.g + percent * (goodColor.g - badColor.g); var fromColor = Qt.rgba(
var resultBlue = badColor.b + percent * (goodColor.b - badColor.b); Style.red.r + fromPercent * (Style.green.r - Style.red.r),
Style.red.g + fromPercent * (Style.green.g - Style.red.g),
Style.red.b + fromPercent * (Style.green.b - Style.red.b),
haveSelection && !fromSelected && !forceSelection ? .2 : 1
)
var toPercent = 1.0 * edge.toLqi / 255;
var toColor = Qt.rgba(
Style.red.r + toPercent * (Style.green.r - Style.red.r),
Style.red.g + toPercent * (Style.green.g - Style.red.g),
Style.red.b + toPercent * (Style.green.b - Style.red.b),
haveSelection && !toSelected && !forceSelection ? .2 : 1
)
var fromX = root.scale * edge.fromNodeItem.x
var fromY = root.scale * edge.fromNodeItem.y
var toX = root.scale * edge.toNodeItem.x
var toY = root.scale * edge.toNodeItem.y
if (selected) { var gradient = ctx.createLinearGradient(fromX, fromY, toX, toY);
ctx.lineWidth = 2 gradient.addColorStop(0, fromColor);
ctx.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, 1) gradient.addColorStop(1, toColor)
} else { ctx.lineWidth = forceSelection ? 3 : fromSelected || toSelected ? 2 : 1
ctx.lineWidth = 1 ctx.strokeStyle = gradient;
var alpha = d.selectedNodeAddress >= 0 ? .2 : 1
ctx.strokeStyle = Qt.rgba(resultRed, resultGreen, resultBlue, alpha)
}
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(root.scale * fromNodeItem.x, root.scale * fromNodeItem.y) ctx.moveTo(fromX, fromY)
ctx.lineTo(root.scale * toNodeItem.x, root.scale * toNodeItem.y) ctx.lineTo(toX, toY)
ctx.stroke(); ctx.stroke();
ctx.closePath() ctx.closePath()
ctx.restore(); ctx.restore();
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -442,8 +444,8 @@ Page {
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.selectedNodeAddress = -1 d.selectedNodeAddress = -1
for (var i = 0; i < d.nodeItems.length; i++) { for (var networkAddress in d.nodeItems) {
var nodeItem = d.nodeItems[i] var nodeItem = d.nodeItems[networkAddress]
// 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(nodeItem.x * root.scale - translatedMouseX) < (root.scale * root.nodeSize / 2) if (Math.abs(nodeItem.x * root.scale - translatedMouseX) < (root.scale * root.nodeSize / 2)
&& Math.abs(nodeItem.y * root.scale - translatedMouseY) < (root.scale * root.nodeSize / 2)) { && Math.abs(nodeItem.y * root.scale - translatedMouseY) < (root.scale * root.nodeSize / 2)) {
@ -461,7 +463,7 @@ Page {
onWheel: { onWheel: {
if (wheel.modifiers & Qt.ControlModifier) { if (wheel.modifiers & Qt.ControlModifier) {
root.scale = Math.min(2, Math.max(.5, root.scale + 1.0 * wheel.angleDelta.y / 1000)) root.scale = Math.min(root.maxScale, Math.max(root.minScale, root.scale + 1.0 * wheel.angleDelta.y / 1000))
root.reload() root.reload()
} else { } else {
wheel.accepted = false wheel.accepted = false
@ -479,7 +481,7 @@ Page {
onPinchUpdated: { onPinchUpdated: {
print("pinch updated:", pinch.scale) print("pinch updated:", pinch.scale)
root.scale = Math.min(2, Math.max(.5, pinch.scale + scaleOffset)) root.scale = Math.min(root.maxScale, Math.max(root.minScale, pinch.scale + scaleOffset))
root.reload() root.reload()
} }
} }
@ -857,48 +859,6 @@ Page {
id: helpColumn id: helpColumn
width: parent.width width: parent.width
ListSectionHeader {
text: qsTr("Map")
}
RowLayout {
ColumnLayout {
Layout.preferredWidth: Style.iconSize
Rectangle {
Layout.fillWidth: true
height: 2
color: Style.green
}
Rectangle {
Layout.fillWidth: true
height: 2
color: Style.orange
}
Rectangle {
Layout.fillWidth: true
height: 2
color: Style.red
}
}
Label {
Layout.fillWidth: true
text: qsTr("Links between nodes")
}
}
RowLayout {
ColumnLayout {
Layout.preferredWidth: Style.iconSize
Rectangle {
Layout.fillWidth: true
height: 2
color: Style.blue
}
}
Label {
Layout.fillWidth: true
text: qsTr("Route to coordinator")
}
}
ListSectionHeader { ListSectionHeader {
text: qsTr("Links") text: qsTr("Links")
} }