Need help with making a dumb fan smart

That’s to bad, well then I will use my old situation and turn on a script multiple times to increase it manually. The main purpose of the template fan was that I could add one tile to my homekit and that I can control the fan with my iPhone, I have 3 tiles now because of the separate commands. I don’t think the are going to edit the template fan any time soon.

I have a prototype solution that uses an input_boolean to control fan-power and an input_select to control fan-speed. An input_text keeps track of the fan’s last-known speed (this entity should be concealed from the the UI).

All that’s left to do is modify my old python_script to handle looping though fan-speed changes.

Let me know if this solution interests you.

Yes I am interested, everything is beter then the situation I have currently setup. The only problem is that (as far as i know) there is no way to combine them into one entity. At the moment I have tiles in home kit for each command(on/off, speed up, rotate). That’s why I was thinking of using the template fan, until you told me that it would not work.

The solution I have is probably not that much different than your existing one. Based on my observations, the Template Fan sends a power-on command before each speed-change and that won’t work here because your fan doesn’t have separate power-on and power-off commands (it has a single power-toggle command).


The solution I’m offering requires:

  • Three inputs.
  • Two automations.
  • One python_script.

Inputs

There are three inputs:

  1. control fan’s power state
  2. control fan’s speed
  3. store fan’s last-known speed

input_boolean.fan_power controls the fan’s power state. It appears as a toggle button in the Lovelace UI.

input_boolean:
  fan_power:
    name: Fan Power

input_select.fan_speed controls the fan’s speed. It appears as a drop-down list in the Lovelace UI.

input_select:
  fan_speed:
    name: Fan Speed
    options: ['1', '2', '3', '4', '5', '6', '7', '8']

input_text.tmp_fan_speed maintains a record of the fan’s last-known speed. There’s no need to show this in the UI.

input_text:
  tmp_fan_speed:
    name: Tmp Fan Speed

Automations

There are two automations:

  1. manage the fan’s power state
  2. manage the fan’s speed

automation.fan_power_control is triggered by changes to input_boolean.fan_power. Set the IP address to the one used by your Broadlink device and set packet to the RF code used by your fan for turning it on/off.

- alias: 'fan power control'
  trigger:
    platform: state
    entity_id: input_boolean.fan_power
  action:
  - service: broadlink.send
    data:
      host: '192.168.1.10'
      packet: 'JgBQAAABJZMSExE3EhMRExE4ETgRExERfODAAAAAAAAAA='

automation.fan_speed_control is triggered by changes to input_select.fan_speed. It will run only if input_boolean.fan_power is on. In other words, you must turn the fan on, using input_boolean.fan_power, before adjusting its speed with input_select.fan_speed.

- alias: 'fan speed control'
  trigger:
    platform: state
    entity_id: input_select.fan_speed
  condition:
    condition: state
    entity_id: input_boolean.fan_power
    state: 'on'
  action:
  - service: python_script.fan_speed_control
    data_template:
      speed: "{{ trigger.to_state.state }}"
  - service: input_text.set_value
    data_template:
      entity_id: input_text.tmp_fan_speed
      value: "{{ trigger.to_state.state }}"

Python_script

There is one python_script to calculate and transmit the correct number of speed-steps to reach a desired fan speed.

fan_speed_control.py is used by automation.fan_speed_control. It requires one parameter:

  • fan_speed is the desired fan speed from 1 to 8.

You must customize this python_script to work with your devices. Enter a valid RF code for increasing the fan’s speed. In addition, provide the IP address of your Broadlink device.

Be sure to read about the python_script integration so you know what to add to your configuration file and where to put this file:

fan_speed_control.py

# Set code to whatever RF code your fan uses for increasing speed.
code = 'JgBQAAISEjcSEhISEhISExETETgRNhMTETgROBAAAAAAAAAA='

speed = data.get('fan_speed')
tmp_speed = hass.states.get('input_text.tmp_fan_speed')

