How can I find entity_id for zwave js scene?

I’m trying to capture scenes in node-red after converting my zwave network to zwave js. My JSON is below but basically entity_id is undefined. However, I do have a node_id. Is there a way to query for entity_id by node_id? Am I missing an obvious way of identifying my entities from this payload?

{
  "payload": {
    "event_type": "zwave_js_value_notification",
    "entity_id": undefined,
    "event": {
      "domain": "zwave_js",
      "node_id": 40,
      "home_id": 4064968902,
      "endpoint": 0,
      "device_id": "548fc616e0def335eca7e79f7d17e634",
      "command_class": 91,
      "command_class_name": "Central Scene",
      "label": "Scene 001",
      "property": "scene",
      "property_name": "scene",
      "property_key": "001",
      "property_key_name": "001",
      "value": "KeyPressed2x",
      "value_raw": 3
    },
    "origin": "LOCAL",
    "time_fired": "2021-10-26T02:26:09.398052+00:00",
    "context": {
      "id": "744588a1505f72565f440fbc40c02027",
      "parent_id": null,
      "user_id": null
    }
  },
  "topic": "zwave_js_value_notification",
  "_msgid": "3589faf5.ecd0b6"
}

That shouldn’t change

How do I map that device_id back to something in Home Assistant?

Using a switch node to filter by that device. Copy the path you get from the debug output.

Sorry, I might be a bit dense today. I get filtering by the device_id once I know the relationship between entity and device. How do I know that light.kitchen corresponds to msg.payload.event.device_id == ‘548fc616e0def335eca7e79f7d17e634’. If I poll that light, I don’t see that device_id anywhere.

I’m not sure what you are trying to accomplish. Lets say I want my media player to turn on when a certain scene is activated.

I have HomeSeer switches HS-WD200 that allow for multi-tap scenes. I have 3 lights in my kitchen.

light.kitchen
light.island
light.cabinets

If I double tap down on any of these switches then I want to turn off all three lights. Before the JS implementation, I listened for zwave.scene_activated on all lights and then built MQTT topics that looked like /scene/{entity_id} with a payload of all the scene data. I then had automations listened for those topics and doing the needful. The challenge I have now is I can’t tell what’s firing the scene. There’s no entity_id, I don’t see a way to query node_id, and I don’t see that device_id is exposed anywhere in the entity.

I have 30 some lights in my house. Knowing it’s a double tap up isn’t useful unless I know what sent it. In the picture of your flow above, do you know the entity that sent the scene when you turn on the media player or do you just know that some scene has been sent and you don’t care what sent it?

Perhaps post an old flow. My take away is, what I posted would work in that scenario.

Would it be possible to see the code behind that flow? My question is still, "how do I know that device_id of “longgobbledgookofnondeterministicdata” is actually an understood entity_id of light.bedroom? I need a translation layer of that key to a known key before I can process anything.

Here’s a subflow I had that parsed the scene data for the switches before but I had entity_id before so I knew the acting switch. I get that device_id may be immutable but they aren’t easily deterministic unless I hard-code a list somewhere and that feels like a maintenance issue. I have a dead switch now I have to replace and I have to make hardware changes once or twice a year and I’d rather not have to rebuild a static list every time I do that.

