Make the Philips Hue Tap dial control anything in your home, using Node-RED

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!

Hello
I’m also trying to get a bit more out of the Hue Tap Dial.
@Valarie would you mind sharing how you got volume, double & triple click working with ZHA?
Right now I’m using this blueprint but it’s a bit limited in options…

Hi @Thyraz, I’m also interested in the STL file if you’d like to share :blush:

1 Like

Hi,
I would like to know how to " 4. Add an instance of the Hue Tap subflow to your palette, and connect the Events: all node output to the Hue Tap node’s input."
With Google I can’t find any explanation how to connect a node output to a subnote input.

Could you or anybody please give me a hint?