Z-Wave graph (without the python)


#84

Battery powered devices are not mesh routers. They will only have one connection and I’m guessing you will have to catch it when it is awake to see that?


#85

I can’t get this to work in Chrome; the Chrome Developer Console responds with:

The FetchEvent for "https://my.server/api/panel_custom/zwavegraph2"
resulted in a network error response: the promise was rejected.

Everything works fine in Safari.

EDIT: cache problem. Cleared the Chrome cache and everything shows up. Awesome!


#86

Fixed and improved today:

  • Failed node should now report as red.
  • Mains power devices should no longer show as a battery shape even if they have a battery
  • Battery powered devices should have the background level reflect battery state.

Make sure to clear browser cache after uploading new version.


#87

Nice additions @AdamNaj! Thanks to you and of course @NigelL’s great work on this. Super helpful component.


#88

Thanks!

It works now.


#89

This is very nice thank you @NigelL & @AdamNaj


#90

What’s the latest JS file for this - is the first post edited as the newest?


#91

The Gist is getting refreshed as I change things. Go ahead and re-download it if needed.


#92

Dropping in to say I’ve just replaced the old PHP version with @AdamNaj’s updated code and everything worked without a hitch! Thanks everyone!


#93

This is a nice panel, thanks for your work. However there is a bug when loading the panel after excluding nodes from the network. If you remove a node the object (and entities) still exist, but some attributes are missing. In this case, capabilities is undefined and blows up:

zwavegraph:406 Uncaught TypeError: Cannot read property 'filter' of undefined
    at HTMLElement.listNodes (zwavegraph:406)
    at HTMLElement.ready (zwavegraph:195)
    at HTMLElement._enableProperties (app-0fd67575.js:1436)
    at HTMLElement.connectedCallback (app-0fd67575.js:1047)
    at HTMLElement.connectedCallback (app-0fd67575.js:1055)
    at Object.then (7dd1bce433b735e38b5d.chunk.js:5)

It’s probably an HA bug that these objects are still around. Anyways, I worked around it by checking if node.attributes.capabilities is undefined and just continued to the next (I’m no JS expert).


#94

Updated the Gist to (hopefully) deal with the problem by filtering the nodes without “capabilities” earlier in the flow.


#95

Thanks, the update works fine.


#96

This doesn’t seem to work now that I’ve upgraded to the 0.84.1 release. I’m getting a large dump of the text and a script error…


#97

Still working for me.
Can you paste the script error?


#98

Mine is working on 84.1. But I did notice my nodes moved around.


#99

Where can I find latest code ? Don’t u have GitHub?


#100

It’s just a Gist to be placed in config/panels as @NigelL described in his initial post.


#101

I can try to fix it, but I’d need the errors that your browser is showing.


#102

I’m not sure how helpful the log will be, but here it is!

The only thing I redacted was my hostname at the very end.

