'Simple' task with fan speed is driving me nuts!

I’m working on a project that on the face of it seems pretty simple:

ESP32 (ESP32_DevkitC_V4)
BME280 (using I2C)
Display (1602A with I2C)
PWM (HW-517)
12v computer fan

I want to take the humidity reading of the BME280 and depending on the reading, make the fan speed up or slow down. I think I’m close but close isn’t on target. I’ve given up on having the fan use a sliding scale (like one would use for dimming an LED bulb) because it was taking way too much time and going nowhere and for this project, it is unnecessary. I did want to learn that though so I could reuse the code to make an artificial sunrise and sunset for my chicken coop over winter.

Back to the fan. I cannot seem to generate YAML code that ESPhome will take and upload. I’ve searched online but there seem to be holes in the documentation regarding use of the speed setting in the Speed Fan. Here is what I have:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: True

sensor:
  - platform: bme280
    temperature:
      name: "Weedbox Temperature"
      id: weed_temperature
      oversampling: 16x
    pressure:
      name: "Weedbox Pressure"
      id: weed_pressure
    humidity:
      name: "Weedbox Humidity"
      id: weed_humidity
      filters:
        offset: -0
      on_value_range:
      - below: 30
        then:
          - fan.turn_off: weed_fan
      - above: 30.1
        below: 30.5
        then:
          - fan.turn_on: weed_fan
            speed: low
      - above: 30.6
        below: 31
        then:
          - fan.turn_on: weed_fan
            speed: medium
      - above: 31.1
        then:
          - fan.turn_on: weed_fan
            speed: high
    address: 0x76
    update_interval: 60s

  - platform: wifi_signal
    name: "Weedbox Wifi Signal"
    id: weedbox_wifi_signal
    update_interval: 60s
    force_update: false
    unit_of_measurement: dB
    icon: mdi:wifi
    accuracy_decimals: 0

  - platform: uptime
    name: "Weedbox Uptime"
    id: weed_uptime
    filters:
      lambda: return x / 3600;

output:
  - platform: ledc
    pin: GPIO19
    id: weed_pwm

fan:
  - platform: speed
    output: weed_pwm
    name: "Weed Fan"
    id: weed_fan
    speed:
      low: 0.33
      medium: 0.66
      high: 1

binary_sensor:
  - platform: status
    name: "Weedbox Status"

text_sensor:
  - platform: wifi_info
    mac_address:
      name: "Weedbox Mac Address"
      icon: mdi:network

switch:
  - platform: restart
    name: "Weedbox Restart"

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      it.printf(0, 0, "T:%.1f", id(weed_temperature).state);
      it.printf(8, 0, "H:%.1f", id(weed_humidity).state);
      it.printf(0, 1, "U:%.1f", id(weed_uptime).state);
      it.printf(8, 1, "F:%.0f", id(weed_fan).state);

The errors seem to be here:

        then:
          - fan.turn_on: weed_fan
            speed: low

with ESPhome complaining that I can’t have ‘fan.turn_on’ and ‘speed:’ in the same block but I can’t figure out how to set the speed of the fan any other way.

I should add that for this project, I want to stay away from using Home Assistant as part of the solution as I have some other ESP projects that I want to use the code on where they will not be part of a HA system. The automation needs to run on the ESP32, not on HA.

As a bonus question, I’d like to have the display show the fan state (off, low, medium, high) in the last line of code. At present, it doesn’t do that but I have to solve the upstream problem first.

This page lists the speeds CAPITALISED

LOW , MEDIUM , HIGH

It does, but neglects to illustrate the syntax used for it. I’ve looked at the material I can find on both ESPhome and also via general Googling for examples but it doesn’t seem to be a well-used function. Also, the approach for the ESP8266 is different than for ESP32 due to different libraries in play.

If I can figure it out, it seems to be a prime candidate for a ‘how-to’ post which I’m willing to write. There seem to be functions for speed control at the output(PWM) level and also using the ‘Speed Fan’ library which doesn’t make clear if the setting used are in the output code or in the fan code. Generally, it is pretty confusing and my experimentation hasn’t been fruitful.

The following validates without error

          - fan.turn_on:
              speed: LOW
              id: weed_fan

Complete listing

esphome:
  name: test_fan
  platform: ESP32
  board: esp-wrover-kit

wifi:
  ssid: "TESTSSID"
  password: "strongpassword"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Test Fan Fallback Hotspot"
    password: "92Ll0OCu5Zpn"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: "strongotapassword"

ota:
  password: "strongotapassword"

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: True

