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.

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') }}

Hello;

Thanks for your work. I am about to try to use your code to control a similar roxon range hood.

I was wondering if you had a version of your mod that did have the light as seperate. I have a presence sensor within the range hood I’d like to tie to the light.

Thanks
Steve

I don’t because you can only detect when the light is on or off when the fan is off. When the fan is on, it’s no longer possible to detect if the light is on or off (for the reasons explained in my post above). In addition, the IR command for the light is a toggle command; you can’t explicitly command the light to turn on or off, merely toggle whatever is its current state.

The combination of these two constraints (not knowing the light’s true state when the fan is running and only being able to toggle its state) makes it impractical to control the light separately.

Hello;

Thanks for the information. I have decided to install a zigbee device inside it in order to have fine control of the light. So I will still need to figure out how to remove the light being tied to the fan. I will have a look and try to figure it out, however any pointer would be nice.

Thanks!
Steve

I don’t know which Roxon fan model you have but for the one I have the light is controlled separately from the fan. In other words, you send separate commands to control the light and fan.

The challenge is to detect the light’s state when the light and fan are operated manually.

  • If someone manually turns on the light while the fan is off, the additional power consumption can be detected.

  • If they turn on the light while the fan is on, the incremental change in consumption is too small to be detected compared to the much larger consumption of the fan (each fan speed’s consumption can vary by as much as 10W, which is three times more than what the light consumes).

So, yes, it’s possible to control the light and fan separately, provided no one is permitted to ever control them manually. Because the moment someone turns the light on or off manually (while the fan is running) it will be undetectable to Home Assistant.

Anyways, good luck hacking its electronics.

Thanks a lot. I completely understand what you mean. I plan on feeding the fan controllers output into the zigbee relay as a toggle switch. I’ve done something similar before with an ifan04.

It’s going to be a chore for sure, but as with all smart home things, if you want it a certain way you better get to learning.

The version I got was this one: ROXON 860 CFM 30-Inch Under Cabinet Range Hood with Handwave Sensor, Touchless Control, Remote Operation, Centrifugal Blower Motors, Stainless Steel Exhaust Fan, LED Lights, Dishwasher-Safe Filters : Amazon.ca: Home

I have learned a lot since posting last. My fan actually has a 433 remote. I have had to adapt your code to get sort of functioning control.

My fan requires power on sent before preset selection. So I’ve had to create an automation to detect power and modify the up received accordingly.

Everything on the home assistant side seems working well. However sometimes the fan doesn’t respond at all, almost like the codes are not sent.

I have your template publishing to mqtt and then home assistant automations detecting the topic and firing the broadlink.

When it works though, it works perfectly.