Z-Wave graph (without the python)


#42

Did you perhaps also edit your zwave node names in the entity_registry.yaml file (that is no longer used for zwave devices in later versions)?

That would explain why your node names look like friendly names.

I had a go at replacing node_name with friendly_name at a couple of places in the zwavegraph2.html file and restarted but it made no difference. I still see the (rather unhelpful) node names in the graph.


#43

Hi Guys, I think I’ve figured out what’s going on, but don’t know how to fix it.

It looks as if my code ends up reading the node_name (or the friendly_name) from the zwcfg_.xml instead of using what gets displayed in the dev_state tab.

As a workaround, if you shutdown HA, and edit that file (carefully) you can change the Name attribute to have the value you want.

I have tried removing the file so that it would be recreated, and I do not advise this solution. It recreated the nodes slowly, but didn’t get the names right.

I will see if I can figure out how to raise this as a bug (because I think it is) but if one of you knows how, that would be great.

NigelL

Editing to add this link to the issue I reported on github. https://github.com/home-assistant/home-assistant/issues/16704


#44

There are a couple of ways to change that. As you mentioned you could carefully modify the zwcfg_.xml file which I have done in the past.


Note the name="" sections.
An this is how my config looks:

Notice the 2 named ones Linear and Zooz. I am going to change Zooz within HA Configuration > Cusomtization section:

Node 31 is the Zooz switch. Notice the name is Laundry Room but the node_name is the default. I then changed it to Laundry Room:

Restarted HA and it modified the customize.yaml file like so:
yaml
I already had the friendly name done so it added the node_name and now my graph looks like this:

And the device state is below:

So you can either do the zwcfg file or modify your customize.yaml file then restart HS or modify it in HA through configuration customization and restart HA. I hope this helps and I didn’t get to far of track.

Forgot to add I am on 77.2


#45

I am getting this same error and the same 'label' of undefined error in the browser console. Did you find resolution for this?


#46

No, I gave up and haven’t gotten a chance to get back to this.


#47

I’ve identified the issue which is causing the easily renamed nodes not to have their new name show up properly.

When you add a zwave element to the HA system, it actually adds two (or more) nodes. One for the zwave physical device, and one (again or more) for the logical device (for instance, a light switch).

My code currently is looking at the zwave item, not at the logical device. I will try to figure out how to get the correct devices, and post an update (probably this weekend).

When you use the cog icon to rename the node, you are renaming the logical node, when you use the zwcfg file you are renaming the physical device (the zwave.xxx object)


#48

I hope you don’t change the way it works.

To me, looking at the zwave entity makes the most logical sense since that is the base device that all of the status’s are based on. If the only issue is the friendly name then the base zwave device can also be named to a friendly name too in the same way as the others.


#49

What about the case when you have 3 or 4 of the same type of device (all called manufacturer name - device type) and one is not connecting to the mesh well? How do you easily tell which one it is? Much easier if the friendly name is used.


#50

you can change the entity_id and the friendly name of the zwave device portion just like you can any of the other entities spawned by the device.

That said I agree that the new way to deal with zwave devices now that the rename node in the zwave control panel has been removed and the entity_registry has been hidden sucks.


#51

For the actual zwave device (rather than it’s associated sensors and switches) I can change the friendly name from the front end but every time I try to change the entity id I get an error saying the name is invalid. I only used lower case letters with no spaces.


#52

Hmmm…

well I guess I should have said that “you SHOULD be able to change the entity_id”. I haven’t actually ever tried it yet. That was my understanding at least. That’s disappointing that it doesn’t work since that was the only saving grace in the new entity naming convention in that you could at least still go in and modify each entity.

Like I said here and in several other threads, I really REALLY dislike the confusion and the user unfriendliness of the entity_registry concept. I have felt that way since it came out. Now that the most recent changes were implemented (doing away with the rename node function especially) it’s even worse. But the devs seem incapable of addressing the concerns. I really don’t get it.

using the old way of doing things this is the way my z wave graph looks:

I’m still planning on coming up with a work around to get the old functionality working again. Even if it will be “unsupported” and “unsanctioned”.

But back top the topic at hand:

If you don’t look at the base zwave device how do you decide which of the spawned children devices do you look at for the graphing function? Not every device will spawn the same domain sub-entities. the only thing that is guaranteed to be there is the zwave.xxx entity. Unless I’m looking at it incorrectly?


#53

This looks awesome, but ive got a hard time installing it. First of all, the "panel_custom config in the first post doesnt seem to be complete, second of all, where’s the required JS file?


#54

Hi @dinth, I know it looks odd if you’re used to seeing full html files, but that is the complete file. There’s no additional javascript file, most of that file is javascript.


#55

