ESPHome filtering/lambda help

I need help with a config I can’t make work as intended. I want to filter invalid ultrasonic sensor readings and then use a sliding time window of those filtered values to set the state of a binary sensor.

esphome:
  name: desk_keyboard_tray
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: "SSID"
  password: "PSK"
  
sensor:
  - platform: ultrasonic
    # Setting ID only will make the sensor internal
    id: "ultrasonic_sensor"
    trigger_pin: D1
    echo_pin: D2
    accuracy_decimals: 5
    update_interval: 1s
    filters:
      - lambda: if (x > 0.2000 && x < 0.6500) return x; else return {};
      - sliding_window_moving_average:
          window_size: 5
          send_every: 5

  - platform: uptime
    name: "Desk Keyboard Tray Uptime"
    
text_sensor:
  - platform: wifi_info
    ip_address:
      name: "Desk Keyboard Tray IP Address"

binary_sensor:
  - platform: template
    name: "Desk Keyboard Tray"
    device_class: opening
    lambda: if (id(ultrasonic_sensor).state > 0.34) return true; else return false;

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

So I only want values > 0.2000 and < 0.6500 to be used in the sliding window. This doesn’t seem to be working like I want. Help?

1 Like

Did you ever get this working? Is the sliding_window_moving_average called with the raw values from the sensor before the lambda function is called? If so, is there another solution?

In my case I’m trying to use the vl53L0X time-of-flight sensor at the NaN values (out of range) is messing up the average as the numbers are thrown out for the average. I’d like to make the NaN values to be 999.

1 Like

I’m not exactly sure what I was trying to solve since it’s been so long, but here is my current config:

substitutions:
  name: andrew_desk_keyboard_tray
  friendly_name: "Andrew Desk Keyboard Tray"

esphome:
  name: ${name}
  platform: ESP8266
  board: d1_mini

<<: !include common.yaml

sensor:
  # Read the sensor and pass to template to validate
  - platform: ultrasonic
    id: "ultrasonic_read"
    trigger_pin: D1
    echo_pin: D2
    accuracy_decimals: 5
    update_interval: 1s

  # Validate reading and then pass to binary
  - platform: template
    id: "ultrasonic_smoothed"
    lambda: |-
      if (id(ultrasonic_read).state > 0.2000 && id(ultrasonic_read).state < 0.6500) {
        return id(ultrasonic_read).state; 
      } else {
        return {};
      }
    update_interval: 1s
    filters:
      - sliding_window_moving_average:
          window_size: 5
          send_every: 2

binary_sensor:
  - platform: template
    name: "${friendly_name}"
    device_class: opening
    lambda: if (id(ultrasonic_smoothed).state > 0.31) return true; else return false;
    
switch:
  - platform: restart
    id: restart_device
    name: "${friendly_name} Restart"
2 Likes

Looks like filters are processed in the order they are defined. That is great news. So my solution was to perform a lambda filter to make NaN into 999. Then the sliding_window_moving_average filter would use the value returned from the lambda filter to perform the average. The solution was easier than I thought. You have to love ESPHome.

  - platform: vl53l0x
    name: "distance1"
    id: distance1
    i2c_id: bus_a
    address: 0x29
    update_interval: 200ms
    unit_of_measurement: "m"
    filters:
    - lambda: if (isnan(x)) {  return 999.0; } return x;     
    - sliding_window_moving_average:
          window_size: 5
          send_every: 5
2 Likes

Only loosely related to the OP but just for those who come here by search because their average or median filters act strangely:

When your sensor returns “unknown” values (ESPHome internally: nan = not a number), the average filters consider this to be a zero value instead of discarding it. At least the median filter does that but I am quite sure the other average filters do the same.

To prevent your filter output from falsely reporting zero you can prepend the averaging / median filter by a filter_out filter like this:

filters:
  - filter_out: nan   # <-- Processing will stop here on unknown values.
  - median:
      window_size: 5
4 Likes

I have done this right now. in testing.

globals:
   - id: last_value
     type: float
     restore_value: no
     initial_value: NAN
----
....
----

filters:
      - filter_out: nan
      - lambda: |-
          if (isnan(id(last_value))) {
            id(last_value) = x;
          }
          if ((abs(x) - id(last_value)) < 0.15) {
            id(last_value) = x;
          }
          return id(last_value);
      - filter_out: nan