diff --git a/home_assistant/blueprints/automations/ers_rotary_dial_light_control.yaml b/home_assistant/blueprints/automations/ers_rotary_dial_light_control_z2m.yaml similarity index 88% rename from home_assistant/blueprints/automations/ers_rotary_dial_light_control.yaml rename to home_assistant/blueprints/automations/ers_rotary_dial_light_control_z2m.yaml index 9621ff1..71de467 100644 --- a/home_assistant/blueprints/automations/ers_rotary_dial_light_control.yaml +++ b/home_assistant/blueprints/automations/ers_rotary_dial_light_control_z2m.yaml @@ -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: - 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 + source_url: https://raw.githubusercontent.com/nwithan8/configs/main/home_assistant/blueprints/automations/ers_rotary_dial_light_control_z2m.yaml + domain: automation input: @@ -71,7 +73,17 @@ variables: hue_tracker: !input "hue_tracker" brightness_tracker: !input "brightness_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) full_rotate_step_count: "{{ 13 * 20 | int }}" current_mode: "{{ states(mode_tracker) | int }}" @@ -88,12 +100,7 @@ variables: 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: > - {% if trigger.event.data.command != 'step' %} - {{ 0 }} - {% else %} - {{ (trigger.event.data.params.step_size | float(0) / hue_steps) * (max_hue - min_hue) }} - {% endif %} + 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) -%} @@ -112,12 +119,7 @@ variables: 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: > - {% if trigger.event.data.command != 'step' %} - {{ 0 }} - {% else %} - {{ (trigger.event.data.params.step_size | float(0) / brightness_steps) * (max_brightness - min_brightness) }} - {% endif %} + 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) -%} @@ -136,12 +138,7 @@ variables: 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: > - {% if trigger.event.data.command != 'step' %} - {{ 0 }} - {% else %} - {{ (trigger.event.data.params.step_size | float(0) / color_steps) * (max_color - min_color) }} - {% endif %} + 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) -%} @@ -170,7 +167,7 @@ action: # If event was a dial turn - conditions: - condition: template - value_template: "{{ trigger.event.data.command == 'step' }}" + value_template: "{{ rotated }}" alias: Dial turned (0 for right, 1 for left) sequence: # Process different actions based on current mode @@ -180,7 +177,7 @@ action: - condition: template value_template: "{{ current_mode == 1 }}" alias: In Mode 1 (Power) - sequence: [] + sequence: [ ] # In Mode 2 (Brightness) - conditions: - condition: template @@ -270,7 +267,7 @@ action: # If event was a dial button short push - conditions: - condition: template - value_template: "{{ trigger.event.data.command == 'toggle' }}" + value_template: "{{ single_pressed }}" sequence: # Change mode (cycling back if reached the end) - service: input_number.set_value @@ -281,11 +278,11 @@ action: # If event was a dial button long press - conditions: - 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: # Toggle light power - service: light.toggle - data: {} + data: { } target: entity_id: !input light alias: Toggle light power diff --git a/home_assistant/blueprints/automations/ers_rotary_dial_light_control_zha.yaml b/home_assistant/blueprints/automations/ers_rotary_dial_light_control_zha.yaml new file mode 100644 index 0000000..b88f86f --- /dev/null +++ b/home_assistant/blueprints/automations/ers_rotary_dial_light_control_zha.yaml @@ -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