Multiple sensors based on single reading?

I’m brand-new to ESPHome, but I have a working config for an esp32 board and HC-SR04 ultrasonic distance sensor that is measuring the level of salt pellets in my water softener. There are four different sensor entities exposed:

  • The “raw” distance (the direct reading from the sensor position to the top of the salt)
  • The “actual” distance from the max-fill line height to the top of the salt (because the sensor is mounted about 16cm above that height)
  • The height of the remaining salt
  • The percentage of the remaining salt compared to the max-fill line

This all seems to be working fine now, I’m getting readings that look accurate:

Screen Shot 2022-10-22 at 11.32.16 AM

But it’s based on four separate sensors each taking their own readings at the same time:

esphome:
  name: water-softener

esp32:
  board: esp32dev
  framework:
    type: arduino

globals:
  # distance between bottom of tank and sensor position is 0.91m
  - id: sensor_height
    type: float
    initial_value: '0.91'

  # distance between salt max fill line and sensor position is 0.16m
  - id: distance_offset
    type: float
    initial_value: '0.16'

  # distance between bottom of tank and max fill line is 0.75m
  - id: max_fill_height
    type: float
    initial_value: '0.75'

sensor:
  - platform: ultrasonic
    trigger_pin: GPIO17
    echo_pin: GPIO18
    name: "Salt Level Distance"
    update_interval: 10s
    unit_of_measurement: "m"
  
  - platform: ultrasonic
    trigger_pin: GPIO17
    echo_pin: GPIO18
    name: "Salt Level Distance From Max Fill Line"
    update_interval: 10s
    filters:
    # Calculates distance, in centimeters, of the difference between the max fill line height and the current level
    - lambda: return (x - id(distance_offset)) * 100;
    unit_of_measurement: "cm"

  - platform: ultrasonic
    trigger_pin: GPIO17
    echo_pin: GPIO18
    name: "Remaining Salt Level"
    update_interval: 10s
    filters:
    # Calculates height, in centimeters, of salt pellets remaining in the tank
    - lambda: return (id(sensor_height) - x) * 100;
    unit_of_measurement: "cm"

  - platform: ultrasonic
    trigger_pin: GPIO17
    echo_pin: GPIO18
    name: "% Remaining Salt Level"
    update_interval: 10s
    filters:
    # Calculates % of salt pellets remaining in the tank
    - lambda: return (id(sensor_height) - x) * (100/id(max_fill_height));
    unit_of_measurement: "%"

Is there a way to make this more concise and take a single reading, then split out the entity values?

I would keep the sensor reading the raw value talking to the ultrasonic sensor, and convert the other ones into template sensors.

1 Like

You can also use “copy” sensors and add your filters to them.

2 Likes

Interesting, thanks both! I figured there had to be a way (or more than one) to do what I was looking for. Either solution seem applicable, I’ll play around with both concepts and see which one seems more straight-forward. I think I’m leaning towards the Copy Sensor, as it seems a little more explicit as to what it is doing and tailor-made for the scenario I’m in. With the Template Sensor, it looks like I would have to set up another global variable to house the raw reading from the ultrasonic (maybe I’m misinterpreting that), whereas the Copy Sensor does all that “heavy lifting” for you.

Copy Sensor works like a charm, and with little modification:

globals:
  # distance between bottom of tank and sensor position is 0.91m
  - id: sensor_height
    type: float
    initial_value: '0.91'

  # distance between salt max fill line and sensor position is 0.16m
  - id: distance_offset
    type: float
    initial_value: '0.16'

  # distance between bottom of tank and max fill line is 0.75m
  - id: max_fill_height
    type: float
    initial_value: '0.75'

sensor:
  - platform: ultrasonic
    id: "salt_level_distance"
    trigger_pin: GPIO17
    echo_pin: GPIO18
    name: "Salt Level Distance"
    update_interval: 10s
    unit_of_measurement: "m"
  
  - platform: copy
    source_id: "salt_level_distance"
    name: "Salt Level Distance From Max Fill Line"
    filters:
    # Calculates distance, in centimeters, of the difference between the max fill lineheight of the salt and the current level
    - lambda: return (x - id(distance_offset)) * 100;
    unit_of_measurement: "cm"

  - platform: copy
    source_id: "salt_level_distance"
    name: "Remaining Salt Level"
    filters:
    # Calculates height, in centimeters, of salt pellets remaining in the tank
    - lambda: return (id(sensor_height) - x) * 100;
    unit_of_measurement: "cm"

  - platform: copy
    source_id: "salt_level_distance"
    name: "% Remaining Salt Level"
    filters:
    # Calculates % of salt pellets remaining in the tank
    - lambda: return (id(sensor_height) - x) * (100/id(max_fill_height));
    unit_of_measurement: "%"

Yeah I find them pretty clean to work with. They are a relatively new ESPHome feature.

Sometimes I chain a few and have “intermediate” sensors to use during development/debugging.

Then you can either toggle them to being “internal” or consolidate them once all your testing is done.