2018-12-13 18:47:11 ERROR (MainThread) [frontend.js.latest.201812110] data:text/javascript;charset=utf-8,%0Aclass%20HaPanelZWave%20extends%20Polymer.Element%20%7B%0A%20%20static%20get%20is()%20%7B%20return%20'ha-panel-zwavegraph2'%3B%20%7D%0A%0A%20%20static%20get%20properties()%20%7B%0A%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%2F%2F%20Home%20Assistant%20object%0A%20%20%20%20%20%20hass%3A%20Object%2C%0A%20%20%20%20%20%20%2F%2F%20If%20should%20render%20in%20narrow%20mode%0A%20%20%20%20%20%20narrow%3A%20%7B%0A%20%20%20%20%20%20%20%20type%3A%20Boolean%2C%0A%20%20%20%20%20%20%20%20value%3A%20false%2C%0A%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%2F%2F%20If%20sidebar%20is%20currently%20shown%0A%20%20%20%20%20%20showMenu%3A%20%7B%0A%20%20%20%20%20%20%20%20type%3A%20Boolean%2C%0A%20%20%20%20%20%20%20%20value%3A%20false%2C%0A%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%2F%2F%20Home%20Assistant%20panel%20info99%0A%20%20%20%20%20%20%2F%2F%20panel.config%20contains%20config%20passed%20to%20register_panel%20serverside%0A%20%20%20%20%20%20panel%3A%20Object%2C%0A%20%20%20%20%7D%3B%0A%20%20%7D%0A%0A%0A%20%20ready()%20%7B%0A%20%20%20%20super.ready()%3B%0A%0A%20%20%20%20var%20data%3Dthis.listNodes(this.hass)%3B%0A%0A%0A%20%20%20%20var%20g%20%3D%20new%20dagreD3.graphlib.Graph().setGraph(%7B%7D)%3B%0A%20%20%20%20g.graph().rankDir%3D%22BT%22%3B%0A%20%20%20%20g.graph().nodeSep%3D15%3B%0A%0A%20%20%20%20for%20(var%20i%20%3D%200%3B%20i%20%3C%20data%5B%22nodes%22%5D.length%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%20%20var%20node%3Ddata%5B%22nodes%22%5D%5Bi%5D%3B%0A%20%20%20%20%20%20g.setNode(node.id%2C%20node)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20for%20(var%20i%20%3D0%3B%20i%3C%20data%5B%22edges%22%5D.length%3B%20i%2B%2B)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20g.setEdge(data%5B%22edges%22%5D%5Bi%5D.from%2C%20data%5B%22edges%22%5D%5Bi%5D.to%2C%20%7Blabel%3A%22%22%2C%20arrowhead%3A%20%22undirected%22%7D)%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F%2F%20Create%20the%20renderer%0A%20%20%20%20var%20render%20%3D%20new%20dagreD3.render()%3B%0A%0A%20%20%20%20var%20svg%3Dd3.select(this.%24.svg)%3B%0A%20%20%20%20var%20inner%20%3D%20svg.append(%22g%22).attr(%22transform%22%2C%20%22translate(20%2C120)scale(1)%22)%3B%0A%0A%20%20%20%20g.graph().minlen%20%3D%200%3B%0A%0A%20%20%20%20%2F%2F%20Run%20the%20renderer.%20This%20is%20what%20draws%20the%20final%20graph.%0A%20%20%20%20render(inner%2C%20g)%3B%0A%0A%0A%20%20%20%20%2F%2F%20Add%20the%20title%20element%20to%20be%20used%20for%20a%20tooltip%20(SVG%20functionality)%0A%20%20%20%20inner.selectAll(%22g.node%22)%0A%20%20%20%20%20%20%20%20.append(%22title%22).html(function(d)%20%7Breturn%20g.node(d).title%3B%7D)%3B%0A%20%20%20%20svg.attr('height'%2C%20g.graph().height%20%2B%20140)%3B%0A%20%20%20%20svg.attr('width'%2C%20g.graph().width%20%2B%20140)%3B%0A%0A%20%20%20%20var%20legends%3D%5B%7Bshape%3A%20%22rect%22%2C%20color%3A%22lightblue%22%2C%20text%3A%22Hub%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bshape%3A%20%22rect%22%2C%20color%3A%22yellow%22%2C%20text%3A%221%20hop%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bshape%3A%20%22rect%22%2C%20color%3A%22green%22%2C%20text%3A%222%20hops%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bshape%3A%20%22rect%22%2C%20color%3A%22orange%22%2C%20text%3A%223%20hops%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bshape%3A%20%22rect%22%2C%20color%3A%22grey%22%2C%20text%3A%224%20hops%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bshape%3A%20%22rect%22%2C%20color%3A%22red%22%2C%20text%3A%22Failed%20Node%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bshape%3A%20%22rect%22%2C%20color%3A%22white%22%2C%20text%3A%22Unconnected%22%7D%5D%3B%0A%0A%20%20%20%20this.addLegend(svg%2C%20legends%2C%205%2C%2020)%3B%0A%0A%0A%20%20%20%20legends%20%3D%20%5B%7Bshape%3A%20%22circle%22%2C%20text%3A%20%22Mains%20power%22%2C%20color%3A%20%22black%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7Bshape%3A%20%22rect%22%2C%20text%3A%20%22Battery%20power%22%2C%20color%3A%20%22black%22%7D%5D%0A%20%20%20%20%0A%20%20%20%20this.addLegend(svg%2C%20legends%2C%20svg.attr(%22width%22)*.75%2C%2020)%0A%0A%0A%0A%20%20%7D%0A%0A%20%20addLegend(svg%2C%20legends%2C%20startX%2C%20startY)%0A%20%20%7B%0A%20%20%20%20for(var%20counter%3D0%3Bcounter%20%3C%20legends.length%3Bcounter%2B%2B)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20if%20(legends%5Bcounter%5D.shape%20%3D%3D%20%22circle%22)%0A%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20svg.append(legends%5Bcounter%5D.shape)%0A%20%20%20%20%20%20%20%20%20%20.attr('cx'%2C%20startX%20%2B%205%20)%0A%20%20%20%20%20%20%20%20%20%20.attr('cy'%2CstartY%20%2B%205%20%2B%2020%20*%20counter)%0A%20%20%20%20%20%20%20%20%20%20.attr('r'%2C%205)%0A%20%20%20%20%20%20%20%20%20%20.style(%22stroke%22%2C%20%22black%22)%0A%20%20%20%20%20%20%20%20%20%20.style(%22fill%22%2C%20legends%5Bcounter%5D.color)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20else%0A%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20svg.append(legends%5Bcounter%5D.shape)%0A%20%20%20%20%20%20%20%20%20%20.attr('x'%2CstartX)%0A%20%20%20%20%20%20%20%20%20%20.attr('y'%2CstartY%20%2B%2020%20*%20counter)%0A%20%20%20%20%20%20%20%20%20%20.attr('width'%2C%2010)%0A%20%20%20%20%20%20%20%20%20%20.attr('height'%2C%2010)%0A%20%20%20%20%20%20%20%20%20%20.style(%22stroke%22%2C%20%22black%22)%0A%20%20%20%20%20%20%20%20%20%20.style(%22fill%22%2C%20legends%5Bcounter%5D.color)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20svg.append('text')%0A%20%20%20%20%20%20%20%20.attr(%22x%22%2C%20startX%20%2B%2020)%0A%20%20%20%20%20%20%20%20.attr(%22y%22%2C%20startY%20%2B%2010%20%2B%2020*counter)%0A%20%20%20%20%20%20%20%20.text(legends%5Bcounter%5D.text)%0A%20%20%20%20%20%20%20%20.attr(%22class%22%2C%20%22textselected%22)%0A%20%20%20%20%20%20%20%20.style(%22text-anchor%22%2C%20%22start%22)%0A%20%20%20%20%20%20%20%20.style(%22font-size%22%2C%2015)%3B%0A%0A%20%20%20%20%7D%0A%0A%20%20%7D%0A%0A%20%20listNodes(hass)%20%7B%0A%20%20%20%20let%20states%3Dnew%20Array()%3B%0A%20%20%20%20for%20(let%20state%20in%20hass.states)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20states.push(%7Bname%3Astate%2C%20entity%3Ahass.states%5Bstate%5D%7D)%3B%0A%20%20%20%20%7D%0A%20%20%20%20let%20zwaves%20%3D%20states.filter((s)%20%3D%3E%20%7Breturn%20s.name.indexOf(%22zwave.%22)%20%3D%3D0%7D)%3B%0A%20%20%20%20let%20result%3D%20%7B%22edges%22%3A%5B%5D%2C%20%22nodes%22%3A%5B%5D%7D%3B%0A%0A%20%20%20%20let%20hubNode%3D0%3B%0A%20%20%20%20let%20neighbours%3D%7B%7D%3B%0A%0A%20%20%20%20for%20(let%20b%20in%20zwaves)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20let%20id%3Dzwaves%5Bb%5D.entity.attributes%5B%22node_id%22%5D%3B%20%0A%20%20%20%20%20%20%20let%20node%20%3D%20zwaves%5Bb%5D.entity%3B%0A%20%20%20%20%20%20%20if%20(node.attributes%5B%22capabilities%22%5D.filter(%0A%09%09%09(s)%20%3D%3E%20%7Breturn%20s%20%3D%3D%22primaryController%22%7D).length%20%3E%200)%20%0A%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20hubNode%3Did%3B%0A%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20neighbours%5Bid%5D%3Dnode.attributes%5B'neighbors'%5D%3B%0A%0A%20%20%20%20%20%20%20let%20entities%20%3D%20states.filter((s)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20((s.name.indexOf(%22zwave.%22)%20%3D%3D%20-1)%20%26%26%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(s.entity.attributes%5B%22node_id%22%5D%20%3D%3D%20id))%20%7D)%3B%0A%20%20%20%20%20%20%20let%20batlev%3Dnode.attributes.battery_level%3B%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20let%20entity%3D%7B%22id%22%3A%20%20id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22label%22%3A%20(node.attributes%5B%22node_name%22%5D%20%2B%20%22%20(%22%20%2B%20node.attributes%5B%22averageResponseRTT%22%5D%2B%22ms)%22).replace(%2F%20%2Fg%2C%20%22%5Cn%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22class%22%3A%20%22unset%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22shape%22%3A%20batlev%20!%3D%20undefined%20%3F%20%22rect%22%20%3A%20%22circle%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22title%22%3A%20%22%3Cb%3E%22%2Bnode.attributes%5B%22node_name%22%5D%2B%22%3C%2Fb%3E%22%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%3Cbr%20%2F%3ENode%3A%20%22%20%2B%20id%20%2B%20(node.attributes%5B%22is_zwave_plus%22%5D%20%3F%20%22%2B%22%20%3A%20%22%22)%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%3Cbr%20%2F%3EProduct%20Name%3A%20%22%20%2B%20node.attributes%5B%22product_name%22%5D%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%3Cbr%20%2F%3EAverage%20Request%20RTT%3A%20%22%20%2B%20node.attributes%5B%22averageResponseRTT%22%5D%2B%22ms%22%20%2B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%3Cbr%20%2F%3EPower%20source%3A%20%22%20%2B%20(batlev%20!%3D%20undefined%20%3F%20%22battery%20(%22%20%2B%20batlev%20%2B%22%25)%22%20%3A%20%22mains%22)%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%3Cbr%20%2F%3E%22%20%2B%20entities.length%20%2B%20%22%20entities%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22forwards%22%3A%20(node.attributes.is_awake%20%26%26%20node.attributes.is_ready%20%26%26%20!node.attributes.is_failed%20%26%26%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20node.attributes.capabilities.includes(%22listening%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%3B%0A%0A%20%20%20%20%20%20%20if%20(node.attributes%5B%22is_failed%22%5D)%0A%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20entity.label%20%3D%20%22FAILED%3A%20%22%2Bentity.label%3B%0A%20%20%20%20%20%20%20%20%20entity%5B%22font.multi%22%5D%3Dtrue%3B%0A%20%20%20%20%20%20%20%20%20entity%5B%22title%22%5D%3D%22%3Cb%3EFAILED%3A%20%3C%2Fb%3E%22%2Bentity.title%3B%0A%20%20%20%20%20%20%20%20%20entity%5B%22group%22%5D%3D%22Failed%22%3B%0A%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20if%20(hubNode%20%3D%3D%20id)%0A%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20entity.label%3D%22ZWave%20Hub%22%3B%0A%20%20%20%20%20%20%20%20%20entity.borderWidth%3D%202%3B%0A%20%20%20%20%20%20%20%20%20entity.fixed%3Dtrue%3B%0A%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20result.nodes.push(entity)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%0A%20%20%20%20if%20(hubNode%20%3E%200)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20let%20layer%3D0%3B%0A%20%20%20%20%20%20let%20previousRow%3D%5BhubNode%5D%3B%0A%20%20%20%20%20%20let%20mappedNodes%3D%5BhubNode%5D%3B%0A%20%20%20%20%20%20while%20(previousRow.length%20%3E%200)%0A%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20layer%20%3D%20layer%2B1%3B%0A%20%20%20%20%20%20%20%20let%20nextRow%3D%5B%5D%3B%0A%20%20%20%20%20%20%20%20for%20(let%20target%20in%20previousRow)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20result.nodes.filter((n)%20%3D%3E%20%7Breturn%20((n.id%20%3D%3DpreviousRow%5Btarget%5D)%20%26%26%20(n.group%3D%22unset%22))%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.every((d)%20%3D%3E%20%7Bd.class%3D%22Layer%22%20%2B%20layer%3B%7D)%0A%0A%20%20%20%20%20%20%20%20%20%20if%20(result.nodes.filter((n)%20%3D%3E%20%7Breturn%20((n.id%20%3D%3D%20previousRow%5Btarget%5D)%20%26%26%20(n.forwards))%7D).length%20%3E%200)%0A%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20let%20row%3Dneighbours%5BpreviousRow%5Btarget%5D%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(let%20node%20in%20row)%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(!mappedNodes.includes(row%5Bnode%5D))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.edges.push(%7B%22from%22%3Arow%5Bnode%5D%2C%20%22to%22%3ApreviousRow%5Btarget%5D%7D)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nextRow.push(row%5Bnode%5D)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20for%20(let%20idx%20in%20nextRow)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20mappedNodes.push(nextRow%5Bidx%5D)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20previousRow%20%3D%20nextRow%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%20%20return%20result%3B%0A%20%20%20%7D%0A%0A%0A%7D%0AcustomElements.define(HaPanelZWave.is%2C%20HaPanelZWave)%3B%0A%0A%2F%2F%23%20sourceURL%3Dhttps%3A%2F%2F[HOSTNAME]2Fapi%2Fpanel_custom%2Fzwavegraph2.js%0A:0:0 Script error.

#103

Okay, I dug into this a little more. I don’t believe it’s an issue with your script, but instead with my NGINX reverse proxy not correctly handling websockets – which is odd since it seems to work in other instances.

I’ll dig into it a bit more and see what I can figure out.

Thanks!