How to automate a range hood fan that supports IR control

The following guide explains how I automated a range hood fan that is controllable via IR commands. The principles described here are applicable to any fan that is controllable via IR.

Physical Devices

  1. IR-controllable range hood
    Roxon RXN-U12BS-30 is IR controllable and supports four fan speeds.

  2. Energy-monitoring smart plug
    CE Smart Home Plug LA-WF7 is flashed with Tasmota and reports the load’s power consumption. The range hood is plugged into the smart plug; its speed is inferred from its power consumption.
    CE Smart Home LA-WF7_1

  3. IR transmitter
    TuYa ZS06 Zigbee to IR remote-control sends IR commands to control the range hood’s light and fan speed. The ZS06 is integrated via Zigbee2MQTT so an IR command is sent by publishing it to the IR transmitter’s MQTT topic.
    TuYa ZS06

Entities

  1. Trigger-based Template Sensor
    sensor.range_hood_speed reports the fan’s speed based on the fan’s power consumption.

  2. Script
    script.range_hood_control centralizes all logic required to control the range hood fan.

  3. Template Fan
    fan.range_hood models the physical range hood fan using sensor.range_hood_speed and script.range_hood_control.

Configuring the energy-monitoring smart plug and IR transmitter are beyond the scope of this guide; the assumption is that you have already integrated and configured both devices and they are operating properly.

Fan Speeds

In Home Assistant, a fan’s speed is represented by a value from 0 to 100. The range hood has four fan speeds so that means 100/4 = 25 per incremental step. The fan’s modes are modeled as:

off = 0
on = 25
low = 25
medium = 50
high = 75
max = 100

Both “on” and “low” represent a speed of 25. My preference is that when fan.range_hood is simply turned on (with no indication of any speed) it will automatically set the range hood fan to a speed of 25.

Inferring the fan’s speed from its power consumption

When the range hood fan’s speed is increased, especially when first turned on, the reported power consumption typically ‘spikes’ then quickly settles down to a relatively stable ‘operating’ value. The difference between the higher ‘spike’ value and the lower ‘operating’ value is typically within 10 watts.

In addition, the ‘operating’ value isn’t always exactly the same and can vary by a few watts. All this to say, that one must infer the fan’s speed based on a range of power values and not a single value.

In the following Trigger-based Template Sensor, a power consumption value between 125 and 160 watts is considered to be a speed of 25 and other power ranges are used to report 50, 75, and 100.

Trigger-based Template Sensor
template:
  - trigger:
      - platform: state
        entity_id: sensor.range_hood_energy_power
    sensor:
      - name: Range Hood Fan Speed
        unique_id: range_hood_fan_speed
        state: >
          {% set p = trigger.to_state.state | int(0) %}
          {{  { p < 125: 0,
                125 <= p < 160: 25,
                160 <= p < 185: 50,
                185 <= p < 225: 75,
                p > 225: 100 
              }.get(true, 0) }}
        attributes:
          light: "{{ 3 <= trigger.to_state.state | int(0) < 7 }}"

In addition, the Template Sensor attempts to infer the light’s state based on power consumption (between 3 and 7 watts). However, this is feasible only when the fan is off.

  • If both the fan and light are off, power consumption is ~1-2 watts. You can infer the light’s state (off) from this value.

  • If the fan is off and the light is on, power consumption is ~3-4 watts. You can infer the light’s state (on) from this value.

  • If the fan and light are on, power consumption is above 125 watts. Each speed’s power consumption is not a fixed value but is within a range of values. The range is at least as large as the light’s power consumption, therefore you cannot determine the light’s state when the fan is on.

IR commands

The TuYa IR transmitter was placed into learn mode in order to learn the Roxon IR remote-control’s commands. Each command was learned several times (each attempt produces a slightly different result). I chose the result that contained what was common to other samples but was shorter than most.

Toggle Light: BV0jqhE/AuAXAQGXBuAZA+AXAeATQwcIm10jyAg/Ag==
Toggle Power: BWUjqBE+AuAXAQGYBuAZA0ABwCfgCwFAG8ABQAvgAwMH/5plI+UIPgI=
Increase Speed: BVojqhE9AuAXAQGaBuAVA0ABQCNAAcAH4AMBwBPgAwdAC8ADBwWbWiP4CD0C
Decrease Speed: BVcjrBFAAuAXAQGUBuAVA0ABQCPgDwHAG0AH4AsDBxObVyPHCEAC

Controlling the fan and light via IR commands