[{"id":"e31cddcc.41ea1","type":"comment","z":"8dd9445d.1948c8","name":"Scene","info":"","x":190,"y":80,"wires":[]},{"id":"6013efbe.3a657","type":"function","z":"8dd9445d.1948c8","name":"z-wave scene","func":"if (msg.payload.event_type != \"zwave.scene_activated\")\n return null;\n\nvar scene_id = msg.payload.event.scene_id;\nvar scene_data = msg.payload.event.scene_data;\nvar scene_state = scene_id == 1 ? \"on\" : \"off\";\nvar scene_action = \"\"; var scene_tap; var scene_hold = false; var scene_release = false;\nswitch(scene_data) {\n case 0:\n case 7680:\n scene_action = \"Single tap\";\n scene_tap = 1;\n break;\n case 1:\n case 7740:\n scene_action = \"Release\";\n scene_release = true;\n break;\n case 2:\n case 7800:\n scene_action = \"Tap and hold\";\n scene_hold = true;\n break;\n case 3:\n case 7860:\n scene_action = \"Double tap\";\n scene_tap = 2;\n break;\n case 4: \n case 7920:\n scene_action = \"Triple tap\";\n scene_tap = 3;\n break;\n case 7980:\n scene_action = \"Four tap\";\n scene_tap = 4;\n break;\n case 8040:\n scene_action = \"five tap\";\n scene_tap = 5;\n break;\n}\n\nvar entity_id = msg.payload.event.entity_id;\n\nmsg.payload = {\n old: msg.payload,\n node_id: msg.payload.event.node_id,\n entity_id : entity_id,\n scene_state: scene_state,\n scene_tap: scene_tap,\n scene_hold: scene_hold,\n scene_release: scene_release,\n scene_action: scene_action + \" \" + scene_state\n};\n\nflow.set('scene', msg.payload);\n\nreturn msg;\n \n// Action\t scene_id\tscene_data\n// Single tap on\t 1\t 0\n// Single tap off\t 2\t 0\n// Double tap on\t 1\t 3\n// Double tap off\t 2\t 3\n// Triple tap on\t 1\t 4\n// Triple tap off\t 2\t 4\n// Tap and hold on\t 1\t 2\n// Tap and hold off\t 2\t 2\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":160,"wires":[["ce5b671c.dcfb98"]]},{"id":"ce5b671c.dcfb98","type":"function","z":"8dd9445d.1948c8","name":"Parse Request","func":"var scene = flow.get('scene');\nvar id = scene.node_id;\n//node.log('id: ' + id);\n//node.log('entity_id: ' + scene.entity_id);\nvar nodes = global.get(\"zwave-lights\");\nvar matchingNode = nodes.find(x => x.node_id === id);\n\n//node.log('match: ' + JSON.stringify(matchingNode));\n//node.log('match id: ' + matchingNode.node_id);\n//node.log('match entity_id: ' + matchingNode.entity_id);\n\nvar match = { \n entity_id: matchingNode.entity_id, \n node_id: matchingNode.node_id,\n scene: scene.scene_action\n };\nmsg.payload = match;\nreturn msg; \n\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":520,"y":160,"wires":[[]]},{"id":"d725b7ff.7f4c28","type":"function","z":"8dd9445d.1948c8","name":"Build MQTT topic & payload","func":"if (msg.payload.event_type != \"zwave.scene_activated\")\n return null;\n\nvar scene_id = msg.payload.event.scene_id;\nvar scene_data = msg.payload.event.scene_data;\nvar scene_state = scene_id == 1 ? \"on\" : \"off\";\nvar scene_action = \"\"; var scene_tap; var scene_hold = false; var scene_release = false;\nswitch(scene_data) {\n case 0:\n case 7680:\n scene_action = \"Single tap\";\n scene_tap = 1;\n break;\n case 1:\n case 7740:\n scene_action = \"Release\";\n scene_release = true;\n break;\n case 2:\n case 7800:\n scene_action = \"Tap and hold\";\n scene_hold = true;\n break;\n case 3:\n case 7860:\n scene_action = \"Double tap\";\n scene_tap = 2;\n break;\n case 4: \n case 7920:\n scene_action = \"Triple tap\";\n scene_tap = 3;\n break;\n case 7980:\n scene_action = \"Four tap\";\n scene_tap = 4;\n break;\n case 8040:\n scene_action = \"five tap\";\n scene_tap = 5;\n break;\n}\n\nvar nodes = global.get(\"zwave-lights\");\nvar matchingNode = nodes.find(x => x.node_id === msg.payload.event.node_id);\n\n \nmsg.topic = \"scene/\" + matchingNode.entity_id; \nmsg.payload = { \n entity_id: matchingNode.entity_id, \n node_id: matchingNode.node_id,\n scene: {\n text: scene_action,\n state: scene_state,\n tap: scene_tap,\n hold: scene_hold,\n release: scene_release\n }\n};\n \nreturn msg; \n\n \n// Action\t scene_id\tscene_data\n// Single tap on\t 1\t 0\n// Single tap off\t 2\t 0\n// Double tap on\t 1\t 3\n// Double tap off\t 2\t 3\n// Triple tap on\t 1\t 4\n// Triple tap off\t 2\t 4\n// Tap and hold on\t 1\t 2\n// Tap and hold off\t 2\t 2\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":380,"y":120,"wires":[["40db4d4a.e94c74"]]},{"id":"40db4d4a.e94c74","type":"mqtt out","z":"8dd9445d.1948c8","name":"","topic":"","qos":"0","retain":"false","broker":"d4edb5b7.9e7718","x":570,"y":120,"wires":[]},{"id":"91efbf48.62c26","type":"server-events","z":"8dd9445d.1948c8","name":"Scene","server":"68cf2cb3.73adc4","version":1,"event_type":"zwave.scene_activated","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":190,"y":140,"wires":[["d725b7ff.7f4c28","6013efbe.3a657"]]},{"id":"d4edb5b7.9e7718","type":"mqtt-broker","name":"Home Assistant","broker":"192.168.1.151","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"68cf2cb3.73adc4","type":"server","name":"Home Assistant","version":1,"addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

Okay so you were grabbing all the scene events, then using the msg info to route and build your message? You’ll need to add constants in the function node mapping from the device_id to entity_id.

To find the device id for the switches just turn them on and off and watch the debug window.

How I’d filter by device

how I would filter by scene id

That’s what I’m trying to avoid. I know the entity_ids of all my switches. If I replace a dead switch, the new switch will have the same entity_id because it functions as the same switch. This value will always be deterministic. Going around my house and trigger scenes so I can grab a value that won’t persist through the replacement of a switch seems like a massive step backwards. Hard-coding non-deterministic data into an array for lookup purposes makes no sense.

Previously, I could build a list of objects by calling the /api/states endpoint and then parsing every object in my home and filtering out what I needed on node-red start up. Here’s the code I used. It’s over-simplified but I only had one kind of switch and it served its purpose.

var nodes = [];
var match = [];

// first pull all the matching z-wave nodes
for (i = 0; i < msg.payload.length; i++) {
    if (msg.payload[i].attributes.node_id !== undefined) {
        var node_id = msg.payload[i].attributes.node_id;
        var entity_id = msg.payload[i].entity_id;
        if (entity_id.startsWith("zwave.")) {
            nodes.push(node_id);
        }
    }
}

for (i = 0; i < msg.payload.length; i++) {
    if (msg.payload[i].attributes.node_id !== undefined) {
        var node_id = msg.payload[i].attributes.node_id;
        var entity_id = msg.payload[i].entity_id;
        var state = msg.payload[i].state;
        var name = msg.payload[i].attributes.friendly_name;
        name = name.substring(0, name.lastIndexOf(" "));
        var is_dimmable = entity_id.startsWith('light') ? true : false;
        var brightness = msg.payload[i].attributes.brightness || 0;
        if (nodes.includes(node_id) && !entity_id.startsWith("zwave.")  && (entity_id.startsWith("switch.") || entity_id.startsWith("light."))) {
            match.push({ 
                entity_id: entity_id, 
                node_id: node_id,
                state: state,
                power: state == "on" ? true : false,
                name: name,
                brightness: brightness,
                is_dimmable: is_dimmable
            });
        }
    }
}

global.set("zwave-lights", match);
msg = {
    payload: msg.payload,
    lights: match
}
return msg;

Have you looked into what nodered stores globally for the switch entities, perhaps it links the device id there. On the right, refresh to load, homeassistant → states.

Another thing have you tried the zwave js node? It seems to function at a much deeper level.

If anyone else is curious, it’s possible tie node-red and home assistant zwave together. It’s a bit complicated. Make a websocket calls to {"type": "config/device_registry/list"} and {"type": "config/entity_registry/list"}. From there join the two objects device.id = entity_id.device_id. It’s overly complicated but it works.

This got me started Get state_changed Events Based on Area | node-red-contrib-home-assistant-websocket.

2 Likes

I saw this thread and I mistakenly took the suggestion in this thread to use node-red-contrib-zwave-js for a regular zwave setup. I’m commenting so others in my situation won’t make the same mistake.

According to someone answering my question on the Discord chat, node-red-contrib-zwave-js is intended for stand-alone use where you don’t need or want to see the devices in the HA UI. If you want to see your devices in HA as well as node-red, install Zwave JS or Zwave JS to MQTT. (Yes, there is a way to try to tie them together the hard way, but most people don’t want harder.)

One other confusing comment about Zwave JS versus Zwave JS to MQTT. Zwave JS seems to be just the engine for Zwave and, as of right now, the good UI for working with Zwave JS is actually in the Zwave JS to MQTT package. The Zwave JS to MQTT can use its own internal Zwave JS server OR it can point to the Zwave JS package installed separately. Its documentation says to try to use the separate installation instead of the internal one.

It’s confusing to me to be honest to have so many variations and so much documentation about each without clarification at the start of the docs of when to use each and why there are variations.

If you use Zwave JS to MQTT, you get a good UI, you can see your zwave devices in HA and they look like typical devices in node red. I’m not sure why I had to do it the hard way to learn what most people probably stumble on anyway.

Thanks @Jay_Heavner and @kohai-ut for the clues.

Here is some node-red which

  • at startup and occasionally thereafter:
    • call HA’s device and entity list APIs
    • looks for specific model of switches (Homeseer HS-WS200+ in my case)
    • creates a node-red flow context with a dictionary to map a device id to its entity id for the switches that were found
  • on zwave value events:
    • set msg.event_source to the entity_id of the switch that was pressed by using the flow context created above
    • set msg.double_click, msg.triple_click, … by decoding the event attributes
    • set msg.on_click : true when “on” was pressed, false when “off” was pressed

Maybe this helps someone get started.

Here is the node-red JSON:

[{"id":"43a4b0ed.3f8d1","type":"debug","z":"fa216d06.aae75","name":"zwave scene","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":590,"y":100,"wires":[]},{"id":"39eee981.86142e","type":"change","z":"fa216d06.aae75","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"event_payload","tot":"msg"},{"t":"set","p":"event_device","pt":"msg","to":"event_payload.event.device_id","tot":"msg"},{"t":"set","p":"double_click","pt":"msg","to":"event_payload.event.value_raw = 3","tot":"jsonata"},{"t":"set","p":"triple_click","pt":"msg","to":"event_payload.event.value_raw = 4","tot":"jsonata"},{"t":"set","p":"quad_click","pt":"msg","to":"event_payload.event.value_raw = 5","tot":"jsonata"},{"t":"set","p":"on_click","pt":"msg","to":"event_payload.event.property_key = \"001\"","tot":"jsonata"},{"t":"set","p":"event_source","pt":"msg","to":"$lookup($flowContext(\"device_to_entity_id\"), event_device)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":140,"wires":[["43a4b0ed.3f8d1","681ef4c3636b638e"]]},{"id":"4466591b81dad150","type":"server-events","z":"fa216d06.aae75","name":"","server":"1777b72c.cb0709","version":1,"event_type":"zwave_js_value_notification","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":160,"y":140,"wires":[["39eee981.86142e"]]},{"id":"84bcd46297aa4f92","type":"debug","z":"fa216d06.aae75","name":"device to entity map","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":900,"y":40,"wires":[]},{"id":"2c5c24c896af9828","type":"inject","z":"fa216d06.aae75","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"43200","crontab":"","once":true,"onceDelay":"120","topic":"","payload":"","payloadType":"date","x":110,"y":40,"wires":[["1c942bd27e1091bb"]]},{"id":"32223a5e2c057a78","type":"ha-api","z":"fa216d06.aae75","name":"entity_registry","server":"1777b72c.cb0709","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\"type\": \"config/entity_registry/list\"}","dataType":"jsonata","responseType":"json","outputProperties":[{"property":"entities","propertyType":"msg","value":"","valueType":"results"}],"x":480,"y":40,"wires":[["95b9851f2089019b"]]},{"id":"95b9851f2089019b","type":"function","z":"fa216d06.aae75","name":"device to entity map","func":"var switches_devs = {};\nmsg.switches_devs = switches_devs;\n\nvar i;\nvar dev;\nfor (i in msg.devices) {\n    dev = msg.devices[i];\n    if (dev.model == \"HS-WS200+\") {\n        switches_devs[dev.id] = dev;\n    }\n}\n\nvar device_to_entity = {};\nmsg.device_to_entity = device_to_entity;\nflow.set(\"device_to_entity\", device_to_entity);\nvar device_to_entity_id = {};\nmsg.device_to_entity_id = device_to_entity_id;\nflow.set(\"device_to_entity_id\", device_to_entity_id);\nvar ent;\nfor (i in msg.entities) {\n    ent = msg.entities[i];\n    if (ent.device_id in switches_devs && ent.entity_id.startsWith(\"switch.\")) {\n        device_to_entity[ent.device_id] = ent;\n        device_to_entity_id[ent.device_id] = ent.entity_id;\n    }\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":40,"wires":[["84bcd46297aa4f92"]]},{"id":"1c942bd27e1091bb","type":"ha-api","z":"fa216d06.aae75","name":"device_registry","server":"1777b72c.cb0709","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\"type\": \"config/device_registry/list\"}","dataType":"jsonata","responseType":"json","outputProperties":[{"property":"devices","propertyType":"msg","value":"","valueType":"results"}],"x":280,"y":40,"wires":[["32223a5e2c057a78"]]},{"id":"681ef4c3636b638e","type":"link out","z":"fa216d06.aae75","name":"zwave scene","mode":"link","links":["0518473636085915","84083e19e77dd382","ad1b608561d8fe7c"],"x":575,"y":140,"wires":[]},{"id":"1777b72c.cb0709","type":"server","name":"Home Assistant","version":2,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30}]