Zigbee2MQTT network map

Hi.

I’ve used this code sample for my OpenHAB 2.4 instance, too. Unfortunately it isn’t working.
After some tests I’ve found a solution which I would present here, too.

The main things was that zigbee.dot wasn’t created with values from my bridge. This results in an empty zigbee.svg file. Within zigbee.html .min was missing at the js link for svg-zoom lib.

Withing the rule there is another way needed to publish the networkmap request to mqtt. Found this in some other posts, too.

I post only changes (and some parts around) to the main post.

/etc/openhab2/html/zigbee.html

<html>
<head>
	<script src="svg-pan-zoom.min.js"></script>
</head>
<body style="margin: 0; padding: 0">
	<embed type="image/svg+xml" src="zigbee.svg" id="map" width="100%" height="100%"/>

	<script language="JavaScript">
	document.getElementById('map').addEventListener('load', function(){
		// Will get called after embed element was loaded
		svgPanZoom(document.getElementById('map'), {
		controlIconsEnabled: true

		});
	})
	var panZoomMap = svgPanZoom('#map');
	</script>
</body>
</html>

Because of that I’ve used the MQTT Binding for OH 2.4 the way only through this item wasn’t working.

First I’ve created a thing for bridge and channel for networkmap

/etc/openhab2/things/something.things

