Need help with making a dumb fan smart

Hi,

I have a dumb fan that I now control with an broadlink rm mini 3. I want to combine these scripts in an template fan everything is going to work except for the part with the speeds. My fan has 8 speeds and uses the same ir code to increase the speed with 1. I want to store the last known speed (for this example it’s 5) then when choosing speed 2 it must calculate that it first has to increase 4 times more to go back to 1 and then increase one more time to get to speed 2. is there someone that can help me?

Three months ago, I helped someone with a similar requirement. I’ll tell you what I told them, whatever solution devised to handle this kind of system will be fragile. By that I mean it can easily lose track of the device’s true state. That’s the drawback of attempting to control any system that provides no feedback about its current state. If you’re willing to accept that whatever solution you create can be easily derailed (such as the device failing to receive a single fan-speed command) then you’ll be less disappointed when it ultimately does misbehave.

It’s not a short thread …

Long story short, I suggested the use of python_script to handle the calculations for controlling brightness (fan speed in your case). Be advised the first version of the suggested python_script was based on an older version of the Broadlink integration. It was updated, later in the thread, to accommodate the breaking changes introduced by a later version of the Broadlink integration.

You appear to have a good grasp of what’s needed to solve this problem. The last-known speed will need to be stored somewhere, such as in an input_number. The calculation will refer to it when determining the number of fan-speed commands to send (and the direction; increase or decrease).

Thank you for your response,

I am willing to take the risk of getting out of sync with home assistant. In the ui I wil make a card with the last known speed and manually adjust the fan to get it back into sync. In the next few days I will try to follow your instructions on the other post. the only problem is that I don’t have a way to decrease the speed, when increasing after speed 8 it goes back to speed 1. so I need to figure that out myself. I was meanly asking this question because i never programmed in home assistant before and was hoping someone had the same problem as me, and clearly there is(kinda).

if I run into some problems is it ok if ask for you help again?

That’s the kind of information you’ll need to reveal up front before we begin designing a solution.

  • The fan has 8 speeds.
  • Pressing the speed button causes the fan to increase the speed.
  • It will increase up to the highest speed (8) then restart from the lowest speed (1).

Questions:

  • Is level 1 the lowest speed or off?
  • Are there any other buttons to control the fan such as power or a preset speed level?
  • If you turn the fan off and then turn it on, what speed level does it use on startup? The lowest level or the last-used level?

First of all sorry for the late response,

No it goes back to 1 after speed 8 not turning the fan off.

There is one button to turn the fan on and off

It remembers the last known speed, only when I unplug it it’s going back to speed 1 (as I told you before will manually bring it back into sync).

I was thinking to somehow calculate that from speed 5 to speed 2 it needs to increase the speed five times, then use a for loop(not foreach) and use that number to iterate thru the for loop five times sending the increase command in each iteration. is there a way of doing this?

Yes, that’s what was achieved by the python_script I had created for the other person. The difference was that you supplied it with either a positive or negative number to increase or decrease the brightness level.

In your case, you’d supply it exclusively with a positive number (representing the desired fan speed). The python_script would compare it to the fan’s current speed (that has been stored in an input_number) and calculate how many speed-commands must be sent to reach the desired speed.

For example, if the current speed is 6 and you want level 2, it has to step 4 times (7, 8, 1, 2) to reach level 2. If the speed is 2 and you want level 7, it has to step 5 times (3, 4, 5, 6, 7). The python_script will contain the formula to calculate these values including the decision to first send a power-on command in the event the fan is currently off.

I experimented with the Template Fan in the hope it could be used for your project.

I thought I had it working until I noticed that whenever you change the speed it first sends a ‘turn on’ command before sending the speed command. That’s not going to work for your fan because you said it has only one button to turn the fan on/off. That means it is power-toggle button and there’s only one command used for both turning the fan on and off. Unfortunately, that arrangement won’t work with the Template Fan.

There’s no option available to suppress the transmission of a ‘turn on’ command before each speed command. That makes the Template Fan unsuitable for your project.

Back to the drawing board …

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.