if speed is not None:
  speed = int(speed)
  last_speed = int(tmp_speed.state) if tmp_speed.state else 1
  if 1 <= speed <= 8:
    if speed > last_speed:
      loop = speed - last_speed
    else:
      loop = (8 - last_speed) + speed

    # Set the IP address to match the one used by your Broadlink device
    service_data = {'host':'192.168.1.10', 'packet':'{}'.format(code)}
    for i in range(loop):
      hass.services.call('broadlink', 'send', service_data, False)
      time.sleep(0.5)
  else:
    logger.warning('<fan_speed_control> Received fan speed is invalid ({})'.format(fan_speed))
else:
  logger.warning('<fan_speed_control> Received fan speed is invalid (None)')

Before attempting to control the fan with this solution, you will need to synchonize it. Using the fan’s remote-control, turn it on, set it to its lowest speed and then turn it off. This is the solution’s initial state (fan_power is off and fan_speed is 1). Now you can begin testing the solution by turning the fan on/off with input_boolean.fan_power. If that works, proceed to controlling its speed with input_select.fan_speed.

1 Like

Template Fan - simulator

This demonstrates the operation of a Template Fan, specifically the behavior where it sends a power-on command before each speed-change command.

If you have already tried my previous solution, ensure you disable its two automations before proceeding with this Template Fan (otherwise they will interact and mangle the results).

You need the following input_boolean and input_text. They serve as power and speed status-indicators for the Template Fan.

input_boolean:
  fan_power:
    name: Fan Power
input_text:
  tmp_fan_speed:
    name: Tmp Fan Speed

This Template Fan simply writes its output to the system log (it simulates controlling a physical fan). By examining the log, you’ll see the sequence of commands produced by the Template Fan.

fan:
  - platform: template
    fans:
      my_fan:
        value_template: "{{ states('input_boolean.fan_power') }}"
        speed_template: "{{ states('input_text.tmp_fan_speed') }}"
        speeds: ['1', '2', '3', '4', '5', '6', '7', '8']
        turn_on:
          - service: system_log.write
            data_template:
              message: "POWER ON {{ now() }}"
              level: warning
          - service: input_boolean.turn_on
            entity_id: input_boolean.fan_power
        turn_off:
          - service: system_log.write
            data_template:
              message: "POWER OFF {{ now() }}"
              level: warning
          - service: input_boolean.turn_off
            entity_id: input_boolean.fan_power
        set_speed:
          - service: system_log.write
            data_template:
              level: warning
              message: >
                {% set target = speed | int %}
                {% set last_speed = states('input_text.tmp_fan_speed') | int %}
                {% set last_speed = 1 if last_speed == 0 else last_speed %}
                {% if target > last_speed %}
                  {% set steps = target - last_speed %}
                {% else %}
                  {% set steps = (8 - last_speed) + target %}
                {% endif %}
                Last = {{last_speed}}, Target = {{target}}, Steps = {{steps}}
          - service: input_text.set_value
            data_template:
              entity_id: input_text.tmp_fan_speed
              value: "{{ speed }}"

Here’s an example of the Template Fan’s output in the system log (read from bottom up). In this case, the fan is already on and we change the speed from 7 to 3 and then from 3 to 6. Notice that before each speed-change it sends a power-on command. Ideally, it should only do this if it detects the fan’s current power-state is off. However, it appears to do this even if the fan is already on (a behavior that will cause failure if the fan is only capable of power-toggling).

Screenshot%20from%202019-07-08%2008-39-08


FWIW, I’ve looked at the Template Fan’s source code but haven’t yet identified the part where it sends a power-on before each speed-change. I’m not fully fluent in python nevertheless you’d think it would be easy to find this interaction. Yet, the code suggests it does not send a power-on before each speed-change. :thinking:

As soon I am back home today I will try your script for controlling my fan. And about the template fan I have been searching as well, I also cannot see anything that sends de power-on command. As far as i know the set_speed function does nothing else then controlling the speed. Do you know if there is a way to contact the contributors of the code?

And about the part that it’s sending the power on command, is there no way to make a template for the on and off command that first checks if the fan is already on or off and if so then does nothing? If I understand it correctly it’s using the power on and off commands from the template fan.

Yes and good thinking! All it takes is just one condition. Here’s the template fan simulator with the condition. I tested it and the power-on command is now suppressed for speed-changes.