Here’s a summary of how the Roxon range hood behaves when controlled by its IR remote control.

  • The Roxon range hood does not have discrete IR commands for setting a specific speed or for explicitly turning the light on and off.

  • The single light command simply toggles the light’s existing state.

  • The fan speed is set by two commands, one for increasing the speed and the other for decreasing it. For example, to set the fan to the highest speed (starting from speed level one) you need to press the remote’s “increase” command three times. In order for a script to change the fan’s speed from one level to another, it has to know the fan’s current speed, determine if the command should be “increase” or “decrease”, and then compute how many commands to send to reach the target speed.

  • The single power command toggles the range hood’s state. It can be used to turn off the fan but not the light.

    • If the range hood is off, the power command will turn on the range hood’s display panel but not set the fan’s speed.
    • If the range hood is on and the fan is running, the power command will turn off the fan and the range hood’s display panel but not the light if it’s on.
  • There’s no single command to turn off both the fan and the light.

  • The fan speed commands don’t ‘wrap around’. In other words, if you attempt to decrease below the lowest speed, it doesn’t wrap around to the highest speed but turns off the fan.

Based on the available IR commands, and what they can and cannot do, the following script controls the range hood’s fan and light. I did not create separate controls for the light and fan; the light is turned on when the fan is turned on (and turned off when the fan is turned off).

Script
script:
  range_hood_control:
    alias: Range Hood Control
    mode: queued
    sequence:
      ## Proceed only if `cmd` variable is defined and
      ## its value is either a number or a valid string
      - condition:
          - "{{ cmd is defined }}"
          - or:
              - "{{ cmd is number }}"
              - "{{ cmd in ['on', 'off', 'low', 'medium', 'high', 'max'] }}"
      - variables:
          speed_sensor: 'sensor.range_hood_fan_speed'
          irt: 'zigbee2mqtt/Kitchen IR Transmitter/set'
          light: 'BV0jqhE/AuAXAQGXBuAZA+AXAeATQwcIm10jyAg/Ag=='
          power: 'BWUjqBE+AuAXAQGYBuAZA0ABwCfgCwFAG8ABQAvgAwMH/5plI+UIPgI='
          increase: 'BVojqhE9AuAXAQGaBuAVA0ABQCNAAcAH4AMBwBPgAwdAC8ADBwWbWiP4CD0C'
          decrease: 'BVcjrBFAAuAXAQGUBuAVA0ABQCPgDwHAG0AH4AsDBxObVyPHCEAC'
          modes:
            'off': 0
            'on': 25
            low: 25
            medium: 50
            high: 75
            max: 100
          msg_light:
            ir_code_to_send: '{{ light }}'
          current_percentage: "{{ states(speed_sensor) | int(0) }}"
          target_percentage: >
            {{ modes.get(cmd, 0) if cmd is not number else 
              { cmd == 0: 0,
                0 < cmd < 37: 25,
                37 <= cmd < 62: 50,
                62 <= cmd < 87: 75,
                cmd >= 87: 100
              }.get(true, 0) }}
          msg:
            ir_code_to_send: '{{ power if target_percentage == 0 else increase if target_percentage > current_percentage else decrease }}'
          iterations: '{{ 1 if target_percentage == 0 else (target_percentage - current_percentage) | abs // 25 }}'

      ## Turn on light if fan is being activated
      - if:
          - "{{ cmd == 'on' or current_percentage == 0 }}"
          - "{{ is_state_attr(speed_sensor, 'light', false) }}"
        then:
          - service: mqtt.publish
            data:
              topic: '{{ irt }}'
              payload: '{{ msg_light | to_json }}'
          - delay:
              seconds: 1

      ## Transmit IR command(s)
      - repeat:
          count: '{{ iterations }}'
          sequence:
            - service: mqtt.publish
              data:
                topic: '{{ irt }}'
                payload: '{{ msg | to_json }}'
            - delay:
                seconds: 1

      ## Turn off light if fan is being deactivated
      - if: "{{ cmd == 'off' or target_percentage == 0 }}"
        then:
          - delay:
              seconds: 2
          - if: "{{ is_state_attr(speed_sensor, 'light', true) }}"
            then:
              - service: mqtt.publish
                data:
                  topic: '{{ irt }}'
                  payload: '{{ msg_light | to_json }}'

Template Fan

The Template Fan derives its state and speed from the Trigger-based Template Sensor and controls the physical range hood fan via the script.

Template Fan
fan:
  - platform: template
    fans:
      range_hood:
        friendly_name: 'Range Hood Fan'
        value_template: "{{ states('sensor.range_hood_fan_speed') | int(0) > 0 }}"
        percentage_template: "{{ states('sensor.range_hood_fan_speed') | int(0) }}"
        turn_on:
          - service: script.range_hood_control
            data:
              cmd: 'on'
        turn_off:
          - service: script.range_hood_control
            data:
              cmd: 'off'
        set_percentage:
          - service: script.range_hood_control
            data:
              cmd: '{{ percentage }}'
        set_preset_mode:
          - service: script.range_hood_control
            data:
              cmd: '{{ preset_mode }}'
        speed_count: 4
        preset_modes:
          - 'off'
          - 'low'
          - 'medium'
          - 'high'
          - 'max'
        preset_mode_template: >
          {{ { 0: 'off',
              25: 'low',
              50: 'medium',
              75: 'high',
              100: 'max'
              }.get(states('sensor.range_hood_fan_speed') | int(0), 'off') }}

1 Like