Hi, I’m trying to make a dimmer too. I just found out, that an automation with this data_template is not too optimal, because HA is handling every single pulse, the resolution just send multiple pulses at once. Any ideas, how to send only every 4th pulse as example? Or how to handle every 4th pulse in HA.
OK, I found a solution:
sensor:
- platform: rotary_encoder
name: "Rotary Encoder"
pin_a: PIN_A
pin_b: PIN_B
min_value: 0
max_value: 100
resolution: 2
filters:
- delta: 4.0
With delta, it will send data to HA if the difference is greater than delta’s value.
My automation:
- id: '1569433349039'
alias: Bedroom Light Dimming
trigger:
- entity_id: sensor.rotary_encoder
platform: state
condition: []
action:
- data_template:
brightness_pct: '{{states.sensor.rotary_encoder.state | int }}'
service: light.turn_on
entity_id: light.bedroom_light
Thanks guys! I really appreciate your help! Other on-going project kept me away from the dimmer, but I will resume it asap to integrate in my home setup.
Hi all
I have been messing about with this for a while. I have rotary encoders wired to wemos d1 minis where my dimmers would be on the walls and zigbee lights in the ceiling
Originally I created some custom MQTT code for the dimmers which was successful, but behaved a bit weirdly.
I now have what I believe is the best solution:
The rotarys just send status to home assistant through esphome
Then I use node red to handle the switch and the rotary movement.
The issue I was coming across was that every time the rotary is moved, the brightness is changed. If you rotate it fast, it generates a huge load/lag on the system while it catches up sending instructions to the lights.
So I installed ‘debounce’ in node red and set up a limit of 250ms with a counter. Basically, when you turn the rotary, node red counts how many times you’re rotating it in that 250ms and then turns that into a command for the lights.
This way, if you want a big adjustment, you rotate it fast and alot and if you want better control you rotate it slowly. There’s still a tiny lag (which is inevitable), but the whole system works brilliantly now.
I also went ahead and added a virtual switch to the esphome which is toggled by a long press on the dimmer. This is set to toggle the colour mode, so I can control brightness and colour temperature at the wall.
Happy to share any code, etc. if this is useful to anyone.
I’d like the code of your flow! I want to use òn_clockwise
and òn_anticlockwise
to increase and decrease brightness. Seems as if Node-RED is the way to go.
Okey dokey.
So hopefully you can make sense of this.
I have two subflows. 1 is for rooms with colour temperature changing lights and adds a long press to the rotary encoder that switches to colour change mode instead of brightness. This is what my main flow looks like:
The brains for the rotary adjustment is in the dimmerControl subroutine. This has some hopefully self explanatory environmental variables:-
Then these are passed into the subflow:-
The magic happens at the top with the debounce node in combination with a counter. It’ll pass a number of how many times it’s been rotated in 250ms through the flow. The rest of the flow handles the environmental variables and then increments or decrements the brightness. It’ll stop at the max brightness set in the environmental variable and turn the light off if it hits minimum brightness.
Here’s the subflow:-
[{"id":"f4daa825.5dd3f8","type":"subflow","name":"dimmerControl (no colour)","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"2af0480a.6ebf38"}]}],"out":[],"env":[{"name":"light","type":"str","value":""},{"name":"brightstep","type":"num","value":"5"},{"name":"brightmin","type":"num","value":"5"},{"name":"brightmax","type":"str","value":"255"}],"color":"#DDAA99"},{"id":"2af0480a.6ebf38","type":"counter","z":"f4daa825.5dd3f8","name":"","init":"0","step":"1","lower":null,"upper":null,"mode":"increment","outputs":"1","x":180,"y":80,"wires":[["ce11729f.961e3"]]},{"id":"aa88fac1.a39c28","type":"debounce","z":"f4daa825.5dd3f8","time":"250","name":"debounce","x":520,"y":80,"wires":[["2b2bdf57.7b2f3","c536d7.0e53b928"]]},{"id":"2b2bdf57.7b2f3","type":"change","z":"f4daa825.5dd3f8","name":"reset count","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":670,"y":80,"wires":[["2af0480a.6ebf38"]]},{"id":"ce11729f.961e3","type":"switch","z":"f4daa825.5dd3f8","name":"if non-zero","property":"count","propertyType":"msg","rules":[{"t":"neq","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":350,"y":80,"wires":[["aa88fac1.a39c28"]]},{"id":"b7c452a6.45573","type":"comment","z":"f4daa825.5dd3f8","name":"How many times have we moved in 250ms interval?","info":"","x":310,"y":40,"wires":[]},{"id":"992273c2.55bc","type":"api-current-state","z":"f4daa825.5dd3f8","name":"Current state - light","server":"e733376d.c37508","version":1,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"${light}","state_type":"str","state_location":"state","override_payload":"msg","entity_location":"lightdata","override_data":"msg","blockInputOverrides":false,"x":230,"y":280,"wires":[["5d3ed6e8.5482c8"]]},{"id":"5d3ed6e8.5482c8","type":"function","z":"f4daa825.5dd3f8","name":"Increment or Decrement?","func":"if (Number(msg.data.old_state.state) < Number(msg.data.new_state.state)) {\n return [ msg, null ];\n} else {\n return [ null, msg ];\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","x":450,"y":360,"wires":[["92e9856d.0fee18"],["4fbf4574.3ea10c"]]},{"id":"2ec45dd6.827172","type":"change","z":"f4daa825.5dd3f8","name":"+ brightness","rules":[{"t":"set","p":"lightdata.attributes.brightness","pt":"msg","to":"$min([$number(lightdata.attributes.brightness + ($number(env.brightstep)*count)), $number(env.brightmax)])","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":500,"wires":[["73047e8d.2f322"]]},{"id":"4fdc3278.fd253c","type":"change","z":"f4daa825.5dd3f8","name":"- brightness","rules":[{"t":"set","p":"lightdata.attributes.brightness","pt":"msg","to":"$max([$number(brightmin), $number(lightdata.attributes.brightness - ($number(env.brightstep)*count))])","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":660,"wires":[["73047e8d.2f322"]]},{"id":"73047e8d.2f322","type":"api-call-service","z":"f4daa825.5dd3f8","name":"light_on","server":"e733376d.c37508","version":1,"debugenabled":false,"service_domain":"light","service":"turn_on","entityId":"${light}","data":"{\"brightness\": {{lightdata.attributes.brightness}}}","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":820,"y":500,"wires":[[]]},{"id":"4fbf4574.3ea10c","type":"switch","z":"f4daa825.5dd3f8","name":"brightness at bright min?","property":"lightdata.attributes.brightness","propertyType":"msg","rules":[{"t":"gt","v":"brightmin","vt":"env"},{"t":"lte","v":"brightmin","vt":"env"}],"checkall":"true","repair":false,"outputs":2,"x":370,"y":700,"wires":[["4fdc3278.fd253c"],["247dca32.696136"]]},{"id":"247dca32.696136","type":"api-call-service","z":"f4daa825.5dd3f8","name":"light_off","server":"e733376d.c37508","version":1,"debugenabled":false,"service_domain":"light","service":"turn_off","entityId":"${light}","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":800,"y":700,"wires":[[]]},{"id":"c536d7.0e53b928","type":"change","z":"f4daa825.5dd3f8","name":"copy env vars","rules":[{"t":"set","p":"env.brightmin","pt":"msg","to":"brightmin","tot":"env"},{"t":"set","p":"env.brightstep","pt":"msg","to":"brightstep","tot":"env"},{"t":"set","p":"env.brightmax","pt":"msg","to":"brightmax","tot":"env"}],"action":"","property":"","from":"","to":"","reg":false,"x":140,"y":200,"wires":[["992273c2.55bc"]]},{"id":"92e9856d.0fee18","type":"switch","z":"f4daa825.5dd3f8","name":"light off?","property":"lightdata.state","propertyType":"msg","rules":[{"t":"neq","v":"off","vt":"str"},{"t":"eq","v":"off","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":360,"y":540,"wires":[["2ec45dd6.827172"],["581a8f95.14cfa"]]},{"id":"581a8f95.14cfa","type":"change","z":"f4daa825.5dd3f8","name":"brightness to min bright","rules":[{"t":"set","p":"lightdata.attributes.brightness","pt":"msg","to":"$number(env.brightmin)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":560,"wires":[["73047e8d.2f322"]]},{"id":"8cd6de54.b82f8","type":"comment","z":"f4daa825.5dd3f8","name":"Take the environmental vars into the msg","info":"","x":200,"y":160,"wires":[]},{"id":"6562bb6f.296884","type":"comment","z":"f4daa825.5dd3f8","name":"Get the current state of the light - msg. state msg.lightdata","info":"","x":250,"y":240,"wires":[]},{"id":"dbcc9e65.15529","type":"comment","z":"f4daa825.5dd3f8","name":"Rotary increased or decreased?","info":"","x":170,"y":320,"wires":[]},{"id":"e733376d.c37508","type":"server","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]
I hope that’s understandable!
Thank you for the response!
Before you answered, I was playing around and came up with this:
The top one is for adjusting the brightness with rotation, and the bottom one turns the light on and off by pushing the knob.
I use this in conjunction with a template sensor that I feed into esphome, so that if I change the brightness in any other way than with the rotary encoder, the rotary encoder gets that value. I also set the min and max for the enoder to 0 and 100.
Apart from the color control, is there anything your flow does that mine doesn’t? I feel like I might be missing something.
So yours will slow the adjustment to a maximum of 1 step per 250ms.
Mine counts the steps made per 250ms and then fires off a brightness command accounting for the steps.
The result is that you can turn the rotary fast and get a larger brightness change, or turn it slow for a more delicate change.
Apart from that, it just deals with minimum and maximum conditions (turning off below minimum, turning on at minimum if rotated while off).
Actually, the flow above does that too. The debounce node passes on a maximum of 1 value per 250 ms, but that doesn’t mean that it only passes on 1 brightness step up or down. If I turn the rotary enconder fast so that it goes from 10 to 45 in 250 ms, then the debounce node will pass on the value 45 to the set-brightness-node, but omit all the steps between 10 and 45.
It also turns off when brightness is turned down to 0% and turns on if brightness is increased from 0%. I set the stepsize to 4 in the esphome rotary button config to avoid having to turn the know a 100 times for 100%.
If you want to simplify your flow, I think the above one would do the same as yours
Ah ok. Yeah I see. It’s because you’ve got the rotary value attached to the light.
I think at some point I abandoned that idea. I have 3 dimmers in the master bedroom and it was adding lag updating all the values.
This would be very useful to me. If you could share the code would be great.
to whoever bumps into this topic:
EspHome has a built in debounce filter
My code is this; I have a 20 step encoder. The first sensor is the rotary encoder itself, with debounce. The second sensor is receiving the new state from HA in case the state is changed from the frontend.
sensor:
- platform: rotary_encoder
name: "Aanrecht dimmer knop"
id: my_rotary_encoder
resolution: 1
min_value: 0
max_value: 20
pin_a:
number: GPIO26
mode:
input: true
pullup: true
pin_b:
number: GPIO27
mode:
input: true
pullup: true
filters:
debounce: 0.25s
- platform: homeassistant
name: Actual Light State
id: actual_light_state
entity_id: input_number.aanrecht_dimmer_knop_value
on_value:
then:
- sensor.rotary_encoder.set_value:
id: my_rotary_encoder
value: !lambda 'return id(actual_light_state).state ;'
This automation translates the value from the rotary encoder (0-20) to a brightness value (0-255) following a square root function. I like this better than a linear profile. sqrt(encoder * 3250)
alias: 'Keuken aanrecht: rotary dim kitchen light'
description: ''
trigger:
- platform: state
entity_id: sensor.aanrecht_dimmer_knop
condition: []
action:
- service: light.turn_on
target:
entity_id: light.keuken_aanrecht_groep
data:
brightness: '{{ (((trigger.to_state.state | float(0)) * 3250)**0.5) | round(0) }}'
mode: single
This function saves the actual brightness to an input_number
following the inverse of the function above, to correlate brightness with the steps of the encoder. Trick here is the restart
mode in combination with the 2 seconds delay; it functions as a debounce filter. (brightness^2)/3250
alias: 'Keuken aanrecht: Sync rotary kitchen light'
description: ''
trigger:
- platform: state
entity_id: light.keuken_aanrecht_groep
attribute: brightness
condition: []
action:
- delay:
hours: 0
minutes: 0
seconds: 2
milliseconds: 0
- service: input_number.set_value
target:
entity_id: input_number.aanrecht_dimmer_knop_value
data:
value: >-
{{ ((float(state_attr("light.keuken_aanrecht_groep", "brightness"),0)|
int )**2/3250) | round(0) }}
mode: restart
This input_number
is picked up by ESPHome and used as set_value
for the rotary encoder.
I ran into an issue. Both the light (in HA) and the ESP were maintaining the absolute dimmer position. I had to keep them in sync which was a pain; updates were bouncing back and forth.
I have now changed the ESP to only issue an incremental value, generated within the debounce time frame. It creates a number
in HA.
There is a global variable which stores the previous encoder position and it is subtracted from the current encoder position.
I hope this helps someone
globals:
- id: aanrechtoldvalue
type: int
restore_value: no
initial_value: '0'
number:
- platform: template
name: Aanrecht Increment
id: aanrechtincrement
min_value: -20
max_value: 20
step: 1
optimistic: true
initial_value: 0
sensor:
- platform: rotary_encoder
name: "Aanrecht dimmer knop"
id: aanrecht_rotary_encoder
resolution: 1
pin_a:
number: GPIO26
mode:
input: true
pullup: true
pin_b:
number: GPIO27
mode:
input: true
pullup: true
filters:
debounce: 0.25s
on_value:
then:
- number.set:
id: aanrechtincrement
value: !lambda 'return id(aanrecht_rotary_encoder).state - id(aanrechtoldvalue);'
- globals.set:
id: aanrechtoldvalue
value: !lambda 'return id(aanrecht_rotary_encoder).state;'
The automation in HA picks up the new number
value and issues a brightness_step
command with that increment. I have 20 steps for a brightness resolution of 255, so multiplied by 13. There is also a pir sensor looking at the dimmer knob, so I have to disable that for a short while. The automation is queued so multiple number
changes are being picked up correctly.
alias: 'Keuken aanrecht: rotary dim kitchen light (increment)'
description: ''
trigger:
- platform: state
entity_id: number.aanrecht_increment
condition: []
action:
- service: light.turn_on
target:
entity_id: light.keuken_aanrecht_groep
data:
brightness_step: '{{ (((trigger.to_state.state | float(0)) * 13)) | round(0) }}'
- service: input_boolean.turn_on
target:
entity_id: input_boolean.aanrecht_override_pir
- delay:
hours: 0
minutes: 0
seconds: 30
milliseconds: 0
- service: input_boolean.turn_off
target:
entity_id: input_boolean.aanrecht_override_pir
mode: queued
max: 10
and
alias: 'Keuken Aanrecht: Reset Override PIR'
description: ''
trigger:
- platform: state
entity_id: input_boolean.aanrecht_override_pir
to: 'on'
condition: []
action:
- delay:
hours: 0
minutes: 0
seconds: 30
milliseconds: 0
- service: input_boolean.turn_off
target:
entity_id: input_boolean.aanrecht_override_pir
mode: restart
Thanks, I will try this. It seems better to do most the processing in ESP.
Hello
Thank you for The code, works pretty well. Smart solution,
Would it be possible to limit The dimmer value so it doesent turn of The light? Just stop on The minimum value.
Because i use The switch for on off toogle.
This was really useful. Especially since I’ll also be using this for my keuken! Many thanks.
I’m trying to configure a rotary encoder as described above, but it did not work for me. Then I simplified the code (see below), just to check if I would get a log message and not even that worked. I also tested with and without the debounce filter and using inverted and pullup as both true and false. None of the combinations worked for me. My encoder does not have a breakout board, as it would not fit the limited space I have, but I think this should not be a problem, as the ESP32 already has pullup in its breakout board. I did check the encoder for continuity with a meter and it seems ok., so it is not a faulty component.
Here is the code I used:
sensor:
- platform: rotary_encoder
name: "dim_Lucas"
pin_a:
number: 18
inverted: true
mode:
input: true
pullup: true
pin_b:
number: 19
inverted: true
mode:
input: true
pullup: true
id: dim_lucas
resolution: 1
min_value: 0
max_value: 25
on_clockwise:
- logger.log: "Turned Clockwise"
on_anticlockwise:
- logger.log: "Turned Anticlockwise"
Any ideas on how to solve this?
How do you have things wired? Is the light controlled by the same esp board that the rotary encoder is on? You only show the rotary encoder part of your code so what type of light are you using? What hardware are you using? Is there a mosfet in here?. You’ve only given half the equation here. There are several eats to do this but if the light Is on the same board, is it even dimmable, do uou have the hardware to dim it, the type of light, etc all matter if you
Are you even seeing the rotary encoder values change in your logs? That’s how you know if it’s working or not. No need to speculate or wonder if it’s working, just look at the logs. The number should increase when spun one direction and decrease the other direction. Are you sure it’s a rotary encoder and not a potentiometer? They can look identical but function completely different. You’ve not provided much information, that’s why no one is answering youm
Thanks @Fallingaway24 for the reply.
The hardware is based on mosfet boards powering up COB LEDs, controlled by the same ESP32 connected to the rotary encoder. The lights work fine when controlled directly by HA but I’m struggling with the rotary encoder. I found the cause of the problem I mentioned earlier, it was related to the ESP pins I used. I was not even getting anything from it in the logs, but when I changed the pins to 32 and 33 it started working.
The problem now is how to get a stable signal from the rotary encoder. I created a new post, as this is a different problem. I will either have to replace the cable or add a new ESP32 closer to the encoder and use a different programming approach.