fan:
  - platform: template
    fans:
      my_fan:
        value_template: "{{ states('input_boolean.fan_power') }}"
        speed_template: "{{ states('input_text.tmp_fan_speed') }}"
        speeds: ['1', '2', '3', '4', '5', '6', '7', '8']
        turn_on:
          - condition: state
            entity_id: input_boolean.fan_power
            state: 'off'
          - service: system_log.write
            data_template:
              message: POWER ON {{now()}}
              level: warning
          - service: input_boolean.turn_on
            entity_id: input_boolean.fan_power
        turn_off:
          - service: system_log.write
            data_template:
              message: "POWER OFF {{ now() }}"
              level: warning
          - service: input_boolean.turn_off
            entity_id: input_boolean.fan_power
        set_speed:
          - service: system_log.write
            data_template:
              level: warning
              message: >
                {% set target = speed | int %}
                {% set last_speed = states('input_text.tmp_fan_speed') | int %}
                {% set last_speed = 1 if last_speed == 0 else last_speed %}
                {% if target > last_speed %}
                  {% set steps = target - last_speed %}
                {% else %}
                  {% set steps = (8 - last_speed) + target %}
                {% endif %}
                Last = {{last_speed}}, Target = {{target}}, Steps = {{steps}}
          - service: input_text.set_value
            data_template:
              entity_id: input_text.tmp_fan_speed
              value: "{{ speed }}"

Screenshot%20from%202019-07-09%2006-12-28

I’ll revise the template fan’s configuration to work with the Broadlink device and python_script.

Here are all the required parts for the Template Fan solution.

Inputs

input_boolean:
  status_fan_power:
    name: Status Fan Power
input_text:
  status_fan_speed:
    name: Status Fan Speed

Template Fan

fan:
  - platform: template
    fans:
      my_fan:
        value_template: "{{ states('input_boolean.status_fan_power') }}"
        speed_template: "{{ states('input_text.status_fan_speed') }}"
        speeds: ['1', '2', '3', '4', '5', '6', '7', '8']
        turn_on:
          - condition: state
            entity_id: input_boolean.status_fan_power
            state: 'off'
          - service: broadlink.send
            data:
              host: '192.168.1.10'
              packet: 'JgBQAAABJZMSExE3EhMRExE4ETgRExERfODAAAAAAAAAA='
          - service: input_boolean.turn_on
            entity_id: input_boolean.status_fan_power
        turn_off:
          - service: broadlink.send
            data:
              host: '192.168.1.10'
              packet: 'JgBQAAABJZMSExE3EhMRExE4ETgRExERfODAAAAAAAAAA='
          - service: input_boolean.turn_off
            entity_id: input_boolean.status_fan_power
        set_speed:
          - service: python_script.fan_speed_control
            data_template:
              fan_speed: "{{ speed }}"
          - service: input_text.set_value
            data_template:
              entity_id: input_text.status_fan_speed
              value: "{{ speed }}"

Python_script

fan_speed_control.py

# Set code to whatever RF code your fan uses for increasing speed.
code = 'JgBQAAISEjcSEhISEhISExETETgRNhMTETgROBAAAAAAAAAA='

speed = data.get('fan_speed')
status_speed = hass.states.get('input_text.status_fan_speed')

if speed is not None:
  speed = int(speed)
  last_speed = int(status_speed.state) if status_speed.state else 1
  if 1 <= speed <= 8:
    if speed > last_speed:
      loop = speed - last_speed
    else:
      loop = (8 - last_speed) + speed

    # Set the IP address to match the one used by your Broadlink device
    service_data = {'host':'192.168.1.10', 'packet':'{}'.format(code)}
    for i in range(loop):
      hass.services.call('broadlink', 'send', service_data, False)
      time.sleep(0.5)
  else:
    logger.warning('<fan_speed_control> Received fan speed is invalid ({})'.format(fan_speed))
else:
  logger.warning('<fan_speed_control> Received fan speed is invalid (None)')
5 Likes

Awsome it works! the only problem I have left is that homekit expects that the first speed is off…


I haven’t tested it because I am not home at the moment but when I do I will check if it can also work without the first speed to be off.

Glad to hear it. Please mark my post with the ‘Solution’ tag so other users can find it quickly (in this long thread).

