Here in coastal California, air conditioning is extremely uncommon. So during a recent heat wave, I got desperate and went down to the big box store in search of some relief and came home with a regular old dumb fan. I’m sure this is the kind of gadget you could probably spend hundreds of dollars on for a ‘smart’ version, but I’m more in favor of adapting existing, cheap technology to do my bidding instead. And since my dumb fan came with an infrared remote control, it’s quite simple to integrate it into the smart home using Node-Red and Home Assistant.
GETTING MY DUMB FAN AN EDUCATION
What does it mean to say a gadget is ‘smart’? It needs to be able to report its state somehow, and accept commands to change that state. Any old device with a remote can be controlled via an IR transmitter, but we need a way to observe what it’s doing as well. For my dumb fan, we can observe the amount of power used to determine it’s state (off, low, medium, or high).
HARDWARE
To accomplish this I’m using:
- A dumb fan with remote
- Broadlink RM Mini or RM Pro for infrared control
- Sonoff POW with Sonoff-Tasmota firmware
For a DIY alternative to the Broadlink, try OpenMQTTGateway, and for a commercial alternative to the Sonoff POW try the Wemo Insight Switch.
INITIAL STEPS
With the Sonoff POW flashed with the Tasmota firmware, I need to make some observations. What range of power is used with the fan set at each speed setting?
On my fan it came out to:
- Low <= 29
- Medium <= 34
- High > 34
Next I captured the IR codes with the Broadlink RM. My fan just has two commands: toggle power, and toggle speed.
HOME ASSISTANT ENTITIES
The first step to getting this thing smart-ified is quantifying it’s state in Home Assistant. So to get the power reading for the Sonoff Pow with the Tasmota firmware, my sensors.yaml configuration is:
- platform: mqtt name: "Fan Power" state_topic: "tele/fan/SENSOR" value_template: '{{ value_json["ENERGY"]["Power"] }}' unit_of_measurement: "W"
I then make a template sensor that converts that power reading into the actual fan speed:
- platform: template sensors: fan_speed_actual: friendly_name: Actual Fan Speed value_template: >- {% if states.sensor.fan_power.state | float <= 5 %} Off {% elif states.sensor.fan_power.state | float <= 29 %} Low {% elif states.sensor.fan_power.state | float <= 34 %} Medium {% elif states.sensor.fan_power.state | float > 34 %} High {% else %} Unknown {% endif %}
For my IR commands I make a script so I can just have the IR packet in one place. This is in my scripts.yaml file:
fan_power_toggle: alias: Fan Power Toggle sequence: - service: switch.broadlink_send_packet_192_168_0_xx data: packet: - "JgBoAAABJ5MTNxISExITEhMSEhMUEBUQFBEUNRU1FDUVNRQ1FTUUNRU1FDUVEBUQFBEUERQQFRAUERQRFDUVNRQ1FTUUNRU1FDUVNRQRFBAVEBQRFBEUEBUQFBEUNRQ2EzYVNRQ2EjcTAA0F" fan_speed_toggle: alias: Fan Speed Toggle sequence: - service: switch.broadlink_send_packet_192_168_0_xx data: packet: - "JgBoAAABKJMTNhQRExITERQRFBEUERMRFBEUNBU2FDYTNhQ2EzYUNhM2FDYUNRQRFBEUERMRFBEUERQRExITNhQ2EzYUNhM2FDYTNhQ2ExEUERQRFBETEhMRFBEUERM2FDYTNhQ2FDUUAA0F"
I now have all the commands and info needed to automate the fan, we just need a little Node-Red logic to put it together.
A SMART FAN IS BORN
Ok now for the fun part, we have all the information we need – how do we make this into a smart fan?
Home Assistant provides us with an MQTT fan template. The MQTT entity kind of acts like a shell that our Node-Red logic can inject it’s smarts into. Node-Red will read the sensors we created above, and keep the state of the MQTT fan entity in sync with the observed state. When commands are issued, Node-Red will figure out what to do and issue the appropriate IR signal to the actual fan.
MQTT FAN
fan: - platform: mqtt name: "Floor Fan" state_topic: "floor_fan/on/state" command_topic: "floor_fan/on/set" speed_state_topic: "floor_fan/speed/state" speed_command_topic: "floor_fan/speed/set" availability_topic: "tele/fan/LWT" payload_available: "Online" payload_not_available: "Offline" qos: 0 payload_on: "true" payload_off: "false" payload_low_speed: "low" payload_medium_speed: "medium" payload_high_speed: "high" speeds: - low - medium - high
The availability_topic is the last-will-and-testament message from the Sonoff POW – this tells Home Assistant if the device is online or not. The rest of the fan topics and payloads I made up and you can make them whatever makes sense to you.
Home Assistant and Node-Red will use these topics to send the defined messages back and forth.
NODE-RED LOGIC
Click here to import this whole flow into Node-Red.
UPDATE FAN ENTITY WITH ACTUAL FAN STATE
First thing we need to do is make sure the fan.floor_fan entity I created above is reflecting reality accurately. If the power is at 0 it should be set to ‘Off’, if it’s not ‘Off’ then I need to know what speed it’s set at.
As the sensor.fan_speed_actual changes based on the power reading, Node-Red will send an MQTT message to Home Assistant so that it can update the fan.floor_fan entity.
This means it doesn’t matter how the state of the fan is changed (via Home Assistant command, it’s own remote, MQTT message, or the button on the actual device) the state will be accurately tracked.
SEND ON/OFF COMMANDS TO FAN
If the fan entity is changed to on or off in Home Assistant, we need to send that command to the fan via IR.
CHANGING FAN SPEEDS
This was the only sort of tricky part. My fan has 3 speeds – low, medium, high – and only a toggle to control them. This means to switch between speeds you either have to press the toggle once or twice to get to the desired speed.
When a command to change the fan speed is given, the actual speed (sensor.fan_speed_actual) is compared to the desired speed (the payload on the floor_fan/speed/set MQTT topic). I then have a function that determines the appropriate number of toggles.
The function node looks like:
newmsg = {}; currentSpeed = msg.payload["sensor.fan_speed_actual"].toLowerCase(); desiredSpeed = msg.payload["floor_fan/speed/set"].toLowerCase(); if (currentSpeed == desiredSpeed) { newmsg.payload = 0; } else if (currentSpeed == "low" && desiredSpeed == "medium") { newmsg.payload = 1; } else if (currentSpeed == "high" && desiredSpeed == "low") { newmsg.payload = 1; } else if (currentSpeed == "low" && desiredSpeed == "high") { newmsg.payload = 2; } else if (currentSpeed == "medium" && desiredSpeed == "high") { newmsg.payload = 1; } else if (currentSpeed == "medium" && desiredSpeed == "low") { newmsg.payload = 2; } else if (currentSpeed == "high" && desiredSpeed == "medium") { newmsg.payload = 2; } else { newmsg.payload = "???? Current " + currentSpeed + " Desired " + desiredSpeed; } return newmsg;
Pretty simple!
CONCLUSION
So now my dumb fan can be controlled by my automations in Home Assistant, and it’s state will be accurately tracked regardless of how people interact with it (whether through the remote or pushing the fan’s physical buttons). This process can be repeated for pretty much any dumb device that can be controlled through a remote!
RESOURCES
- Home Assistant MQTT Fan
- Home Assistant Broadlink RM documentation
- Pastebin link for Node-Red Flow
- Sonoff-Tasmota firmware
Good write up. What if I don’t have a Sonoff POW and I don’t have a smart switch although I do have the Black Bean Mini. What I’m trying to achieve is that, when we switch on the fan via the dumb switch, based on the current temp of the room/outdoors, the fan will dial to the necessary speed (I have a 5-speed fan). Is that doable with Node-Red?
Sure, but you won’t be able to observe the fan state directly.
Excellent write up Brad, thank you for building this. Your information fills in a lot of missing areas in the current documentation of Home Assistant / Node-RED interfacing.
This is one of my favorites. Unfortunately due to the new Fan MQTT layout in home assistant it’s stopped working. Do you have any plans on an update for this automation. Thanks
30 Oct 05:51:27 – [error] [function:Here to There] TypeError: Cannot read property ‘toLowerCase’ of undefined