Wow pretty ! I may steal your idea!
Is there anyway to do this directly in Home Assistant without installing Node Red?
I suppose you could create an automation (and therefore a blueprint) that intercepts the events and implements the logic that my Node-RED flow implements for you, but it would require a bunch of helpers to remember the click timeout and state machine state.
I only did it in Node Red because I contemplated doing it in Home Assistant directly, and I failed.
Best of luck to you.
@Rudd-O very nice and detail work, I just implanted for two bedside Tap dials, now I am trying to adjust brightness and RGB controls, just like how you had volume media control, do you have a subflow for that you can share?
What to look for instead of ZHA events if I am using ZB2MQTT? Thanks.
You should be able to see the device inside the dropdown of a device(pink) node. Either that or the mqtt in and listen to it’s topic. Connect to a debug with it set to complete message object. Then use a switch node to break up the individual commands.
I don’t because I’m not using the Tap dial for lights, but it should be easy to have a function node that remembers the last level from 0 to 255 using context.get()
and context.set()
, adds or subtracts from that level based on the amount rotated (this is provided by my node), and sends that to a service call light.turn_on
Home Assistant Node Red Websocket node.
Not sure how Z2M event packets look like. But I bet it’s a 1:1 transformation to what the subflow I made wants.
did anybody got this working with Zigbee2Mqtt so far?
i think i have to change something on the Events node but not sure what
Not sure why you would use an event node when it’s broadcasting to mqtt. There are dedicated mqtt nodes, you just need to set it to a topic. If z2m creates devices the device node should work as well.
Not to mention the specific z2m nodes.
That’s awesome! Been looking to replace the buttons with dedicated icons on mine and yours is the first post I’ve seen which does exactly that!.
Do you have an stl file or any pointers where I could find one?
Hi,
have someone got it to work with lights and dimming them up and down? I dont get it to work, for some reason.
Offtopic good sir.
Hi, meanwhile i got it working. But i dont know how to get the long-press-event for each button in ZHA …
for each button, that i press long, i got the same ZHA event in my Log
hue_tap_dial Step With On Off event was fired with parameters: {‘step_mode’: <StepMode.Down: 1>, ‘step_size’: 255, ‘transition_time’: 8}
any suggestion? Till now i switch the radio-stations with the doubleclick of button with 4dots
i will share my flow in next week for others who wants to do this with ZHA instead of deConz
regards
The Node-RED flow is supposed to process those double taps for you.
Hi,
first thanks for your answer. But i think u havent understand me. I wanted to create a 8th exit, that fires, when there is a longpress event for each button. I think the long-press-event is easy to get (transitione time= 8) but i dont get the button, that was long pressed …
when i see your payload, there is no difference between a longpress of button 1 or 2 that i could use …
i have changed the subflow for the additional longpress event, maybe someone can have a look, to add the right button
[{"id":"4e994db4b13653d5","type":"subflow","name":"Hue Tap Dial longpress","info":"","category":"","in":[{"x":180,"y":200,"wires":[{"id":"68fbdca716ada157"}]}],"out":[{"x":2120,"y":400,"wires":[{"id":"0bd68f511154ccc1","port":3}]},{"x":2130,"y":440,"wires":[{"id":"0bd68f511154ccc1","port":4}]},{"x":2130,"y":260,"wires":[{"id":"0bd68f511154ccc1","port":0}]},{"x":2130,"y":300,"wires":[{"id":"0bd68f511154ccc1","port":1}]},{"x":2130,"y":340,"wires":[{"id":"0bd68f511154ccc1","port":2}]},{"x":2140,"y":480,"wires":[{"id":"0bd68f511154ccc1","port":5}]},{"x":1050,"y":640,"wires":[{"id":"59b5d0e44c355dad","port":7}]}],"env":[],"meta":{},"color":"#C0DEED","inputLabels":["zha"],"outputLabels":["up","down","click","double","tripple","longpress","else"],"status":{"x":1280,"y":580,"wires":[{"id":"66b32dad9f5784b3","port":0}]}},{"id":"68fbdca716ada157","type":"function","z":"4e994db4b13653d5","name":"Reformat data","func":"Object.keys(msg.payload.event).forEach((key, index) => {\n msg.payload[key] = msg.payload.event[key];\n})\ndelete msg.payload[\"event\"];\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":320,"wires":[["59b5d0e44c355dad"]]},{"id":"59b5d0e44c355dad","type":"switch","z":"4e994db4b13653d5","name":"Knob action","property":"msg","propertyType":"jsonata","rules":[{"t":"jsonata_exp","v":"payload.command = \"step_with_on_off\" and payload.params.step_mode = 0 and payload.params.transition_time = 4","vt":"jsonata"},{"t":"jsonata_exp","v":"payload.command = \"step_with_on_off\" and payload.params.step_mode = 1 and payload.params.transition_time = 4","vt":"jsonata"},{"t":"jsonata_exp","v":"payload.command = \"recall\" and payload.params.scene_id = 1","vt":"jsonata"},{"t":"jsonata_exp","v":"payload.command = \"recall\" and payload.params.scene_id = 0","vt":"jsonata"},{"t":"jsonata_exp","v":"payload.command = \"recall\" and payload.params.scene_id = 5","vt":"jsonata"},{"t":"jsonata_exp","v":"payload.command = \"recall\" and payload.params.scene_id = 4","vt":"jsonata"},{"t":"jsonata_exp","v":"payload.command = \"step_with_on_off\" and payload.params.transition_time = 8","vt":"jsonata"},{"t":"else"}],"checkall":"false","repair":false,"outputs":8,"x":670,"y":320,"wires":[["98993c23f8e6a409"],["5214626b1788a042"],["6091155c8c2f9ed7"],["a0964ec7c08edf2b"],["28ccde4ba1ea1972"],["ac951353ca516ebb"],["42a089976e19329d"],[]],"outputLabels":["up","down","scene1","scene2","scene3","scene4","longpress","else"]},{"id":"98993c23f8e6a409","type":"function","z":"4e994db4b13653d5","name":"Reformat","func":"msg.payload = {\n \"command\": \"up\",\n \"step_size\": msg.payload.params.step_size / 8,\n \"transition_time\": msg.payload.params.transition_time,\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":180,"wires":[["7e9d63211b95ace7"]]},{"id":"5214626b1788a042","type":"function","z":"4e994db4b13653d5","name":"Reformat","func":"msg.payload = {\n \"command\": \"down\",\n \"step_size\": msg.payload.params.step_size / 8,\n \"transition_time\": msg.payload.params.transition_time,\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":220,"wires":[["7e9d63211b95ace7"]]},{"id":"6091155c8c2f9ed7","type":"function","z":"4e994db4b13653d5","name":"Reformat","func":"msg.payload = {\n \"command\": \"click\",\n \"button\": msg.payload.params.scene_id,\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":260,"wires":[["7e9d63211b95ace7"]]},{"id":"a0964ec7c08edf2b","type":"function","z":"4e994db4b13653d5","name":"Reformat","func":"msg.payload = {\n \"command\": \"click\",\n \"button\": 2,\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":300,"wires":[["7e9d63211b95ace7"]]},{"id":"28ccde4ba1ea1972","type":"function","z":"4e994db4b13653d5","name":"Reformat","func":"msg.payload = {\n \"command\": \"click\",\n \"button\": 3,\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":340,"wires":[["7e9d63211b95ace7"]]},{"id":"ac951353ca516ebb","type":"function","z":"4e994db4b13653d5","name":"Reformat","func":"msg.payload = {\n \"command\": \"click\",\n \"button\": msg.payload.params.scene_id,\n}\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":380,"wires":[["7e9d63211b95ace7"]]},{"id":"252c28a0af3018ca","type":"trigger","z":"4e994db4b13653d5","name":"","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"250","extend":true,"overrideDelay":true,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":1400,"y":340,"wires":[["7e9d63211b95ace7"]]},{"id":"7e9d63211b95ace7","type":"function","z":"4e994db4b13653d5","name":"Process delayed taps","func":"var last = context.get(\"last_active\")\nvar current = {\n time: Date.now(),\n msg: msg\n};\n\nfunction fmt(msg) {\n if (msg.payload.command == \"click\" || msg.payload.command == \"doubleclick\" || msg.payload.command == \"tripleclick\") {\n return \"\" + msg.payload.command + \" of button \" + msg.payload.button + (msg.selection ? \" (mode \" + msg.selection + \")\" : \"\");\n }\n else if (msg.payload.command == \"up\" || msg.payload.command == \"down\") {\n return \"\" + msg.payload.command + \" of step size \" + msg.payload.step_size + (msg.selection ? \" (mode \" + msg.selection + \")\" : \"\");\n }\n return JSON.stringify(msg);\n}\n\nfunction status(msg, buffering) {\n node.status({ text: msg, fill: \"blue\", shape: buffering ? \"ring\" : \"dot\"});\n}\n\nif (current.msg.flush) {\n if (last !== undefined && last !== null) {\n context.set(\"last_active\", null);\n status(\"flushing \" + fmt(last.msg), false);\n return [[last.msg], null];\n }\n}\nelse if (current.msg.payload.command == \"click\") {\n if (last === undefined || last === null) {\n context.set(\"last_active\", current);\n status(\"buffering \" + fmt(current.msg), true);\n return [null, { flush: true }];\n } else {\n if (\n current.msg.payload.button == last.msg.payload.button\n ) {\n if (\n last.msg.payload.command == \"click\"\n ) {\n // Instead of queueing the current message,\n // update the last message.\n last.msg.payload.command = \"doubleclick\";\n status(\"rebuffering \" + fmt(last.msg), true);\n return [null, { flush: true }];\n }\n else if (\n last.msg.payload.command == \"doubleclick\"\n ) {\n last.msg.payload.command = \"tripleclick\";\n context.set(\"last_active\", null);\n status(\"flushing \" + fmt(last.msg), false);\n return [[last.msg], null];\n }\n else {\n // This is an error\n node.error({msg:\"Cannot be\", current: current, last: last})\n }\n } else { /* buttons don't match */\n context.set(\"last_active\", current);\n status(\"flushing \" + fmt(last.msg) + \" and buffering \" + fmt(current.msg), true);\n return [[last.msg], { flush: true }];\n }\n }\n}\nelse {\n if (last === undefined || last === null) {\n status(\"relaying \" + fmt(current.msg), false);\n return [current.msg];\n }\n else {\n context.set(\"last_active\", null);\n status(\"relaying previous \" + fmt(last.msg) + \" and current \" + fmt(current.msg), false);\n return [[last.msg, current.msg], null];\n }\n}\n","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1400,"y":240,"wires":[["705858de13af6112"],["252c28a0af3018ca"]],"outputLabels":["output","flush request"]},{"id":"0bd68f511154ccc1","type":"function","z":"4e994db4b13653d5","name":"switch","func":"if (msg.payload.command == \"click\") {\n return [msg, null, null, null, null];\n}\nif (msg.payload.command == \"doubleclick\") {\n return [null, msg, null, null, null];\n}\nif (msg.payload.command == \"tripleclick\") {\n return [null, null, msg, null, null];\n}\nif (msg.payload.command == \"up\") {\n return [null, null, null, msg, null];\n}\nif (msg.payload.command == \"down\") {\n return [null, null, null, null, msg];\n}\nif (msg.payload.command == \"longpress\") {\n return [null, null, null, null, msg];\n}\n","outputs":6,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1830,"y":320,"wires":[[],[],[],[],[],[]]},{"id":"66b32dad9f5784b3","type":"status","z":"4e994db4b13653d5","name":"","scope":["7e9d63211b95ace7"],"x":1060,"y":580,"wires":[[]]},{"id":"705858de13af6112","type":"function","z":"4e994db4b13653d5","name":"Selection func","func":"function getSelection() {\n return context.get(\"selection\");\n}\n\nfunction cloneWithSelection(oldm) {\n var m = RED.util.cloneMessage(oldm);\n var g = getSelection();\n if (g !== null && g !== undefined) {\n m.payload.last_clicked_button = g;\n }\n return m;\n}\n\nfunction setSelection(sel) {\n context.set(\"selection\", sel);\n}\n\nmsg = cloneWithSelection(msg);\n\nif (msg.payload.command == \"click\") {\n setSelection(msg.payload.button);\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1660,"y":260,"wires":[["0bd68f511154ccc1"]]},{"id":"42a089976e19329d","type":"function","z":"4e994db4b13653d5","name":"Reformat","func":"msg.payload = {\n \"command\": \"longpress\"\n }\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":460,"wires":[["0bd68f511154ccc1"]]}]
Ah yeah my flow doesn’t handle long presses. It’ll have to be modified.
Contributions welcome!