I wasn’t aware of the new requirement to have the first speed-level represent off. I imagine we could achieve that by creating a zeroth speed level. However, it means the python_script must be enhanced to handle zero as a power-off command. It would first check if input_boolean.status_fan_power is on and then send a power-off command.

Hi, All the STANDING FAN that I see on stores have the power plug AND a button, so its not enough to give power to the power plug remotely, but you need also to phisically push a button: I know I could open it and put a NodeMCU+Relay, but that’s a lot of work and probably very difficult to have it well integrated (little internal space)

Any link for a FAN that can be used with an ESP or WiFi power plug (no need to press a button) or (less preferred) in alternative something with IR, and I would use a Broadlink to command it?

Sorry buddy, completely irrelevant to this thread, suggest you start another.

Thanks @123 and @BeyondRico! I tweaked it slightly to work with my fan that has separate + and - speed buttons. It works fine from HA.

Is there a way to get Google Assistant/Home to recognize the speed controls for voice control? It looks like there’s support for it, but it doesn’t seem to work. I’ve been trying “set the bedroom fan to speed 5” and “set the bedroom fan to maximum”.

Thanks!

Someone else will have to respond to your question because I don’t have any experience using Google Assistant with Home Assistant.

1 Like

Hey. Care to share your config for the desperate +/- buttons?

Hi,

Sorry for the late reply! This is what I did. Hope it makes sense to you, as I’ve forgotten what I did! :sweat_smile:

configuration.yaml

input_boolean:
  status_fan_power:
    name: Status Fan Power

input_text:
  status_fan_speed:
    name: Status Fan Speed

fan:
  - platform: template
    fans:
      bedroom_fan:
        friendly_name: "Bedroom Fan"
        value_template: "{{ states('input_boolean.status_fan_power') }}"
        speed_template: "{{ states('input_text.status_fan_speed') }}"
        speeds: ['1', '2', '3', '4', '5', '6', '7', '8', '9','10']
        turn_on:
          - condition: state
            entity_id: input_boolean.status_fan_power
            state: 'off'
          - service: broadlink.send
            data:
              host: '192.168.1.103'
              packet: 'JgAkAEobGTMbGhkbGTIbGhkaGRoZHBkbGRsZGhkbGRoZGhkyGgANBQAAAAA='
          - service: input_boolean.turn_on
            entity_id: input_boolean.status_fan_power
        turn_off:
          - service: broadlink.send
            data:
              host: '192.168.1.103'
              packet: 'JgAkAEobGTMbGhkbGTIbGhkaGRoZHBkbGRsZGhkbGRoZGhkyGgANBQAAAAA='
          - service: input_boolean.turn_off
            entity_id: input_boolean.status_fan_power
        set_speed:
          - service: python_script.fan_speed_control
            data_template:
              fan_speed: "{{ speed }}"
          - service: input_text.set_value
            data_template:
              entity_id: input_text.status_fan_speed
              value: "{{ speed }}"

I have been looking into Dialogflow for creating a custom section in google assistant. However did not go fully into it because of lack of time. Maybe this is something that could help you.

Just wanted to say A BIG THANK YOU because I finally managed to get the Template Fan to work (documentation is REALLY POOR on this component). The solution to this thread should be added to the example section of the Template Fan page.
Only problem I encountered is that for some reason sometimes the “else 1” part of the script didn’t work and since my fan always does a “clean” start with fan at 1 ad oscillation off I added this to the turn on part:

          - service: input_text.set_value
            data:
              entity_id: input_text.ventilatore_sala_fan_speed
              value: 1

Maybe it’s overkill for someone, but worked for me.

Some improvements I’d like to suggest:

  1. I’d make the script as much plug and play as possible. Isn’t it possible to get the number of speeds from the component (the length of the speeds array)?
  2. I’d make the broadlink part easier to setup, by using the alternative method to send commands:
- service: remote.send_command
            data:
              entity_id: remote.broadlink
              device: ventilatore
              command: speed

Doing so I’d only need to put the device and command name in the script, that should be easier than looking for the IP and the command string.

Now I’ll try to work on the script to make it work with oscillation modes, that are 4 in my fan (fixed, left/right, up/down and a mix of the two) while the Template only has on and off. I thinks that it can’t be done directly with the template but I’ll have to make some extra text_select and the automations to change it in cycle via the script.
Maybe if I succeed I’ll post the solution here.

