Open michaelwoods opened 5 years ago
Right now you could probably do this as a custom output that turned the various binary outputs on and off as necessary.
I'm not sure if @OttoWinter has plans to implement template binary output or template float output, but it seems like those are a logical next step given the other cool things he's allowed us to do with templates + lambdas. A template float output would let you do what you want without being specifically tied to the speed fan use case.
Yeah I guess template outputs can also arrive at some time. For now, I would suggest using the custom output (usage is quite simple and pretty much exactly the same as it would be in a template output, just with more boilerplate code)
Or... Use a Home Assistant template fan. Probably a bit easier.
I'm looking into implementing Sonoff's iFan02, which also requires a mapping from analog speeds to discreet relay outputs. This should be straightforward to do with a custom float output. What I'm not sure about is how to integrate both off/on messages and speed commands. The HASS fan UI card has an on/off switch that toggles the fan's motion. Ideally the fan's speed should be retained independently of powering the fan on and off. In the ESPHome documentation, it's not clear to me if there's functionality that would allow both power state and speed to be stored/referenced locally and through the HASS API. I'm contemplating writing some "off->speed 0" logic using a HA template fan, but local logic and storage would be preferable. Any advice @OttoWinter ?
What I'm not sure about is how to integrate both off/on messages and speed commands.
That is handled by ESPHome, custom float outputs only have to map values 0.0 (off), 0.33 (low), 0.66 (medium), 1.0 (high) to the relays.
All of the things you mentioned are handled by ESPHome, you only need to implement the custom float output.
I'm trying to create this and having a lot of difficulty, is someone able to post a full example including the fan component and not just the output?
How the ids tie to the relays would be great also!
Maybe if I explain what I want to achieve I might get better responses...
I plan to use 3 relays to control the speeds of a ceiling fan, I have the wiring part sorted.
I want to use esphome and create rules so the fan can still be used if the wifi goes down.
I plan to have essentially 2 buttons, one to increase the speed (but goes from high back to off), and the other button decreases the speed (goes from off to high).
I would like to use a pwm led to represent the speeds (0% off, 33% low, 66% med, 100% high).
I would love if it could be displayed like a fan component in home assistant! But if it needs to be a slider or a drop down menu I can do that.
My thoughts was to use lamda code to modify a global variable to count from 0-3 (and loops around) and this relates to the speeds. The counting would be obviously controlled by the two buttons. 0 would turn the led and all the relays off, 1 would set the led to 33% and turn relay1 on and the other relays off, 2 would set the led to 66% and turn relay2 on and the other relays off, and so on for 3.
I'm not sure if the speed select in home assistant would directly modify the global variable or if they can tie together somehow.
I know what I need to achieve but just no idea how to write it, any help would be greatly appreciated!
check this cookbook https://deploy-preview-218--esphome.netlify.com/cookbook/ifan02.html
thanks, I think I can work with that. I managed to get it to work without the fan component (using an input select and node-red to "link" everything).
api:
ota:
globals:
- id: var_speed
type: int
restore_value: no
initial_value: '0'
switch:
- platform: gpio
pin: D4
id: relay1
inverted: yes
interlock: &interlock_group [relay1, relay2, relay3]
- platform: gpio
pin: D3
id: relay2
inverted: yes
interlock: *interlock_group
- platform: gpio
pin: D2
id: relay3
inverted: yes
interlock: *interlock_group
- platform: gpio
pin: D1
id: relay4
name: "Fan Light"
inverted: yes
- platform: template
name: "Fan Speed Off"
lambda: |-
if (id(var_speed) == 0) {
return true;
} else {
return false;
}
turn_on_action:
- switch.turn_off: relay1
- switch.turn_off: relay2
- switch.turn_off: relay3
- globals.set:
id: var_speed
value: '0'
- platform: template
name: "Fan Speed 1"
id: fan_speed_1
lambda: |-
if (id(var_speed) == 1) {
return true;
} else {
return false;
}
turn_on_action:
- switch.turn_on: relay1
- globals.set:
id: var_speed
value: '1'
turn_off_action:
- switch.turn_on: relay1
- platform: template
name: "Fan Speed 2"
id: fan_speed_2
lambda: |-
if (id(var_speed) == 2) {
return true;
} else {
return false;
}
turn_on_action:
- switch.turn_on: relay2
- globals.set:
id: var_speed
value: '2'
turn_off_action:
- switch.turn_on: relay2
- platform: template
name: "Fan Speed 3"
id: fan_speed_3
lambda: |-
if (id(var_speed) == 3) {
return true;
} else {
return false;
}
turn_on_action:
- switch.turn_on: relay3
- globals.set:
id: var_speed
value: '3'
turn_off_action:
- switch.turn_on: relay3
binary_sensor:
- platform: gpio
pin: D8
name: "Up"
id: up
on_press:
then:
- lambda: |-
if (id(var_speed) < 3) {
id(var_speed) +=1;
} else {
id(var_speed) = 0;
}
ESP_LOGD("main", "Global value is: %d", id(var_speed));
- if:
condition:
lambda: 'return id(var_speed) == 0;'
then:
- switch.turn_off: relay1
- switch.turn_off: relay2
- switch.turn_off: relay3
- if:
condition:
lambda: 'return id(var_speed) == 1;'
then:
- switch.turn_on: relay1
else:
- switch.turn_off: relay1
- if:
condition:
lambda: 'return id(var_speed) == 2;'
then:
- switch.turn_on: relay2
else:
- switch.turn_off: relay2
- if:
condition:
lambda: 'return id(var_speed) == 3;'
then:
- switch.turn_on: relay3
else:
- switch.turn_off: relay3
- platform: gpio
pin: D7
name: "Down"
id: down
on_press:
then:
- lambda: |-
if (id(var_speed) > 0) {
id(var_speed) -=1;
} else {
id(var_speed) = 3;
}
ESP_LOGD("main", "Global value is: %d", id(var_speed));
- if:
condition:
lambda: 'return id(var_speed) == 0;'
then:
- switch.turn_off: relay1
- switch.turn_off: relay2
- switch.turn_off: relay3
- if:
condition:
lambda: 'return id(var_speed) == 1;'
then:
- switch.turn_on: relay1
else:
- switch.turn_off: relay1
- if:
condition:
lambda: 'return id(var_speed) == 2;'
then:
- switch.turn_on: relay2
else:
- switch.turn_off: relay2
- if:
condition:
lambda: 'return id(var_speed) == 3;'
then:
- switch.turn_on: relay3
else:
- switch.turn_off: relay3
check this cookbook https://deploy-preview-218--esphome.netlify.com/cookbook/ifan02.html
I made a mixture of the two and got it to work perfectly, thank you very much.
So with the following code I managed to get all of what I was hoping to achieve to work perfectly. I did use the link above and modified the ifan02.h file which I'll share in my next post to include updating the light that is on my two-way switch.
I also used node-red to update from the template switches that are hidden in hassio to the fan speed settings also in hassio. Also in node-red when the fan in hassio is turned off it updates the "off" template switch in hassio so the global variable in esphome is updated correctly.
The combinations of relays on/off by default are a different combination, I will update the ons and offs according to my particular fans.
I hope this helps anyone else trying to achieve something similar.
esphome:
name: fan_test2
platform: ESP8266
board: d1_mini
includes:
- ifan02.h
on_boot:
priority: 225
# turn off the light as early as possible
then:
- switch.turn_off: mancave_light
wifi:
ssid: "*********"
password: "**********"
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Mancave Fan Fallback Hotspot"
password: "***********"
captive_portal:
# Enable logging
logger:
# Enable Home Assistant API
api:
ota:
globals:
- id: var_speed
type: int
restore_value: no
initial_value: '0'
binary_sensor:
##---right rotation on the two-way switch---------
- platform: gpio
pin: D8
id: up
on_press:
then:
- lambda: |-
if (id(var_speed) < 3) {
id(var_speed) +=1;
} else {
id(var_speed) = 0;
}
ESP_LOGD("main", "Global value is: %d", id(var_speed));
- if:
condition:
lambda: 'return id(var_speed) == 0;'
then:
- switch.turn_off: relay1
- switch.turn_off: relay2
- switch.turn_off: relay3
- if:
condition:
lambda: 'return id(var_speed) == 1;'
then:
- switch.turn_on: relay1
else:
- switch.turn_off: relay1
- if:
condition:
lambda: 'return id(var_speed) == 2;'
then:
- switch.turn_on: relay2
else:
- switch.turn_off: relay2
- if:
condition:
lambda: 'return id(var_speed) == 3;'
then:
- switch.turn_on: relay3
else:
- switch.turn_off: relay3
##---left rotation on the two-way switch---------
- platform: gpio
pin: D7
id: down
on_press:
then:
- lambda: |-
if (id(var_speed) > 0) {
id(var_speed) -=1;
} else {
id(var_speed) = 3;
}
ESP_LOGD("main", "Global value is: %d", id(var_speed));
- if:
condition:
lambda: 'return id(var_speed) == 0;'
then:
- switch.turn_off: relay1
- switch.turn_off: relay2
- switch.turn_off: relay3
- if:
condition:
lambda: 'return id(var_speed) == 1;'
then:
- switch.turn_on: relay1
else:
- switch.turn_off: relay1
- if:
condition:
lambda: 'return id(var_speed) == 2;'
then:
- switch.turn_on: relay2
else:
- switch.turn_off: relay2
- if:
condition:
lambda: 'return id(var_speed) == 3;'
then:
- switch.turn_on: relay3
else:
- switch.turn_off: relay3
##----sensors for updating the global varible------
##----these are tied to the relays-----------------
- platform: gpio
pin:
number: D4
inverted: True
id: fan_relay1
on_press:
then:
- globals.set:
id: var_speed
value: '1'
- platform: gpio
pin:
number: D3
inverted: True
id: fan_relay2
on_press:
then:
- globals.set:
id: var_speed
value: '2'
- platform: gpio
pin:
number: D2
inverted: True
id: fan_relay3
on_press:
then:
- globals.set:
id: var_speed
value: '3'
output:
- platform: custom
type: float
outputs:
id: fanoutput
lambda: |-
auto mancave_fan = new ManCaveFanOutput();
App.register_component(mancave_fan);
return {mancave_fan};
##-----ring light on two-way switch------
- platform: esp8266_pwm
id: switch_light
pin: D5
switch:
##-----template switches----------
- platform: template
name: "Fan Speed Off"
lambda: |-
if (id(var_speed) == 0) {
return true;
} else {
return false;
}
turn_on_action:
- switch.turn_off: relay1
- switch.turn_off: relay2
- switch.turn_off: relay3
- output.set_level:
id: switch_light
level: 0%
- globals.set:
id: var_speed
value: '0'
- platform: template
name: "Fan Speed 1"
id: fan_speed_1
lambda: |-
if (id(var_speed) == 1) {
return true;
} else {
return false;
}
turn_on_action:
- switch.turn_on: relay1
- output.set_level:
id: switch_light
level: 10%
- globals.set:
id: var_speed
value: '1'
turn_off_action:
- switch.turn_on: relay1
- platform: template
name: "Fan Speed 2"
id: fan_speed_2
lambda: |-
if (id(var_speed) == 2) {
return true;
} else {
return false;
}
turn_on_action:
- switch.turn_on: relay2
- output.set_level:
id: switch_light
level: 50%
- globals.set:
id: var_speed
value: '2'
turn_off_action:
- switch.turn_on: relay2
- platform: template
name: "Fan Speed 3"
id: fan_speed_3
lambda: |-
if (id(var_speed) == 3) {
return true;
} else {
return false;
}
turn_on_action:
- switch.turn_on: relay3
- output.set_level:
id: switch_light
level: 100%
- globals.set:
id: var_speed
value: '3'
turn_off_action:
- switch.turn_on: relay3
##-----Relays-----------------
- platform: gpio
pin: D4
id: relay1
inverted: yes
- platform: gpio
pin: D3
id: relay2
inverted: yes
- platform: gpio
pin: D2
id: relay3
inverted: yes
- platform: gpio
pin: D1
id: mancave_light
name: "Mancave light"
inverted: yes
fan:
- platform: speed
output: fanoutput
id: mancave_fan
name: "Mancave Fan"
and this is the ifan02.h file...
#include "esphome.h"
using namespace esphome;
class ManCaveFanOutput : public Component, public FloatOutput {
public:
void write_state(float state) override {
if (state < 0.3) {
// OFF
digitalWrite(2, HIGH); //D4 on D1 Mini
digitalWrite(0, HIGH); //D3 on D1 Mini
digitalWrite(4, HIGH); //D2 on D1 Mini
analogWrite(14, 0); //D5 on D1 Mini (led status light) 0%
} else if (state < 0.6) {
// low speed
digitalWrite(2, LOW);
digitalWrite(0, HIGH);
digitalWrite(4, HIGH);
analogWrite(14, 25); //25 is 10% brightness
} else if (state < 0.9) {
// medium speed
digitalWrite(2, HIGH);
digitalWrite(0, LOW);
digitalWrite(4, HIGH);
analogWrite(14, 127); // 127 is 50% brightness
} else {
// high speed
digitalWrite(2, HIGH);
digitalWrite(0, HIGH);
digitalWrite(4, LOW);
analogWrite(14, 255); //255 is 100% brightness
}
}
};
FYI - this is mostly doable with a Custom Output. The current issue is that there's no way to feed back the fan state if there's additional input to the current system. For example, I'm wrapping a Tuya dimmer as a FloatOutput so that I can use it to control a fan. However, if I turn the fan off using the actual switch on the dimmer, there's no way to tell the fan that the output value has changed, since the output is write-only.
FYI - this is mostly doable with a Custom Output. The current issue is that there's no way to feed back the fan state if there's additional input to the current system. For example, I'm wrapping a Tuya dimmer as a FloatOutput so that I can use it to control a fan. However, if I turn the fan off using the actual switch on the dimmer, there's no way to tell the fan that the output value has changed, since the output is write-only.
This is not true, I was able to get it to work. I have written a new version that seems to work a lot better
esphome:
name: newer_fan_test
platform: ESP8266
board: d1_mini
includes:
- ifan02.h
wifi:
ssid: "****"
password: "****"
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Newer Fan Test Fallback Hotspot"
password: "****"
captive_portal:
# Enable logging
logger:
# Enable Home Assistant API
api:
ota:
binary_sensor:
- platform: gpio
id: motion
name: "Mancave motion"
pin: D1
device_class: motion
on_press:
then:
if:
condition:
- switch.is_off: mancave_light
then:
- light.turn_on:
id: button_led
brightness: 100%
- delay: 10s
- light.turn_off: button_led
- platform: template
id: led_low
lambda: |-
if (id(test_fan).speed == 0){
if(id(test_fan).state){
return true;
} else {
return false;
}
} else {
return false;
}
on_press:
then:
- light.turn_on:
id: switch_led
brightness: 30%
- switch.turn_on:
id: dim_led
- platform: template
id: led_mid
lambda: |-
if (id(test_fan).speed == 1){
if(id(test_fan).state){
return true;
} else {
return false;
}
} else {
return false;
}
on_press:
then:
- light.turn_on:
id: switch_led
brightness: 60%
- switch.turn_on:
id: dim_led
- platform: template
id: led_high
lambda: |-
if (id(test_fan).speed == 2){
if(id(test_fan).state){
return true;
} else {
return false;
}
} else {
return false;
}
on_press:
then:
- light.turn_on:
id: switch_led
brightness: 100%
- switch.turn_on:
id: dim_led
- platform: template
id: led_off
lambda: |-
if (id(test_fan).state){
return false;
} else {
return true;
}
on_press:
then:
- light.turn_off:
id: switch_led
- switch.turn_off:
id: dim_led
##---light switch button--------------------------
- platform: gpio
pin: D6
id: light_button
on_release:
then:
- switch.toggle: mancave_light
- if:
condition:
- switch.is_on: mancave_light
then:
- light.turn_off: button_led
##---right rotation on the two-way switch---------
- platform: gpio
pin: D8
id: up
on_press:
then:
- lambda: |-
if (id(test_fan).state) {
if (id(test_fan).speed == 0) {
id(fan_med).turn_on();
ESP_LOGD("main", "Fan set to Medium");
} else {
if (id(test_fan).speed == 1){
id(fan_high).turn_on();
ESP_LOGD("main", "Fan set to High");
} else {
id(fan_off).turn_on();
ESP_LOGD("main", "Fan set to Off");
}
}
} else {
id(fan_low).turn_on();
ESP_LOGD("main", "Fan set to Low");
}
- platform: gpio
pin: D7
id: down
on_press:
then:
- lambda: |-
if (id(test_fan).state) {
if (id(test_fan).speed == 0) {
id(fan_off).turn_on();
ESP_LOGD("main", "Fan set to Off");
} else {
if (id(test_fan).speed == 1){
id(fan_low).turn_on();
ESP_LOGD("main", "Fan set to Low");
} else {
id(fan_med).turn_on();
ESP_LOGD("main", "Fan set to Medium");
}
}
} else {
id(fan_high).turn_on();
ESP_LOGD("main", "Fan set to High");
}
fan:
- platform: speed
output: fanoutput
id: test_fan
name: "Test Fan"
output:
- platform: custom
type: float
outputs:
id: fanoutput
lambda: |-
auto test_fan = new FanOutput();
App.register_component(test_fan);
return {test_fan};
##-----ring light on two-way switch------
- platform: esp8266_pwm
id: switch_light
pin: D0
##-----ring light for light button-------
- platform: esp8266_pwm
id: button_light
pin: D5
switch:
- platform: gpio
pin: D4
id: relay1
inverted: yes
- platform: gpio
pin: D3
id: relay2
inverted: yes
- platform: gpio
pin: D2
id: relay3
inverted: yes
- platform: gpio
pin: GPIO1
id: mancave_light
name: "Mancave light"
inverted: yes
restore_mode: "RESTORE_DEFAULT_OFF"
- platform: template
id: fan_off
lambda: |-
if (id(test_fan).state){
return false;
} else {
return true;
}
turn_on_action:
- fan.turn_off:
id: test_fan
- platform: template
id: fan_low
turn_on_action:
- fan.turn_on:
id: test_fan
speed: LOW
- platform: template
id: fan_med
turn_on_action:
- fan.turn_on:
id: test_fan
speed: MEDIUM
- platform: template
id: fan_high
turn_on_action:
- fan.turn_on:
id: test_fan
speed: HIGH
- platform: template
id: dim_led
turn_on_action:
- delay: 5s
- if:
condition:
- switch.is_on: fan_off
then:
- light.turn_off:
id: switch_led
else:
- light.turn_on:
id: switch_led
brightness: 10%
light:
- platform: monochromatic
name: "Mancave fan switch led"
id: switch_led
output: switch_light
default_transition_length: 0.5s
- platform: monochromatic
name: "Mancave light switch led"
id: button_led
output: button_light
default_transition_length: 0.5s
what it is connected to.
There's no way to get it to work without some additional code, that is. I ended up with a much shorter version of what you have, using an interval lambda that syncs the light value back to the fan speed.
Oh, yeah at the moment you require the extra code. Mine would be a lot smaller if I didn't use the led ring as an indicator as what speed you are on.
One day there will be a solution without a custom component and life will be easy.
@michaelwoods did you ever figure something out?
@Apogee19832312 do you have a guide posted somewhere? i'm having the same problem: i need to convert a 3 speed fan (pull string) that also have a reverse switch. The light does not matter as i used an ikea bulp so that i can control allready
@OttoWinter are there something in the works so we dont have to add custom code etc?
would love to see a template fan integration as well. Just used a 4ch Sonoff hooked up to a floor fan to complete something similar to the example with a custom output but it was a little hairy.
The problem with the custom output workaround is that there's no Lambda available to determine the current state of the fan if changed from elsewhere.
Say look at the modbus integration: speed of the fan can be controlled by a register value, which can be set with an output. But how do we read the register value first, when the node boots up? The fan is already running at speed 2...
I just wanted to show my config because I have a similar fan to @Apogee19832312 where there is 3 different wires for 3 different speeds. But thanks to the Template Output component I managed to do this config without a custom component:
output:
- platform: gpio
id: fan_1
pin: GPIO12
- platform: gpio
id: fan_2
pin: GPIO5
- platform: gpio
id: fan_3
pin: GPIO4
- platform: template
id: fan_template_output
type: float
write_action:
- then:
- output.turn_off: fan_1
- output.turn_off: fan_2
- output.turn_off: fan_3
- lambda: |-
if (state > 0.99) {
id(fan_3).turn_on();
} else if (state > 0.66) {
id(fan_2).turn_on();
} else if (state > 0) {
id(fan_1).turn_on();
}
fan:
- platform: speed
output: fan_template_output
name: "Extraction Fan"
speed_count: 3
I'm researching whether this library can fulfill my use case.
I have a Vornado fan that I am going to solder some GPIO pins to the buttons on the PCB. There's a power button and 4 speeds (will ignore one speed for Speed Fan support).
As this is not directly a float compatible setup, it doesn't seem currently possible to map ranges of a Float Output to multiple Binary Outputs. Is this true? If so, I'd consider extending the Float Output to accept a dictionary of outputs with ranges.
Example:
0.0 < speed <= 0.33
: low output pin0.33 < speed <= 0.66
: med output pin0.66 < speed <= 1
: high output pinAlternatively, I suppose the Speed Fan component can be modified to associate outputs with speeds.