Bridge mqtt:broker:mymqtt "MQTT Broker v2" @ "MQTT" [ 
	host="127.0.0.1",
	secure=false,
	port=1883,
	qos=0,
	clientid="OpenHAB2",
	keep_alive_time=30000,
	reconnect_time=60000
] {

	Thing topic bridge "MQTT Zigbee Bridge C2531 Config" @ "MQTT" {
		Channels:
			Type switch : config-permit_join "Permit Join" [
				stateTopic="zigbee2mqtt/bridge/config",
				commandTopic="zigbee2mqtt/bridge/config/permit_join",
				transformationPattern="JSONPATH:$.permit_join",
				on="true",
				off="false"
			]
			Type string : config-version "Version" [
				stateTopic="zigbee2mqtt/bridge/config",
				transformationPattern="JSONPATH:$.version"
			]
			Type string : config-commit "Commit" [
				stateTopic="zigbee2mqtt/bridge/config",
				transformationPattern="JSONPATH:$.commit"
			]
			Type string : config-coordinator_firmware "Coordinator Firmware" [
				stateTopic="zigbee2mqtt/bridge/config",
				transformationPattern="JSONPATH:$.coordinator_firmware"
			]
			Type string : config-log_level "Log Level" [
				stateTopic="zigbee2mqtt/bridge/config",
				transformationPattern="JSONPATH:$.log_level"
			]
			Type string : networkmap "Network Map" [
				stateTopic="zigbee2mqtt/bridge/networkmap/graphviz"
			]
	}

/etc/openhab2/rules/zigbee_networkmap.rules

import java.io.FileWriter
import java.io.FileReader
import java.io.BufferedReader

// parameters
val String dotFile = "/etc/openhab2/html/zigbee.dot"
val String mapFile = "/etc/openhab2/html/zigbee.map"
val String svgFile = "/etc/openhab2/html/zigbee.svg"

rule "Request ZigBee Network Map in Graphviz format"
when
	Time cron "0 3/5 * * * ?"
//	Time cron "* * * * * ?"
then
	val actions = getActions("mqtt","mqtt:broker:mymqtt")
	actions.publishMQTT("zigbee2mqtt/bridge/networkmap","graphviz")

	// ask zigbee2mqtt to generate graphviz network map (in DOT format)
	//publish("mqttembeddedbroker", "zigbee2mqtt/bridge/networkmap", "graphviz")
end


rule "Fetch ZigBee tree in Graphviz format"
when
	Item zigbee_NetworkMap changed
then
	print(zigbee_NetworkMap)
	// once zigbee2mqtt sent graphviz network map => store it and convert it to PNG/SVG
	if (zigbee_NetworkMap.state !== NULL && zigbee_NetworkMap.state != "") {
		var String dotString = zigbee_NetworkMap.state.toString

		// load mapping file and regex-replace network addresses with friendly names
		var FileReader fr = new FileReader(mapFile);
		var BufferedReader br = new BufferedReader(fr);
		var String line
		var String[] split
		var String regex
		var String replace
		while ((line = br.readLine()) !== null) {
			split  = line.split("=")
			if(split.length == 2) {
				regex = "(label=.*)(" + split.get(0) + ")(.*)"
				replace = "$1" + split.get(1) + "$3"
				dotString = dotString.replaceFirst(regex, replace)
			}
		}
		fr.close();

		// store result to DOT file
		var fw = new FileWriter(dotFile, false);
		fw.write(dotString);
		fw.flush();
		fw.close();

		// render PNG/SVG
		executeCommandLine("/usr/bin/sfdp -Nfontname=Arial -Nfontsize=9 -Ncolor=#666666 -Nstyle=filled -Nfillcolor=#eeeeee -Efontname=Arial -Efontsize=8 -Efontcolor=#cc0000 -Ecolor=#cccccc -Tsvg -o" + svgFile + " " + dotFile, 5000)
	}
end

/etc/openhab2/items/zigbee.items

String zigbee_NetworkMap { channel="mqtt:topic:mymqtt:bridge:networkmap" }
Group zigbee_NetworkMap_Webview "ZigBee Topologie"

Sitemap has no additional changes…

Hope I’ve nothing forgotten to post here. Feel free to test …

2 Likes

Thanks - works great.
The only thing for me, was: I had to take the original html

1 Like

Is there a docker installation for this zigbee topography? If so , where can I find it?

I’m having a little issue with the network map. It is basically working, so when the first rule is triggered zigbee2mqtt does its network scan etc (I can see this in the log).
However the handover to the second rule only works, when the string item zigbee_NetworkMap is null - and this is only after reboot.
Even when the network map changed due to new members, the rule isn’t triggered somehow. But at this point the string item should have changed?

So basically whenever I want to have a new network map, I have to reboot. Does anyone else have this problem or know a solution?

Update:
The item zigbee_NetworkMap ist refreshed after actions.publishMQTT("zigbee2mqtt/bridge/networkmap","graphviz"). Therefore the second rule is not triggered. The item state of zigbee_NetworkMap will remain the same even though zigbee2mqtt publishes a new one. Does anyone know why the item is not getting overwritten?

Update:
Seems like a bug in MQTT after reading a few topics in the forum. The publish action sometimes stops working. To solve this problem, delete the action from the rule, save the rule, clear the cache and then insert the action back into the rule. It should work from there on.

I think it’s not needed to remove the action part. You just need to modify the rule. A new line / char should be enough.

Currently I’ve separated MQTT broker and MQTT things. Mostly the action blocking is a result when the broker isn’t there/connected.

1 Like

Hi Markus,
Would it be possible to post a complete idiot’s( ie me) guide for this network map for mqtt 2.4?

My zigbee2mqtt is in a docker. Would it possible to also have the network map installed as a docker?

Sorry for very late reply… You just need to copy code from the beginning of the thread. Then you have to modify/replace it with my part…

Hello,

sry for my english, its not my native language.
i use openhab 2.5.

i use your files and code changes, but the .svg file is still empty.
i dont see any log entries of the rule.

is something wrong by my code ?

items file…

String zigbee_NetworkMap { channel="mqtt:broker:6bd42746:networkmap" }

after “channel” that is my broker

here i put my broker in too

zigbee_networkmap.rules…

val actions = getActions("mqtt","mqtt:broker:6bd42746")
actions.publishMQTT("zigbee2mqtt/bridge/networkmap","graphviz")

my problem is the folder “bridge” or “networkmap” doesnt exists ion my openhabian?!

here is my thing

THNAK YOU

@marc1310

  1. Check you channel definition.
  2. zigbee_NetworkMap is only a string not a thing.
  3. do OH have write permissions to the html folder?
  4. did you really read everything in this thread?

i am completly new to openhab2…

-you mean that all the channels and the networkmap must be under the broker?
-yes OH has write premissions

  • i think so…what have I forgotten

For all following user with basic questions… this is my shortened setup to have the network map.

I’ve separated broker and Zigbee things. Otherwise Zigbee bridge isn’t found sometimes.

/etc/openhab2/things/zigbee-bridge-mqtt.things

Thing mqtt:topic:mymqtt:bridge "MQTT Zigbee Bridge C2531 Config" (mqtt:broker:mymqtt) @ "MQTT Bridge" {
        Channels:
                Type switch : config-permit_join "Permit Join" [
                        stateTopic="zigbee2mqtt/bridge/config",
                        commandTopic="zigbee2mqtt/bridge/config/permit_join",
                        transformationPattern="JSONPATH:$.permit_join",
                        on="true",
                        off="false"
                ]
                Type string : config-version "Version" [
                        stateTopic="zigbee2mqtt/bridge/config",
                        transformationPattern="JSONPATH:$.version"
                ]
                Type string : config-commit "Commit" [
                        stateTopic="zigbee2mqtt/bridge/config",
                        transformationPattern="JSONPATH:$.commit"
                ]
                Type string : config-coordinator_firmware "Coordinator FW" [
                        stateTopic="zigbee2mqtt/bridge/config",
                        transformationPattern="JSONPATH:$.coordinator"
                ]
                Type string : config-log_level "Log Level" [
                        stateTopic="zigbee2mqtt/bridge/config",
                        transformationPattern="JSONPATH:$.log_level"
                ]
                Type string : networkmap "Network Map" [
                        stateTopic="zigbee2mqtt/bridge/networkmap/graphviz"
                ]
}

/etc/openhab2/things/zigbee-sensors-motion-mqtt.things (Example Zigbee thing)

Thing mqtt:topic:mymqtt:0x00***********eab7 "MQTT Motion" (mqtt:broker:mymqtt) @ "Aqara Motion" {
        Channels:
                Type number : illuminance "Illuminance" [
                        stateTopic="zigbee2mqtt/0x00***********eab7",
                        transformationPattern="JSONPATH:$.illuminance"
                ]
                Type switch : occupancy "Occupancy" [
                        stateTopic="zigbee2mqtt/0x00***********eab7",
                        transformationPattern="JSONPATH:$.occupancy",
                        on="true",
                        off="false"
                ]
                Type number : voltage "Voltage" [
                        stateTopic="zigbee2mqtt/0x00***********eab7",
                        transformationPattern="JSONPATH:$.voltage"
                ]
                Type number : battery "Battery" [
                        stateTopic="zigbee2mqtt/0x00***********eab7",
                        transformationPattern="JSONPATH:$.battery"
                ]
                Type number : link "Link Quality" [
                        stateTopic="zigbee2mqtt/0x00***********eab7",
                        transformationPattern="JSONPATH:$.linkquality"
                ]
}

/etc/openhab2/items/zigbee.items

String zigbee_NetworkMap { channel="mqtt:topic:mymqtt:bridge:networkmap" }
Group zigbee_NetworkMap_Webview "ZigBee Topologie"

Switch mqtt_z_0x00***********eab7_occupancy "Motion" <motion> { channel="mqtt:topic:mymqtt:0x00***********eab7:occupancy" }
Number mqtt_z_0x00***********eab7_illuminance "Illuminance" <link> { channel="mqtt:topic:mymqtt:0x00***********eab7:illuminance" }
Number mqtt_z_0x00***********eab7_linkquality "Linkquality [%.2f %%]" <link> { channel="mqtt:topic:mymqtt:0x00***********eab7:linkquality" }
Number mqtt_z_0x00***********eab7_battery "Battery [%.1f %%]" <battery> { channel="mqtt:topic:mymqtt:0x00***********eab7:battery" }
Number mqtt_z_0x00***********eab7_voltage "Voltage [%.0f mV]" <battery> { channel="mqtt:topic:mymqtt:0x00***********eab7:voltage" }

/etc/openhab2/rules/zigbee_networkmap.rules

import java.io.FileWriter
import java.io.FileReader
import java.io.BufferedReader

// parameters
val String dotFile = "/etc/openhab2/html/zigbee.dot"
val String mapFile = "/etc/openhab2/html/zigbee.map"
val String svgFile = "/etc/openhab2/html/zigbee.svg"

rule "Request ZigBee Network Map in Graphviz format"
when
        Time cron "0 * 2/3 * * ?" // stunde 2 alle 3 stunden
//      Time cron "0 3/5 * * * ?" // minute 3 alle 5 min
//      Time cron "* * * * * ?"
then
        val actions = getActions("mqtt","mqtt:broker:mymqtt")
        actions.publishMQTT("zigbee2mqtt/bridge/networkmap","graphviz")

        // ask zigbee2mqtt to generate graphviz network map (in DOT format)
        //publish("mqttembeddedbroker", "zigbee2mqtt/bridge/networkmap", "graphviz")
end


rule "Fetch ZigBee tree in Graphviz format"
when
        Item zigbee_NetworkMap changed
then
        print(zigbee_NetworkMap)
        // once zigbee2mqtt sent graphviz network map => store it and convert it to PNG/SVG
        if (zigbee_NetworkMap.state !== NULL && zigbee_NetworkMap.state != "") {
                var String dotString = zigbee_NetworkMap.state.toString
                // Gimmick: Reduce sensor name length
                dotString = dotString.replace("Xiaomi Aqara door & window contact sensor (MCCGQ11LM)", "Aqara Contact (MCCGQ11LM)")
                dotString = dotString.replace("Xiaomi Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)", "Aqara Temp-Humi-Press (WSDCGQ11LM)")
                dotString = dotString.replace("Xiaomi Aqara vibration sensor (DJT11LM)", "Aqara vibration (DJT11LM)")

                // load mapping file and regex-replace network addresses with friendly names
                var FileReader fr = new FileReader(mapFile);
                var BufferedReader br = new BufferedReader(fr);
                var String line
                var String[] split
                var String regex
                var String replace
                while ((line = br.readLine()) !== null) {
                        split  = line.split("=")
                        if(split.length == 2) {
                                regex = "(label=.*)(" + split.get(0) + ")(.*)"
                                replace = "$1" + split.get(1) + "$3"
                                dotString = dotString.replaceFirst(regex, replace)
                        }
                }
                fr.close();

                // store result to DOT file
                var fw = new FileWriter(dotFile, false);
                fw.write(dotString);
                fw.flush();
                fw.close();

                // render PNG/SVG
                executeCommandLine("/usr/bin/sfdp -Nfontname=Arial -Nfontsize=9 -Ncolor=#666666 -Nstyle=filled -Nfillcolor=#eeeeee -Efontname=Arial -Efontsize=8 -Efontcolor=#cc0000 -Ecolor=#cccccc -Tsvg -o" + svgFile + " " + dotFile, 5000)
        }
end

/etc/openhab2/html/zigbee.html

<html>
<head>
        <script src="svg-pan-zoom.min.js"></script>
</head>
<body style="margin: 0; padding: 0">
        <embed type="image/svg+xml" src="zigbee.svg" id="map" width="100%" height="100%"/>

        <script language="JavaScript">
        document.getElementById('map').addEventListener('load', function(){
                // Will get called after embed element was loaded
                svgPanZoom(document.getElementById('map'), {
                controlIconsEnabled: true

                });
        })
        var panZoomMap = svgPanZoom('#map');
        </script>
</body>
</html>

/etc/openhab2/html/zigbee.map

0x00***********eab7=Motion
...zigbee-id...=Name you want

/etc/openhab2/html/svg-pan-zoom.min.js

Load this file and put it into the html folder, too.
Currently there is v3.6.1 (I’m using 3.6.0, because I haven’t updated yet.

/etc/openhab2/sitemaps/yoursite.sitemap

Put this element where you want into the sitemap

        Group item=zigbee_NetworkMap_Webview {
                Webview url="/static/zigbee.html" height=16 icon="network"
        }
6 Likes

Thanks, I really like this solution and was starting to implement something similar. The svg approach is something which is much superior to the pngs, etc.
However, I have no problem with the bridge surrounding all things.
Thanks for sharing.

1 Like

Is there any chance to also enable pinch/touch zoom? That would make zooming and navigating the networkmap much more easy.

If it’s not blocked by the webview element from the app, you can enable this by
svgPanZoom('#map', {viewportSelector: '.svg-pan-zoom_viewport', dblClickZoomEnabled: true});

Example here http://ariutta.github.io/svg-pan-zoom/demo/mobile.html

Thank you for the suggestion and the help.

I have tried it like this sadly it behaves the same as before.

<html>
    <head>
            <script src="svg-pan-zoom.min.js"></script>
    </head>
    <body style="margin: 0; padding: 0">
            <embed type="image/svg+xml" src="zigbee.svg" id="map" width="100%" height="100%"/>

            <script language="JavaScript">
            document.getElementById('map').addEventListener('load', function(){
                    // Will get called after embed element was loaded
                    svgPanZoom(document.getElementById('map'), {
                    controlIconsEnabled: true

                    });
            })
            var panZoomMap = svgPanZoom('#map', {viewportSelector: '.svg-pan-zoom_viewport', dblClickZoomEnabled: true});
            </script>
    </body>
    </html>

then put it in the function above svgPanZoom(). Here are all parameters listed: https://github.com/ariutta/svg-pan-zoom#how-to-use

Edit: for mobile use, he implements Hammer.js. The author also wrote how to integrate it

Thanks for summarizing the config! I only missed this part :slight_smile:

Everything is working perfect!

As you know my part was only an incremental information to the initial posting. And there it is written. :slight_smile:

The lib also enables touch support as I just saw. So whoever wants to implement this, just add as script

var eventsHandler;

        eventsHandler = {
          haltEventListeners: ['touchstart', 'touchend', 'touchmove', 'touchleave', 'touchcancel']
        , init: function(options) {
            var instance = options.instance
              , initialScale = 1
              , pannedX = 0
              , pannedY = 0

            // Init Hammer
            // Listen only for pointer and touch events
            this.hammer = Hammer(options.svgElement, {
              inputClass: Hammer.SUPPORT_POINTER_EVENTS ? Hammer.PointerEventInput : Hammer.TouchInput
            })

            // Enable pinch
            this.hammer.get('pinch').set({enable: true})

            // Handle double tap
            this.hammer.on('doubletap', function(ev){
              instance.zoomIn()
            })

            // Handle pan
            this.hammer.on('panstart panmove', function(ev){
              // On pan start reset panned variables
              if (ev.type === 'panstart') {
                pannedX = 0
                pannedY = 0
              }

              // Pan only the difference
              instance.panBy({x: ev.deltaX - pannedX, y: ev.deltaY - pannedY})
              pannedX = ev.deltaX
              pannedY = ev.deltaY
            })

            // Handle pinch
            this.hammer.on('pinchstart pinchmove', function(ev){
              // On pinch start remember initial zoom
              if (ev.type === 'pinchstart') {
                initialScale = instance.getZoom()
                instance.zoomAtPoint(initialScale * ev.scale, {x: ev.center.x, y: ev.center.y})
              }

              instance.zoomAtPoint(initialScale * ev.scale, {x: ev.center.x, y: ev.center.y})
            })

            // Prevent moving the page on some devices when panning over SVG
            options.svgElement.addEventListener('touchmove', function(e){ e.preventDefault(); });
          }

        , destroy: function(){
            this.hammer.destroy()
          }
        }

and in the call for the function with the svg id

, controlIconsEnabled: true
, customEventsHandler: eventsHandler

Note: these are also just hints :wink:

Could you please change your complete setup, where do I need to put this script ?

“hammer” sounds like an external script? Do we need anything else?