OK, I added the oscillation mode as a separate input_select and here’s my solution:

1 setup input_select: First I define an input select containing the modes starting with a number (I extract the number in the script to cycle correctly, so this is mandatory):

input_select:

  ventilatore_sala_oscillation_mode:
    name: 'Ventilatore sala oscillazione'
    icon:  mdi:arrow-decision-outline
    options:
      - '1 - off'
      - '2 - destra sinistra'
      - '3 - su giù'
      - '4 - misto'

2. revert to 1 at every turno_on: Then in the Template Fan I add an action on turn_on so that the input_select is back to 1 at startup (remember that my fan start from fresh no oscillation, fan speed 1 at every startup - it may or may not be necessary for your setup but then I’d lock or hide the control when the fan is off), my full turn_on automation goes like this:

turn_on:
          - condition: state
            entity_id: input_boolean.ventilatore_sala_stato
            state: 'off'
          - service: remote.send_command
            data:
              entity_id: remote.broadlink
              device: ventilatore
              command: power
          - service: input_select.select_option
            data:
              entity_id: input_select.ventilatore_sala_oscillation_mode
              option: '1 - off'
          - service: input_boolean.turn_on
            entity_id: input_boolean.ventilatore_sala_stato
          - service: input_text.set_value
            data:
              entity_id: input_text.ventilatore_sala_fan_speed
              value: 1

I added the same automation on turn_off, even if it’s not necessary, but it’s a sort of “reset” when turning off.
3 - automate script launch when the input_select changes Then I setup an automation that feeds the script with the from and to state of input_select:

- id: '1598781094128'
  alias: Fan - modo oscillazione
  description: ''
  trigger:
  - entity_id: input_select.ventilatore_sala_oscillation_mode
    platform: state
  condition:
  - condition: state
    entity_id: fan.ventilatore_sala
    state: 'on'
  action:
  - data_template:
      oscillation_mode: '{{ trigger.to_state.state }}'
      oscillation_mode_from: '{{ trigger.from_state.state }}'
    service: python_script.fan_oscillation
  mode: single

**4 - The script: ** and finally the modified script goes like this, set broadlink IP, code and number of states in the input_select accordingly to your setup (I’m no python programmer so excuse if it’s not very clean code):

# Set code to whatever RF code your fan uses for oscillation mode.
code = 'JgByAJWUETERMRIxETERMRIxEVERMhExETESMRFREVESURExETIRMZaUETERMREyETERMRExElERMhExETERMRJREVERUhExETESAAGhAAEpSBMABo4AAShKEgAGjgABKEoRAAaPAAEnShIACPUGAAzFCAANBQAAAAA='

oscillation_raw = data.get('oscillation_mode')
status_oscillation_raw = data.get('oscillation_mode_from')
# status_oscillation_raw = hass.states.get('input_select.ventilatore_sala_oscillation_mode')

# extracting the first charachter that is the mode in numeric form
# maybe define an array and compare?
oscillation = oscillation_raw[0]
# logger.warning('to:'+ oscillation)
# logger.warning(oscillation_raw[0])
status_oscillation = status_oscillation_raw[0]
# logger.warning('from:' + status_oscillation)
# logger.warning(status_oscillation_raw[0])

if oscillation is not None:
  oscillation = int(oscillation)
  # last_oscillation = int(status_oscillation.state) if status_oscillation.state else 1
  last_oscillation = int(status_oscillation)
  if 1 <= oscillation <= 4:
    if oscillation > last_oscillation:
      loop = oscillation - last_oscillation
    else:
      loop = (4 - last_oscillation) + oscillation

    # Set the IP address to match the one used by your Broadlink device
    service_data = {'host':'192.168.0.195', 'packet':'{}'.format(code)}
    for i in range(loop):
      hass.services.call('broadlink', 'send', service_data, False)
      time.sleep(0.5)
  else:
    logger.warning('<fan_oscillation_control> Received fan oscillation is invalid ({})'.format(fan_oscillation))
else:
  logger.warning('<fan_oscillation_control> Received fan oscillation is invalid (None)')

All improvements are strongly welcome.
Thanks again to everyone that allowed me to get it to work.