Got it working after factory resetting all devices. Looks like my mesh needs work.


#56

Im getting an error when i try to add it:
Unable to find webcomponent for zwavegraph2: /config/panels/zwavegraph2.html
Setup failed for panel_custom: Component failed to initialize.


#57

Works for me in Firefox, but I’m getting an error in Chrome:
<Event call_service[L]: service=write, domain=system_log, service_data=logger=frontend.js.es5.201808310, message=https://XXXXXXXXXX.duckdns.org:XXX/static/custom-elements-es5-adapter.js:4:618 Uncaught TypeError: Class constructor HaPanelZWave cannot be invoked without 'new'>


#58

I might have found a bug…

I haven’t seen a failed node in a while but yesterday I needed to kill power to an outlet for some work and it’s now showing as failed in my z wave controller but it’s not displaying red. It’s showing white now (disconnected) but earlier today I saw it as “failed” in the device box but it was still displaying as green.

I’m using the second version of your code from aug 19th.

ex


#60

I’ve only currently got one device, and using your third post (latest?) version, I get the graph appearing under the legend. Any way to center the graph on the page?


#61

Yeah he’s aware of that problem.


#62

Hi all, sorry I haven’t been around much, work is keeping me busy.

Here’s an update which should fix the width issues for users with small z-wave networks.

<dom-module id='ha-panel-zwavegraph2'>
  <template>
    <style include="ha-style">
      .node.Layer1 > rect, .node.Layer1 > circle
      {
        fill: lightblue;
      } 

      .node.Layer2 > rect, .node.Layer2 > circle
      {
        fill: yellow;
      }

      .node.Layer3 > rect, .node.Layer3 > circle
      {
        fill: green;
      }

      .node.Layer4 > rect, .node.Layer4 > circle
      {
        fill: orange;
      }

      .node.Layer5 > rect, .node.Layer5 > circle
      {
        fill: grey;
      }

      .node.Error > rect, .node.Error > circle
      {
        fill: red;
      }

      .node.unset > rect, .node.unset > circle
      {
        fill: white;
      }

      .node > rect, .node > circle
      {
        stroke: black;
      }

      .node text
      {
        font-size: 12px;
      }

      .edgePath path {
        stroke: #333;
        fill: #333;
        stroke-width: 1.5px;
      }

    </style>

    <app-header-layout has-scrolling-region>
      <app-header slot="header" fixed>
        <app-toolbar>
          <ha-menu-button narrow='[[narrow]]' show-menu='[[showMenu]]'></ha-menu-button>
          <div main-title>Zwave Graph</div>
        </app-toolbar>
      </app-header>

      <div class="content">
        <svg id="svg"></svg>
      </div>
    </app-header-layout>

  </template>

</dom-module>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.6.1/dagre-d3.js"></script>
<script>
class HaPanelZWave extends Polymer.Element {
  static get is() { return 'ha-panel-zwavegraph2'; }

  static get properties() {
    return {
      // Home Assistant object
      hass: Object,
      // If should render in narrow mode
      narrow: {
        type: Boolean,
        value: false,
      },
      // If sidebar is currently shown
      showMenu: {
        type: Boolean,
        value: false,
      },
      // Home Assistant panel info99
      // panel.config contains config passed to register_panel serverside
      panel: Object,
    };
  }


  ready() {
    super.ready();

    var data=this.listNodes(this.hass);


    var g = new dagreD3.graphlib.Graph().setGraph({});
    g.graph().rankDir="BT";
    g.graph().nodeSep=15;

    for (var i = 0; i < data["nodes"].length; i++) {
      var node=data["nodes"][i];
      g.setNode(node.id, node);
    }

    for (var i =0; i< data["edges"].length; i++)
    {
      g.setEdge(data["edges"][i].from, data["edges"][i].to, {label:"", arrowhead: "undirected"})
    }

    // Create the renderer
    var render = new dagreD3.render();

    var svg=d3.select(this.$.svg);
    var inner = svg.append("g").attr("transform", "translate(20,120)scale(1)");

    g.graph().minlen = 0;

    // Run the renderer. This is what draws the final graph.
    render(inner, g);


    // Add the title element to be used for a tooltip (SVG functionality)
    inner.selectAll("g.node")
        .append("title").html(function(d) {return g.node(d).title;});
    svg.attr('height', g.graph().height + 140);
    svg.attr('width', g.graph().width + 140);

    var legends=[{shape: "rect", color:"lightblue", text:"Hub"},
                 {shape: "rect", color:"yellow", text:"1 hop"},
                 {shape: "rect", color:"green", text:"2 hops"},
                 {shape: "rect", color:"orange", text:"3 hops"},
                 {shape: "rect", color:"grey", text:"4 hops"},
                 {shape: "rect", color:"red", text:"Failed Node"},
                 {shape: "rect", color:"white", text:"Unconnected"}];

    this.addLegend(svg, legends, 5, 20);


    legends = [{shape: "circle", text: "Mains power", color: "black"},
               {shape: "rect", text: "Battery power", color: "black"}]
    
    this.addLegend(svg, legends, svg.attr("width")*.75, 20)



  }

