Wahoo WFTNP: Use your hometrainer power output to control the light color

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)

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

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: []