sensor:
  - platform: bme280
    temperature:
      name: "Weedbox Temperature"
      id: weed_temperature
      oversampling: 16x
    pressure:
      name: "Weedbox Pressure"
      id: weed_pressure
    humidity:
      name: "Weedbox Humidity"
      id: weed_humidity
      filters:
        offset: -0
      on_value_range:
      - below: 30
        then:
          - fan.turn_off: weed_fan
      - above: 30.1
        below: 30.5
        then:
          - fan.turn_on:
              speed: LOW
              id: weed_fan
      - above: 30.6
        below: 31
        then:
          - fan.turn_on: 
              speed: MEDIUM
              id: weed_fan
      - above: 31.1
        then:
          - fan.turn_on:
              speed: HIGH
              id: weed_fan
    address: 0x76
    update_interval: 60s

  - platform: wifi_signal
    name: "Weedbox Wifi Signal"
    id: weedbox_wifi_signal
    update_interval: 60s
    force_update: false
    unit_of_measurement: dB
    icon: mdi:wifi
    accuracy_decimals: 0

  - platform: uptime
    name: "Weedbox Uptime"
    id: weed_uptime
    filters:
      lambda: return x / 3600;

output:
  - platform: ledc
    pin: GPIO19
    id: weed_pwm

fan:
  - platform: speed
    output: weed_pwm
    name: "Weed Fan"
    id: weed_fan
    speed:
      low: 0.33
      medium: 0.66
      high: 1

binary_sensor:
  - platform: status
    name: "Weedbox Status"

text_sensor:
  - platform: wifi_info
    mac_address:
      name: "Weedbox Mac Address"
      icon: mdi:network

switch:
  - platform: restart
    name: "Weedbox Restart"

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      it.printf(0, 0, "T:%.1f", id(weed_temperature).state);
      it.printf(8, 0, "H:%.1f", id(weed_humidity).state);
      it.printf(0, 1, "U:%.1f", id(weed_uptime).state);
      it.printf(8, 1, "F:%.0f", id(weed_fan).state);
1 Like

Well, that works like a hot damn! Thank you!

There was obviously something I was missing and it may have been the capitalisation but in between trying - output.set_level and - fan.set_level and - fan.turn_on I was just missing the correct sequence which, in hindsight, now seems so easy. After a couple of solid days on it, it may have just been that I was too close to the problem to see the solution.

I have a couple of enhancements and fixes that I want to do with this code after which I’ll produce a write-up that should help others with this problem and also provide a useful project to run it in. As you can see from the names in the code, this project is a heaterless dehumidifier for weed.

So, fancy having a crack at the last part of the code? :laughing:

Current is:

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      it.printf(0, 0, "T:%.1f", id(weed_temperature).state);
      it.printf(8, 0, "H:%.1f", id(weed_humidity).state);
      it.printf(0, 1, "U:%.1f", id(weed_uptime).state);
      it.printf(8, 1, "F:%x", id(weed_fan).speed);

What I get on the display is one of: 0 (for off), 0 (for low), 1 (for medium), 2 (for high).

I would like to get off, low, medium, high.

I get the feeling that the speed_fan code is using a char for the speed setting rather than an integer. If I use “F:%X” then I get a number returned, if I use “F:%s” then validates OK but it bitches at me during compile (below) and then won’t connect to wifi or display anything at all;

“src/main.cpp: In lambda function:
src/main.cpp:407:46: warning: format ‘%s’ expects argument of type ‘char*’, but argument 5 has type ‘int’ [-Wformat=]
it.printf(8, 1, “F:%s”, weed_fan->speed);”

Any ideas?

I should add that I’ve considered the use of if/else statements to derive the text state of the fan but with both ‘off’ and ‘low’ both returning zero, I seem to be at a hurdle that my skills are unable to get me over.

So, further investigation reveals this:
weed_fan.state returns either ON or OFF
Weed_fan.speed returns 0,0,1,2 for off, low, medium, high

I tried this:

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      it.printf(0, 0, "T:%.1f", id(weed_temperature).state);
      it.printf(8, 0, "H:%.1f", id(weed_humidity).state);
      it.printf(0, 1, "U:%.1f", id(weed_uptime).state);
#      it.printf(8, 1, "F:%u", id(weed_fan).state);
      if (id(weed_fan).state = "off") {
        it.print(8, 1, "F: OFF");
      } else
        if (id(weed_fan).speed = '0') {
          it.print(8, 1, "F: LOW");
        } else
          if (id(weed_fan).speed = '1') {
            it.print(8, 1, "F: MED");
          } else
            it.print(8, 1, "F: HIGH");

and got this:

