Blueprint to correlate the power output of your Wahoo device with a light source.
The use case is that you can control the light color through your power output for example on a kickr core.
The HACS extension can be found here. For now you still have to install that through a custom repository.
It allows also to connect to more devices supporting WFTNP. Credit here to elfrances (cannot link him because of new user limitation)
blueprint:
name: Watt → Light Color
description: >
Sets light color based on a power sensor (watts) using YAML-defined ranges.
Default brightness_pct may be overriden it by specifying brightness_pct.
Range logic: from <= watt < to
First match wins.
domain: automation
input:
power_sensor:
name: Power sensor (watts)
selector:
entity:
domain: sensor
light_target:
name: Light(s) to control
selector:
target:
entity:
domain: light
transition_seconds:
name: Transition (seconds)
default: 1
selector:
number:
min: 0
max: 30
step: 1
mode: slider
default_brightness_pct:
name: Default brightness_pct (required)
selector:
number:
min: 0
max: 100
step: 1
mode: slider
default: 70
ranges:
name: Ranges (YAML list)
description: >
Example:
- from: 100
to: 120
color: green
brightness_pct: 60
- from: 120
to: 140
color: yellow
- from: 140
to: 170
color: "#ff7f00"
brightness_pct: 80
- from: 170
to: 99999
color: "255,0,0"
selector:
object: {}
default:
- from: 100
to: 120
color: green
brightness_pct: 60
- from: 120
to: 140
color: yellow
- from: 140
to: 170
color: "#ff7f00"
brightness_pct: 80
- from: 170
to: 99999
color: "255,0,0"
mode: restart
trigger:
- platform: state
entity_id:
- !input power_sensor
condition:
- condition: template
value_template: >-
{% set min_delta = 2.0 %}
{% set a = trigger.from_state.state | float(none) if trigger.from_state else none %}
{% set b = trigger.to_state.state | float(none) %}
{{ a is not none and b is not none and (b - a) | abs >= min_delta }}
action:
- variables:
watt: "{{ trigger.to_state.state | float(0) }}"
ranges: !input ranges
default_bright: !input default_brightness_pct
transition: !input transition_seconds
# Find first matching range (assume from/to are numeric and color is present)
picked: >-
{% set ns = namespace(found=false, color='', bright=none) %}
{% for r in ranges %}
{% if ns.found %}{% continue %}{% endif %}
{% set lo = r.get('from') | float %}
{% set hi = r.get('to') | float %}
{% set c = r.get('color') | string %}
{% if watt >= lo and watt < hi %}
{% set ns.found = true %}
{% set ns.color = c %}
{% set ns.bright = r.get('brightness_pct', none) %}
{% endif %}
{% endfor %}
{{ dict(found=ns.found, color=ns.color, brightness_pct=ns.bright) }}
color_raw: "{{ picked.color }}"
# Required default brightness, overridden by range if provided
bright_num: >-
{% if picked.brightness_pct is not none %}
{{ picked.brightness_pct | float }}
{% else %}
{{ default_bright }}
{% endif %}
# Color parsing
is_hex: "{{ color_raw.startswith('#') and (color_raw | length) == 7 }}"
is_rgb: >-
{{ (',' in color_raw) and (color_raw.split(',') | length == 3) }}
rgb: >-
{% if is_hex %}
{% set r = color_raw[1:3] | int(base=16) %}
{% set g = color_raw[3:5] | int(base=16) %}
{% set b = color_raw[5:7] | int(base=16) %}
{{ [r,g,b] }}
{% elif is_rgb %}
{% set p = color_raw.split(',') %}
{{ [p[0]|trim|int, p[1]|trim|int, p[2]|trim|int] }}
{% else %}
{{ [] }}
{% endif %}
- choose:
- conditions: "{{ picked.found }}"
sequence:
- choose:
- conditions: "{{ is_hex or is_rgb }}"
sequence:
- service: light.turn_on
target: !input light_target
data:
rgb_color: "{{ rgb }}"
brightness_pct: "{{ bright_num }}"
transition: "{{ transition }}"
default:
- service: light.turn_on
target: !input light_target
data:
color_name: "{{ color_raw }}"
brightness_pct: "{{ bright_num }}"
transition: "{{ transition }}"
default: []