  addLegend(svg, legends, startX, startY)
  {
    for(var counter=0;counter < legends.length;counter++)
    {
      if (legends[counter].shape == "circle")
      {
        svg.append(legends[counter].shape)
          .attr('cx', startX + 5 )
          .attr('cy',startY + 5 + 20 * counter)
          .attr('r', 5)
          .style("stroke", "black")
          .style("fill", legends[counter].color);
      }
      else
      {
        svg.append(legends[counter].shape)
          .attr('x',startX)
          .attr('y',startY + 20 * counter)
          .attr('width', 10)
          .attr('height', 10)
          .style("stroke", "black")
          .style("fill", legends[counter].color);
      }
      svg.append('text')
        .attr("x", startX + 20)
        .attr("y", startY + 10 + 20*counter)
        .text(legends[counter].text)
        .attr("class", "textselected")
        .style("text-anchor", "start")
        .style("font-size", 15);

    }

  }

  listNodes(hass) {
    let states=new Array();
    for (let state in hass.states)
    {
      states.push({name:state, entity:hass.states[state]});
    }
    let zwaves = states.filter((s) => {return s.name.indexOf("zwave.") ==0});
    let result= {"edges":[], "nodes":[]};

    let hubNode=0;
    let neighbours={};

    for (let b in zwaves)
    {
       let id=zwaves[b].entity.attributes["node_id"]; 
       let node = zwaves[b].entity;
       if (node.attributes["capabilities"].filter(
			(s) => {return s =="primaryController"}).length > 0) 
       {
         hubNode=id;
       }
       neighbours[id]=node.attributes['neighbors'];

       let entities = states.filter((s) => {
                    return ((s.name.indexOf("zwave.") == -1) &&
                            (s.entity.attributes["node_id"] == id)) });
       let batlev=node.attributes.battery_level;       
       let entity={"id":  id,
                   "label": (node.attributes["node_name"] + " (" + node.attributes["averageResponseRTT"]+"ms)").replace(/ /g, "\n"),
                   "class": "unset",
                   "shape": batlev != undefined ? "rect" : "circle",
                   "title": "<b>"+node.attributes["node_name"]+"</b>" +
                            "<br />Node: " + id + (node.attributes["is_zwave_plus"] ? "+" : "") +
                            "<br />Product Name: " + node.attributes["product_name"] +
                            "<br />Average Request RTT: " + node.attributes["averageResponseRTT"]+"ms" + 
                            "<br />Power source: " + (batlev != undefined ? "battery (" + batlev +"%)" : "mains") +
                            "<br />" + entities.length + " entities",
                   "forwards": (node.attributes.is_awake && node.attributes.is_ready && !node.attributes.is_failed &&
                                node.attributes.capabilities.includes("listening"))
                  };

       if (node.attributes["is_failed"])
       {
         entity.label = "FAILED: "+entity.label;
         entity["font.multi"]=true;
         entity["title"]="<b>FAILED: </b>"+entity.title;
         entity["group"]="Failed";
       }

       if (hubNode == id)
       {
         entity.label="ZWave Hub";
         entity.borderWidth= 2;
         entity.fixed=true;
       }

       result.nodes.push(entity);
    }

    
    if (hubNode > 0)
    {
      let layer=0;
      let previousRow=[hubNode];
      let mappedNodes=[hubNode];
      while (previousRow.length > 0)
      {
        layer = layer+1;
        let nextRow=[];
        for (let target in previousRow)
        {
          result.nodes.filter((n) => {return ((n.id ==previousRow[target]) && (n.group="unset"))})
                      .every((d) => {d.class="Layer" + layer;})

          if (result.nodes.filter((n) => {return ((n.id == previousRow[target]) && (n.forwards))}).length > 0)
          {
            let row=neighbours[previousRow[target]];
            for (let node in row)
            {

              if (!mappedNodes.includes(row[node]))
              {
                result.edges.push({"from":row[node], "to":previousRow[target]});
                nextRow.push(row[node]);
              }
            }
          }
        }

        for (let idx in nextRow)
        {
          mappedNodes.push(nextRow[idx]);
        }
        previousRow = nextRow;
      }
    }
    return result;
   }


}
customElements.define(HaPanelZWave.is, HaPanelZWave);
</script>