src/main.cpp: In lambda function:
src/main.cpp:421:34: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
       if (weed_fan->state = "off") {
                                  ^
src/main.cpp:424:29: error: invalid conversion from 'char' to 'esphome::fan::FanSpeed' [-fpermissive]
         if (weed_fan->speed = '0') {
                             ^
src/main.cpp:424:34: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
         if (weed_fan->speed = '0') {
                                  ^
src/main.cpp:427:31: error: invalid conversion from 'char' to 'esphome::fan::FanSpeed' [-fpermissive]
           if (weed_fan->speed = '1') {
                               ^
src/main.cpp:427:36: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
           if (weed_fan->speed = '1') {
                                    ^
*** [/data/esp32_weeddryer/.pioenvs/esp32_weeddryer/src/main.cpp.o] Error 1

I’ve discovered more things that don’t work but regrettably, nothing that does. I tried the parentheses around (off) but then I get an error that it isn’t declared in the scope. I’m not a coder at all (you guessed that already) and I feel that these errors are running up against areas that I’m aware of but do not have the knowledge yet to solve. Interestingly, I have another project that is stalling because I can’t figure out how to use a value that is stored as a char in a string.

I was able to get an ON/OFF display using the following:

if (id(weed_fan).state) {
  it.print(8, 1, "F:ON");
} else {
  it.print(8, 1, "F:OFF"); 

That almost worked - I think - but with the TRUE state being primary, it left me nowhere that was obvious to put the rest of the code to sort the fan speed. If the first action was based on fan state being OFF then the rest of the code would flow from that. Still, it worked and I’m taking it as a win at this point. I was thrown off for a while because that line of code that I commented out with a hash caused an issue and threw a scalar error.

So, I tried this:

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      it.printf(0, 0, "T:%.1f", id(weed_temperature).state);
      it.printf(8, 0, "H:%.1f", id(weed_humidity).state);
      it.printf(0, 1, "U:%.1f", id(weed_uptime).state);
      if (id(weed_fan).state)
        then
        if (id(weed_fan).speed = '0') {
          it.print(8, 1, "F:LOW");
        } else
        if (id(weed_fan).speed = '1') {
          it.print(8, 1, "F:MED");
        } else
        if (id(weed_fan).speed = '2') {
          it.print(8, 1, "F:HIGH");
      } else {
        it.print(8, 1, "F:OFF");
      }
        

but once again, the errors are back. I’ll give it another shot tomorrow.

and as per this post

try this - it validates ok

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      if (id(weed_fan).state = OFF) {it.print(8, 1, "F: OFF");}
      else if (id(weed_fan).speed = '0') {it.print(8, 1, "F: LOW");}
      else if (id(weed_fan).speed = '1') {it.print(8, 1, "F: MED");}
      else {it.print(8, 1, "F: HIGH");}
      it.printf(0, 0, "T:%.1f", id(weed_temperature).state);
      it.printf(8, 0, "H:%.1f", id(weed_humidity).state);
      it.printf(0, 1, "U:%.1f", id(weed_uptime).state);

Me neither, but YAML is pedantic, you had the correct state in your post you just changed it in your code :man_facepalming: :crazy_face:

I changed that but it was still complaining, so I took your code and deleted out all the nested ifs (because its too easy to run into trouble with them) and ran with just one if/else.

It still complained so I got rid of your previous statements in the lambda and just ran with the one if so that I could get the validator to tell me where the error was, but that worked ok it didn’t complain about anything so I added the non if statements back in and that worked ok.

Then I googled “esphome nested if” and found that thread and added the nested ifs back in - let me know if it works for you.

Yep I kept getting scalar errors too

Interesting that it compiles OK for you. I still get the errors below:

src/main.cpp:318:3: warning: multi-line comment [-Wcomment]
   //   lambda: !lambda "if (id(weed_fan).state = OFF) {it.print(8, 1, \"F: OFF\");}\nelse\
   ^
src/main.cpp: In lambda function:
src/main.cpp:404:29: error: 'OFF' was not declared in this scope
       if (weed_fan->state = OFF) {it.print(8, 1, "F: OFF");}
                             ^
src/main.cpp:405:32: error: invalid conversion from 'char' to 'esphome::fan::FanSpeed' [-fpermissive]
       else if (weed_fan->speed = '0') {it.print(8, 1, "F: LOW");}
                                ^
src/main.cpp:405:37: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
       else if (weed_fan->speed = '0') {it.print(8, 1, "F: LOW");}
                                     ^
src/main.cpp:406:32: error: invalid conversion from 'char' to 'esphome::fan::FanSpeed' [-fpermissive]
       else if (weed_fan->speed = '1') {it.print(8, 1, "F: MED");}
                                ^
src/main.cpp:406:37: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
       else if (weed_fan->speed = '1') {it.print(8, 1, "F: MED");}
                                     ^
*** [/data/esp32_weeddryer/.pioenvs/esp32_weeddryer/src/main.cpp.o] Error 1

I’ve tried lower case off, “OFF”, “off”, (OFF) and (off) in addition to the combinations behind state = , state=

I also tried cutting out the last few lines and just working with the If part but to no success.

I never compiled it - just validated it, let me try

Yep craps out when compiling

RTFMing Lambdas aren’t validated :man_facepalming: :crazy_face:

I’m wondering if it isn’t an issue with the lambda syntax but instead an issue with the data type in the conditions. I don’t know how to tell what data type is OFF and what data type is 0,1,2.

This validates and compiles - it throws a warning about expecting a double when fan_speed is defined as int but give it a try.

esphome:
  name: test_fan
  platform: ESP32
  board: esp-wrover-kit

wifi:
  ssid: "TESTSSID"
  password: "strongpassword"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Test Fan Fallback Hotspot"
    password: "fwLUCneJGiwm"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: "strongotapassword"

ota:
  password: "strongotapassword"

globals:
  - id: fan_speed
    type: int

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: True

sensor:
  - platform: bme280
    temperature:
      name: "Weedbox Temperature"
      id: weed_temperature
      oversampling: 16x
    pressure:
      name: "Weedbox Pressure"
      id: weed_pressure
    humidity:
      name: "Weedbox Humidity"
      id: weed_humidity
      filters:
        offset: -0
      on_value_range:
      - below: 30
        then:
          - fan.turn_off:
              id: weed_fan
          - lambda: |-
              id(fan_speed) = 0;
      - above: 30.1
        below: 30.5
        then:
          - fan.turn_on:
              speed: LOW
              id: weed_fan
          - lambda: |-
              id(fan_speed) = 1;
      - above: 30.6
        below: 31
        then:
          - fan.turn_on: 
              speed: MEDIUM
              id: weed_fan
          - lambda: |-
              id(fan_speed) = 2;
      - above: 31.1
        then:
          - fan.turn_on:
              speed: HIGH
              id: weed_fan
          - lambda: |-
              id(fan_speed) = 3;
    address: 0x76
    update_interval: 60s

  - platform: wifi_signal
    name: "Weedbox Wifi Signal"
    id: weedbox_wifi_signal
    update_interval: 60s
    force_update: false
    unit_of_measurement: dB
    icon: mdi:wifi
    accuracy_decimals: 0

  - platform: uptime
    name: "Weedbox Uptime"
    id: weed_uptime
    filters:
      lambda: return x / 3600;

output:
  - platform: ledc
    pin: GPIO19
    id: weed_pwm

fan:
  - platform: speed
    output: weed_pwm
    name: "Weed Fan"
    id: weed_fan
    speed:
      low: 0.33
      medium: 0.66
      high: 1

binary_sensor:
  - platform: status
    name: "Weedbox Status"

text_sensor:
  - platform: wifi_info
    mac_address:
      name: "Weedbox Mac Address"
      icon: mdi:network

switch:
  - platform: restart
    name: "Weedbox Restart"

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      it.printf(0, 0, "T:%.1f", id(weed_temperature).state);
      it.printf(8, 0, "H:%.1f", id(weed_humidity).state);
      it.printf(0, 1, "U:%.1f", id(weed_uptime).state);
      it.printf(8, 1, "F:%f", id(fan_speed));

If it works for you change

globals:
  - id: fan_speed
    type: int

to

globals:
  - id: fan_speed
    type: double

I see what you did there… :slight_smile:

It does validate AND compile but the display reads out a decimal fraction - F:0.0000 and throws a bunch of warnings about LCD out of print range in the logs. That would be expected when it can’t fit the entire output on the display.

I considered a similar approach using a template but you implemented into the sensor which is a nice touch.

Net result is that it still isn’t producing the required output on the display. I’ve tried playing around with int, double and char as the global type and also the printf function but with no success. I recognise that the 0.0000 is coming from the ‘f’ on “F:%f” but ‘d’ and ‘u’ both result in an unchanging ‘F:0’ on the display and the one I really want - F:%s - results in the wifi not connecting and nothing at all on the display.

As I think we were calling integers from the code you added under sensor:, I tried encapsulating the fan speed numbers in single quotes ’ ’ but that didn’t work either.

and if you go back to

it.printf(8, 1, "F:%f", id(weed_fan).speed);

Global is set to int, no char quotes on fan speeds under sensor.

it.printf(8, 1, “F:%u”, id(weed_fan).speed); - note the change from f to u for unsigned decimal integer.

It validates AND compiles without error but I’m back to 0,0,1,2 on the display.

:man_shrugging: Yeah sorry Mark