- Improve calculations for ZHA ERS light dial (remove double-click power)
- Add Z2M port of ERS light dial
This commit is contained in:
		| @ -1,9 +1,11 @@ | |||||||
| # Instructions: https://community.home-assistant.io/t/zha-control-light-color-hue-brightness-with-ers-dial/595002 | # Instructions: https://community.home-assistant.io/t/zha-z2m-control-light-color-hue-brightness-with-ers-dial/595002 | ||||||
| 
 | 
 | ||||||
| blueprint: | blueprint: | ||||||
|   name: ERS Rotary Dial - Light Control |   name: ERS Rotary Dial - Light Control (Z2M) - EVENT mode | ||||||
|   description: Control light brightness, hue and color with an ERS rotary dial |   description: Control light brightness, hue and color with an ERS rotary dial | ||||||
| 
 | 
 | ||||||
|  |   source_url: https://raw.githubusercontent.com/nwithan8/configs/main/home_assistant/blueprints/automations/ers_rotary_dial_light_control_z2m.yaml | ||||||
|  | 
 | ||||||
|   domain: automation |   domain: automation | ||||||
| 
 | 
 | ||||||
|   input: |   input: | ||||||
| @ -71,7 +73,17 @@ variables: | |||||||
|   hue_tracker: !input "hue_tracker" |   hue_tracker: !input "hue_tracker" | ||||||
|   brightness_tracker: !input "brightness_tracker" |   brightness_tracker: !input "brightness_tracker" | ||||||
|   color_tracker: !input "color_tracker" |   color_tracker: !input "color_tracker" | ||||||
|   positive: "{{ trigger.event.data.command == 'step' and trigger.event.data.params.step_mode == 0 }}" |   command: "{{ trigger.to_state.state }}" | ||||||
|  |   single_pressed: "{{ command == 'single' }}" | ||||||
|  |   double_pressed: "{{ command == 'double' }}" | ||||||
|  |   rotated: "{{ command in ['rotate_right', 'rotate_left'] }}" | ||||||
|  |   positive: "{{ command == 'rotate_right' }}" | ||||||
|  |   step_size: > | ||||||
|  |     {% if rotated %} | ||||||
|  |       {{ trigger.to_state.attributes.action_step_size | int }} | ||||||
|  |     {% else %} | ||||||
|  |       {{ 0 }} | ||||||
|  |     {% endif %} | ||||||
|   # How many steps to go full rotation (min 13 per step * 20 steps per full rotation) |   # How many steps to go full rotation (min 13 per step * 20 steps per full rotation) | ||||||
|   full_rotate_step_count: "{{ 13 * 20 | int }}" |   full_rotate_step_count: "{{ 13 * 20 | int }}" | ||||||
|   current_mode: "{{ states(mode_tracker) | int }}" |   current_mode: "{{ states(mode_tracker) | int }}" | ||||||
| @ -88,12 +100,7 @@ variables: | |||||||
|   max_hue: "{{ state_attr(hue_tracker, 'max') | int }}" |   max_hue: "{{ state_attr(hue_tracker, 'max') | int }}" | ||||||
|   # How many steps to go full min-max spectrum |   # How many steps to go full min-max spectrum | ||||||
|   hue_steps: "{{ full_rotate_step_count * 2 | int }}" |   hue_steps: "{{ full_rotate_step_count * 2 | int }}" | ||||||
|   hue_delta: > |   hue_delta: "{{ (step_size | float(0) / hue_steps) * (max_hue - min_hue) }}" | ||||||
|     {% if trigger.event.data.command != 'step' %} |  | ||||||
|       {{ 0 }} |  | ||||||
|     {% else %} |  | ||||||
|       {{ (trigger.event.data.params.step_size | float(0) / hue_steps) * (max_hue - min_hue) }} |  | ||||||
|     {% endif %} |  | ||||||
|   # don't loop when reached max or min |   # don't loop when reached max or min | ||||||
|   next_hue: > |   next_hue: > | ||||||
|     {%- set val = states(hue_tracker) | float(0) -%} |     {%- set val = states(hue_tracker) | float(0) -%} | ||||||
| @ -112,12 +119,7 @@ variables: | |||||||
|   max_brightness: "{{ state_attr(brightness_tracker, 'max') | int }}" |   max_brightness: "{{ state_attr(brightness_tracker, 'max') | int }}" | ||||||
|   # How many steps to go full min-max spectrum |   # How many steps to go full min-max spectrum | ||||||
|   brightness_steps: "{{ full_rotate_step_count * 2 | int }}" |   brightness_steps: "{{ full_rotate_step_count * 2 | int }}" | ||||||
|   brightness_delta: > |   brightness_delta: "{{ (step_size | float(0) / brightness_steps) * (max_brightness - min_brightness) }}" | ||||||
|     {% if trigger.event.data.command != 'step' %} |  | ||||||
|       {{ 0 }} |  | ||||||
|     {% else %} |  | ||||||
|       {{ (trigger.event.data.params.step_size | float(0) / brightness_steps) * (max_brightness - min_brightness) }} |  | ||||||
|     {% endif %} |  | ||||||
|   # don't loop when reached max or min |   # don't loop when reached max or min | ||||||
|   next_brightness: > |   next_brightness: > | ||||||
|     {%- set val = states(brightness_tracker) | float(0) -%} |     {%- set val = states(brightness_tracker) | float(0) -%} | ||||||
| @ -136,12 +138,7 @@ variables: | |||||||
|   max_color: "{{ state_attr(color_tracker, 'max') | int }}" |   max_color: "{{ state_attr(color_tracker, 'max') | int }}" | ||||||
|   # How many steps to go full min-max spectrum |   # How many steps to go full min-max spectrum | ||||||
|   color_steps: "{{ full_rotate_step_count * 3 | int }}" |   color_steps: "{{ full_rotate_step_count * 3 | int }}" | ||||||
|   color_delta: > |   color_delta: "{{ (step_size | float(0) / color_steps) * (max_color - min_color) }}" | ||||||
|     {% if trigger.event.data.command != 'step' %} |  | ||||||
|       {{ 0 }} |  | ||||||
|     {% else %} |  | ||||||
|       {{ (trigger.event.data.params.step_size | float(0) / color_steps) * (max_color - min_color) }} |  | ||||||
|     {% endif %} |  | ||||||
|   # loop when reached max or min |   # loop when reached max or min | ||||||
|   next_color: > |   next_color: > | ||||||
|     {%- set val = states(color_tracker) | float(0) -%} |     {%- set val = states(color_tracker) | float(0) -%} | ||||||
| @ -170,7 +167,7 @@ action: | |||||||
|       # If event was a dial turn |       # If event was a dial turn | ||||||
|       - conditions: |       - conditions: | ||||||
|           - condition: template |           - condition: template | ||||||
|             value_template: "{{ trigger.event.data.command == 'step' }}" |             value_template: "{{ rotated }}" | ||||||
|             alias: Dial turned (0 for right, 1 for left) |             alias: Dial turned (0 for right, 1 for left) | ||||||
|         sequence: |         sequence: | ||||||
|           # Process different actions based on current mode |           # Process different actions based on current mode | ||||||
| @ -180,7 +177,7 @@ action: | |||||||
|                   - condition: template |                   - condition: template | ||||||
|                     value_template: "{{ current_mode == 1 }}" |                     value_template: "{{ current_mode == 1 }}" | ||||||
|                     alias: In Mode 1 (Power) |                     alias: In Mode 1 (Power) | ||||||
|                 sequence: [] |                 sequence: [ ] | ||||||
|               # In Mode 2 (Brightness) |               # In Mode 2 (Brightness) | ||||||
|               - conditions: |               - conditions: | ||||||
|                   - condition: template |                   - condition: template | ||||||
| @ -270,7 +267,7 @@ action: | |||||||
|       # If event was a dial button short push |       # If event was a dial button short push | ||||||
|       - conditions: |       - conditions: | ||||||
|           - condition: template |           - condition: template | ||||||
|             value_template: "{{ trigger.event.data.command == 'toggle' }}" |             value_template: "{{ single_pressed }}" | ||||||
|         sequence: |         sequence: | ||||||
|           # Change mode (cycling back if reached the end) |           # Change mode (cycling back if reached the end) | ||||||
|           - service: input_number.set_value |           - service: input_number.set_value | ||||||
| @ -281,11 +278,11 @@ action: | |||||||
|       # If event was a dial button long press |       # If event was a dial button long press | ||||||
|       - conditions: |       - conditions: | ||||||
|           - condition: template |           - condition: template | ||||||
|             value_template: "{{ trigger.event.data.command == 'move_saturation' }}" # dumb: https://github.com/zigpy/zha-device-handlers/blob/92c9fbc6d01a5d86f78d64183302b906aa7d8215/zhaquirks/tuya/ts004f.py#L146C37-L146C37 |             value_template: "{{ double_pressed }}" | ||||||
|         sequence: |         sequence: | ||||||
|           # Toggle light power |           # Toggle light power | ||||||
|           - service: light.toggle |           - service: light.toggle | ||||||
|             data: {} |             data: { } | ||||||
|             target: |             target: | ||||||
|               entity_id: !input light |               entity_id: !input light | ||||||
|             alias: Toggle light power |             alias: Toggle light power | ||||||
| @ -0,0 +1,277 @@ | |||||||
|  | # Instructions: https://community.home-assistant.io/t/zha-z2m-control-light-color-hue-brightness-with-ers-dial/595002 | ||||||
|  |  | ||||||
|  | blueprint: | ||||||
|  |   name: ERS Rotary Dial - Light Control (ZHA) - COMMAND mode | ||||||
|  |   description: Control light brightness, hue and color with an ERS rotary dial | ||||||
|  |  | ||||||
|  |   source_url: https://raw.githubusercontent.com/nwithan8/configs/main/home_assistant/blueprints/automations/ers_rotary_dial_light_control_zha.yaml | ||||||
|  |  | ||||||
|  |   domain: automation | ||||||
|  |  | ||||||
|  |   input: | ||||||
|  |     light: | ||||||
|  |       name: Light | ||||||
|  |       description: The Light entity or Light Group entity to control | ||||||
|  |       selector: | ||||||
|  |         entity: | ||||||
|  |           filter: | ||||||
|  |             - domain: | ||||||
|  |                 - light | ||||||
|  |                 - group | ||||||
|  |           multiple: false | ||||||
|  |  | ||||||
|  |     dial: | ||||||
|  |       name: Rotary dial | ||||||
|  |       description: Select the rotary dial you wish to use to control the light | ||||||
|  |       selector: | ||||||
|  |         device: | ||||||
|  |           filter: | ||||||
|  |             integration: zha | ||||||
|  |             model: TS004F | ||||||
|  |  | ||||||
|  |     mode_tracker: | ||||||
|  |       name: Mode tracker | ||||||
|  |       description: An input number helper to store the current mode in (1-4) | ||||||
|  |       selector: | ||||||
|  |         entity: | ||||||
|  |           filter: | ||||||
|  |             domain: input_number | ||||||
|  |  | ||||||
|  |     color_tracker: | ||||||
|  |       name: Color tracker | ||||||
|  |       description: An input number helper to store the current RGB value in (0-1535) | ||||||
|  |       selector: | ||||||
|  |         entity: | ||||||
|  |           filter: | ||||||
|  |             domain: input_number | ||||||
|  |  | ||||||
|  |     hue_tracker: | ||||||
|  |       name: Hue tracker | ||||||
|  |       description: An input number helper to store the current hue in (2000-6500) | ||||||
|  |       selector: | ||||||
|  |         entity: | ||||||
|  |           filter: | ||||||
|  |             domain: input_number | ||||||
|  |  | ||||||
|  |     brightness_tracker: | ||||||
|  |       name: Brightness tracker | ||||||
|  |       description: An input number helper to store the current brightness in (0-255) | ||||||
|  |       selector: | ||||||
|  |         entity: | ||||||
|  |           filter: | ||||||
|  |             domain: input_number | ||||||
|  |  | ||||||
|  | # Queued, to ignore non-matched events but capture existing ones | ||||||
|  | mode: queued | ||||||
|  | max: 20 | ||||||
|  | max_exceeded: silent | ||||||
|  |  | ||||||
|  | variables: | ||||||
|  |   light: !input "light" | ||||||
|  |   dial: !input "dial" | ||||||
|  |   mode_tracker: !input "mode_tracker" | ||||||
|  |   hue_tracker: !input "hue_tracker" | ||||||
|  |   brightness_tracker: !input "brightness_tracker" | ||||||
|  |   color_tracker: !input "color_tracker" | ||||||
|  |   command: "{{ trigger.event.data.command }}" | ||||||
|  |   single_pressed: "{{ command == 'toggle' }}" | ||||||
|  |   # double_pressed: "{{ command == 'move_saturation' }}" # dumb: https://github.com/zigpy/zha-device-handlers/blob/92c9fbc6d01a5d86f78d64183302b906aa7d8215/zhaquirks/tuya/ts004f.py#L146C37-L146C37 | ||||||
|  |   rotated: "{{ command == 'step' }}" | ||||||
|  |   positive: "{{ rotated and trigger.event.data.params.step_mode == 0 }}" | ||||||
|  |   step_size: > | ||||||
|  |     {% if rotated %} | ||||||
|  |       {{ trigger.event.data.params.step_size | float(0) }} | ||||||
|  |     {% else %} | ||||||
|  |       {{ 0 }} | ||||||
|  |     {% endif %} | ||||||
|  |   # How many steps to go full rotation (min 13 per step * 20 steps per full rotation) | ||||||
|  |   full_rotate_step_count: "{{ 13 * 20 | int }}" | ||||||
|  |   current_mode: "{{ states(mode_tracker) | int }}" | ||||||
|  |   min_mode: "{{ state_attr(mode_tracker, 'min') | int }}" | ||||||
|  |   max_mode: "{{ state_attr(mode_tracker, 'max') | int }}" | ||||||
|  |   # always increment, looping | ||||||
|  |   next_mode: > | ||||||
|  |     {% set val = current_mode + 1 %}  | ||||||
|  |     {% if val > max_mode %}  | ||||||
|  |       {% set val = min_mode %} | ||||||
|  |     {% endif %} | ||||||
|  |     {{ val | int }} | ||||||
|  |   min_hue: "{{ state_attr(hue_tracker, 'min') | int }}" | ||||||
|  |   max_hue: "{{ state_attr(hue_tracker, 'max') | int }}" | ||||||
|  |   # How many steps to go full min-max spectrum | ||||||
|  |   hue_steps: "{{ full_rotate_step_count * 2 | int }}" | ||||||
|  |   hue_delta: "{{ (step_size | float(0) / hue_steps) * (max_hue - min_hue) }}" | ||||||
|  |   # don't loop when reached max or min | ||||||
|  |   next_hue: > | ||||||
|  |     {%- set val = states(hue_tracker) | float(0) -%} | ||||||
|  |     {%- set delta = hue_delta -%} | ||||||
|  |     {%- if not positive -%} | ||||||
|  |       {%- set delta = delta * -1 -%} | ||||||
|  |     {%- endif -%} | ||||||
|  |     {%- set val = val + delta | int -%} | ||||||
|  |     {%- if positive and val > max_hue -%} | ||||||
|  |       {%- set val = max_hue -%} | ||||||
|  |     {%- elif not positive and val < min_hue -%} | ||||||
|  |       {%- set val = min_hue -%} | ||||||
|  |     {%- endif -%} | ||||||
|  |     {{ val | int }} | ||||||
|  |   min_brightness: "{{ state_attr(brightness_tracker, 'min') | int }}" | ||||||
|  |   max_brightness: "{{ state_attr(brightness_tracker, 'max') | int }}" | ||||||
|  |   # How many steps to go full min-max spectrum | ||||||
|  |   brightness_steps: "{{ full_rotate_step_count * 2 | int }}" | ||||||
|  |   brightness_delta: "{{ (step_size | float(0) / brightness_steps) * (max_brightness - min_brightness) }}" | ||||||
|  |   # don't loop when reached max or min | ||||||
|  |   next_brightness: > | ||||||
|  |     {%- set val = states(brightness_tracker) | float(0) -%} | ||||||
|  |     {%- set delta = brightness_delta -%} | ||||||
|  |     {%- if not positive -%} | ||||||
|  |       {%- set delta = delta * -1 -%} | ||||||
|  |     {%- endif -%} | ||||||
|  |     {%- set val = val + delta | int -%} | ||||||
|  |     {%- if positive and val > max_brightness -%}  | ||||||
|  |       {%- set val = max_brightness -%} | ||||||
|  |     {%- elif not positive and val < min_brightness -%} | ||||||
|  |       {%- set val = min_brightness -%} | ||||||
|  |     {%- endif -%} | ||||||
|  |     {{ val | int }} | ||||||
|  |   min_color: "{{ state_attr(color_tracker, 'min') | int }}" | ||||||
|  |   max_color: "{{ state_attr(color_tracker, 'max') | int }}" | ||||||
|  |   # How many steps to go full min-max spectrum | ||||||
|  |   color_steps: "{{ full_rotate_step_count * 3 | int }}" | ||||||
|  |   color_delta: "{{ (step_size | float(0) / color_steps) * (max_color - min_color) }}" | ||||||
|  |   # loop when reached max or min | ||||||
|  |   next_color: > | ||||||
|  |     {%- set val = states(color_tracker) | float(0) -%} | ||||||
|  |     {%- set delta = color_delta -%} | ||||||
|  |     {%- if not positive -%} | ||||||
|  |       {%- set delta = delta * -1 -%} | ||||||
|  |     {%- endif -%} | ||||||
|  |     {%- set val = val + delta | int -%} | ||||||
|  |     {%- if positive and val > max_color -%} | ||||||
|  |       {%- set val = val % max_color -%} | ||||||
|  |     {%- elif not positive and val < min_color -%} | ||||||
|  |       {%- set diff = min_color - val -%} | ||||||
|  |       {%- set val = max_color - (diff % max_color) -%} | ||||||
|  |     {%- endif -%} | ||||||
|  |     {{ val | int }} | ||||||
|  |  | ||||||
|  | trigger: | ||||||
|  |   - platform: event | ||||||
|  |     event_type: zha_event | ||||||
|  |     event_data: | ||||||
|  |       device_id: !input dial | ||||||
|  |  | ||||||
|  | action: | ||||||
|  |   # Process current event | ||||||
|  |   - choose: | ||||||
|  |       # If event was a dial turn | ||||||
|  |       - conditions: | ||||||
|  |           - condition: template | ||||||
|  |             value_template: "{{ rotated }}" | ||||||
|  |             alias: Dial turned (0 for right, 1 for left) | ||||||
|  |         sequence: | ||||||
|  |           # Process different actions based on current mode | ||||||
|  |           - choose: | ||||||
|  |               # In Mode 1 (don't do anything; prevent accidental knob rotation) | ||||||
|  |               - conditions: | ||||||
|  |                   - condition: template | ||||||
|  |                     value_template: "{{ current_mode == 1 }}" | ||||||
|  |                     alias: In Mode 1 (Power) | ||||||
|  |                 sequence: [ ] | ||||||
|  |               # In Mode 2 (Brightness) | ||||||
|  |               - conditions: | ||||||
|  |                   - condition: template | ||||||
|  |                     value_template: "{{ current_mode == 2 }}" | ||||||
|  |                     alias: In Mode 2 (Brightness) | ||||||
|  |                 sequence: | ||||||
|  |                   - service: input_number.set_value | ||||||
|  |                     entity_id: !input brightness_tracker | ||||||
|  |                     data: | ||||||
|  |                       value: "{{ next_brightness }}" | ||||||
|  |                     alias: Update brightness tracker | ||||||
|  |                   - service: light.turn_on | ||||||
|  |                     data: | ||||||
|  |                       brightness: "{{ states(brightness_tracker) | int }}" | ||||||
|  |                     target: | ||||||
|  |                       entity_id: !input light | ||||||
|  |                     alias: Change light brightness | ||||||
|  |               # In Mode 3 (Hue) | ||||||
|  |               - conditions: | ||||||
|  |                   - condition: template | ||||||
|  |                     value_template: "{{ current_mode == 3 }}" | ||||||
|  |                     alias: In Mode 3 (Hue) | ||||||
|  |                 sequence: | ||||||
|  |                   - service: input_number.set_value | ||||||
|  |                     entity_id: !input hue_tracker | ||||||
|  |                     data: | ||||||
|  |                       value: "{{ next_hue }}" | ||||||
|  |                     alias: Update hue tracker | ||||||
|  |                   - service: light.turn_on | ||||||
|  |                     data: | ||||||
|  |                       kelvin: "{{ states(hue_tracker) | int }}" | ||||||
|  |                     target: | ||||||
|  |                       entity_id: !input light | ||||||
|  |                     alias: Change light hue | ||||||
|  |               # In Mode 4 (Color) | ||||||
|  |               - conditions: | ||||||
|  |                   - condition: template | ||||||
|  |                     value_template: "{{ current_mode == 4 }}" | ||||||
|  |                     alias: In Mode 4 (Color) | ||||||
|  |                 sequence: | ||||||
|  |                   - service: input_number.set_value | ||||||
|  |                     entity_id: !input color_tracker | ||||||
|  |                     data: | ||||||
|  |                       value: "{{ next_color }}" | ||||||
|  |                     alias: Update color tracker | ||||||
|  |                   - service: light.turn_on | ||||||
|  |                     data: | ||||||
|  |                       # Calculate RGB codes based on 0-1535 index (max 2 channels, one always off) | ||||||
|  |                       rgb_color: > | ||||||
|  |                         {%- set r = 0 -%} | ||||||
|  |                         {%- set g = 0 -%} | ||||||
|  |                         {%- set b = 0 -%} | ||||||
|  |                         {%- set index = states(color_tracker) | int -%} | ||||||
|  |                         {%- if index > 0 and index <= 256 -%} | ||||||
|  |                           {%- set r = 255 -%} | ||||||
|  |                           {%- set g = index -%} | ||||||
|  |                           {%- set b = 0 -%} | ||||||
|  |                         {%- elif index > 256 and index <= 512 -%} | ||||||
|  |                           {%- set r = 512 - index -%} | ||||||
|  |                           {%- set g = 255 -%} | ||||||
|  |                           {%- set b = 0 -%} | ||||||
|  |                         {%- elif index > 512 and index <= 768 -%} | ||||||
|  |                           {%- set r = 0 -%} | ||||||
|  |                           {%- set g = 255 -%} | ||||||
|  |                           {%- set b = index - 512 -%} | ||||||
|  |                         {%- elif index > 768 and index <= 1024 -%} | ||||||
|  |                           {%- set r = 0 -%} | ||||||
|  |                           {%- set g = 1024 - index -%} | ||||||
|  |                           {%- set b = 255 -%} | ||||||
|  |                         {%- elif index > 1024 and index <= 1280 -%} | ||||||
|  |                           {%- set r = index - 1024 -%} | ||||||
|  |                           {%- set g = 0 -%} | ||||||
|  |                           {%- set b = 255 -%} | ||||||
|  |                         {%- elif index > 1280 and index <= 1536 -%} | ||||||
|  |                           {%- set r = 255 -%} | ||||||
|  |                           {%- set g = 0 -%} | ||||||
|  |                           {%- set b = 1536 - index -%} | ||||||
|  |                         {%- endif -%} | ||||||
|  |                         {%- set r = r | string -%} | ||||||
|  |                         {%- set g = g | string -%} | ||||||
|  |                         {%- set b = b | string -%} | ||||||
|  |                         {{ r + "," + g + "," + b }} | ||||||
|  |                     target: | ||||||
|  |                       entity_id: !input light | ||||||
|  |                     alias: Change light color | ||||||
|  |             alias: Determine action based on mode | ||||||
|  |       # If event was a dial button short push | ||||||
|  |       - conditions: | ||||||
|  |           - condition: template | ||||||
|  |             value_template: "{{ single_pressed }}" | ||||||
|  |         sequence: | ||||||
|  |           # Change mode (cycling back if reached the end) | ||||||
|  |           - service: input_number.set_value | ||||||
|  |             entity_id: !input mode_tracker | ||||||
|  |             data: | ||||||
|  |               value: "{{ next_mode }}" | ||||||
|  |             alias: Switch to next mode | ||||||
		Reference in New Issue
	
	Block a user