This is a pretty interesting discussion. For the record with Node RED, it may be possible to write this but @Mutt’s points are totally valid, I don’t really know whether you’d actually get the desired response time. I personally avoid any kind of event loops at this speed as well.
To give this a shot I think you’d want to find out exactly what event is fired on the event bus when the button is pressed and released. You’ll want to listen for those events directly rather then listening for any kind of changes in state otherwise it sounds like you’ll definitely be limited to 1/s. I don’t know what those events are but the easiest way to find out is drop to drop in an Events: All
node with the event type blank that links to a Debug
node set to log out the the complete message object. Deploy this, press the buttons quickly, then immediately disable it. Listening to all events unrestricted for long can create problems (as the node tells you), you just need to capture the event.
Code to import
[{"id":"2ad21885.a7bea","type":"server-events","z":"a74fee2d.ac9068","name":"START_EVENT_TYPE here","server":"cc03735a.94933","event_type":"start_event","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":170,"y":1740,"wires":[["c73c61db.eb751"]]},{"id":"b81f7547.8fed7","type":"traffic","z":"a74fee2d.ac9068","name":"Start/Stop messages","property_allow":"start","filter_allow":"^s$","ignore_case_allow":false,"negate_allow":false,"send_allow":true,"property_stop":"stop","filter_stop":"^e$","ignore_case_stop":false,"negate_stop":false,"send_stop":false,"default_start":false,"differ":false,"x":580,"y":1800,"wires":[["6426c326.c7fcec","a2c4fa54.3197b"]]},{"id":"c73c61db.eb751","type":"change","z":"a74fee2d.ac9068","name":"Set start","rules":[{"t":"set","p":"start","pt":"msg","to":"s","tot":"str"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":1740,"wires":[["b81f7547.8fed7"]]},{"id":"ecc71b4b.f0bef","type":"api-call-service","z":"a74fee2d.ac9068","name":"Change light color","server":"cc03735a.94933","version":1,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"light.osram_cla60_rgbw_osram_00ae2d01_3","data":"{\"hs_color\":[\"{{ color }}\",\"100.0\"]}","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":950,"y":1740,"wires":[[]]},{"id":"6426c326.c7fcec","type":"change","z":"a74fee2d.ac9068","name":"Set color","rules":[{"t":"set","p":"color","pt":"msg","to":"$millis()*10%360","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":780,"y":1740,"wires":[["ecc71b4b.f0bef"]]},{"id":"28212b4e.69a13c","type":"link out","z":"a74fee2d.ac9068","name":"Loop","links":["eb12fb1c.846ae8","5c4a84de.82dee4"],"x":910,"y":1800,"wires":[],"l":true},{"id":"5c4a84de.82dee4","type":"link in","z":"a74fee2d.ac9068","name":"Loop","links":["28212b4e.69a13c"],"x":190,"y":1800,"wires":[["804f901d.799b08"]],"l":true},{"id":"a2c4fa54.3197b","type":"delay","z":"a74fee2d.ac9068","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":770,"y":1800,"wires":[["28212b4e.69a13c"]]},{"id":"f3ecc5df.7e05","type":"server-events","z":"a74fee2d.ac9068","name":"STOP_EVENT_TYPE here","server":"cc03735a.94933","event_type":"stop_event","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":170,"y":1860,"wires":[["93daffdd.a3128"]]},{"id":"93daffdd.a3128","type":"change","z":"a74fee2d.ac9068","name":"Set stop","rules":[{"t":"set","p":"stop","pt":"msg","to":"e","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":1860,"wires":[["b81f7547.8fed7"]]},{"id":"804f901d.799b08","type":"change","z":"a74fee2d.ac9068","name":"Remove start","rules":[{"t":"delete","p":"start","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":1800,"wires":[["b81f7547.8fed7"]]},{"id":"cc03735a.94933","type":"server","z":"","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]
What this should do is the start message will come in and enable and pass through the traffic node, allowing the loop to proceed. It’ll then change the light color and wait 500ms (configurable) before looping again. When the stop event comes in then the traffc node is disabled thus ending the loop until the button is pressed again. Also by forking the path we don’t have to wait for the light to actually change in addition to 500ms, they happen in parallel.
By listening on the HA event bus and forking the service call I think this might be closer to what you want but there’s still a lot of X factors here, I don’t know how close it will actually be to the desired loop time and behavior
Also I feel obliged to ask, is this really easier then just opening your phone and using a color wheel to pick the color? I know, that’s completely counter to the core HA message (using your phone to change the lights is only useful for showing off) but I feel like this is one of the few cases where its actually easier. Picking a color from a normal color wheel sounds a lot easier and faster here.
Or alternatively, if you just have like a set of 5-10 colors you want to use in practice, maybe just make it so clicking the button repeatedly scrolls through the wheel? I just feel like this kind of loop maybe isn’t the most practical way to change the color of your lights. Just my two cents.