ESPHome synchronizing two sensor value updates

I’m using ESPHome with a device I put together to monitor water pressures before and after my whole house filter. I really want to use the two sensors to measure the pressure drop of the filter system but with the filters I’ve established the two sensor values come in very asynchronously and the pressure of the two changes much more rapidly than I originally assumed so the delta/difference value marches all over the place.

I’d like to set up my ESPHome configuration to push sensor values on both sensors at the same time if either sensor’s filter is triggered. It seems like this should be possible, maybe with the on_value and lambda publish_state() function but I’m having a hard time wrapping my brain around it…

The two sensor configurations currently looks like this:

sensor:
  - platform: ads1115
    multiplexer: 'A0_GND'
    gain: 6.144
    name: "Incoming Water Pressure"
    id: incoming_water_pressure
    update_interval: 5s
    unit_of_measurement: "psi"
    icon: "mdi:gauge"
    accuracy_decimals: 1
    filters:
      - throttle: 10s
      - or:
        - heartbeat: 600s
        - delta: 0.04
      - lambda: return x * 42.3 - 18;
  - platform: ads1115
    multiplexer: 'A1_GND'
    gain: 6.144
    name: "Outgoing Water Pressure"
    id: outgoing_water_pressure
    update_interval: 5s
    unit_of_measurement: "psi"
    icon: "mdi:gauge-low"
    accuracy_decimals: 1
    filters:
      - throttle: 10s
      - or:
        - heartbeat: 600s
        - delta: 0.04
      - lambda: return x * 47.3 - 22;

I think you could you make these two sensors internal so they don’t publish to HA, then create two template sensors that don’t return a value (or have an update_interval of never), then use an interval action to publish both template sensors at whatever rate you need. I haven’t tried, but something like this:

sensor:
  - platform: template
    id: copy_of_incoming_water_pressure
    name: Copy of Incoming Water Pressure
    unit_of_measurement: psi
    update_interval: never
    # No need to include a lambda here, do that from interval with publish_state

# Obviously add another template sensor for outgoing pressure

interval:
  - interval: 30s
    then:
      - sensor.template.publish:
          id: copy_of_incoming_water_pressure # Your template sensor which is a copy of the original
          state: id(incoming_water_pressure).state
      - sensor.template.publish:
          id: copy_of_outgoing_water_pressure  # Your template sensor which is a copy of the original
          state: id(incoming_water_pressure).state

Let me know how you go!

I just realised that may not be what you’re after. My example above should push both values to HA at the same time. I think maybe you want both pressures to be sampled and published to HA at the same time?

In that case, in one of your ads1115 sensors, you could try this. You may need to set the update_interval of the outgoing sensor to never

sensor:
  - platform: ads1115
    multiplexer: 'A0_GND'
    gain: 6.144
    name: "Incoming Water Pressure"
    id: incoming_water_pressure
    update_interval: 5s
    unit_of_measurement: "psi"
    icon: "mdi:gauge"
    accuracy_decimals: 1
    on_value:
      then:
        - component.update: outgoing_water_pressure
2 Likes

Thanks @patfelst, I think this will work! The idea is still not very clear in my head – I wanted either sensor to be able trigger the publish to HA but I also want both published always at the same time to HA. Which can probably cause an infinite loop if done incorrectly. So, maybe only triggering the publish from one sensor, as you suggested, is a better way to do this…

In your example, do you know if the outgoing_water_pressure sensor filters are applied after the component.update is called? Since I’m using throttle/delta/heartbeat, I wonder if I’ll need to disable those for this.

no worries. Each sensor is basically just a function/method that either gets called by it’s update_interval, or in this case, called by the component.update. The filter should then be applied after the update.

To be honest, as an alternative to trying to catch the pressure samples at the same time, I’d be experimenting with a moving average filter and upping your update_interval rate. Perhaps have an update interval of say 100ms, and apply a moving average filter with a window of 10. This will output the average of 10 samples in 1 second. Or even 50 samples over a 5 second period. Be careful to avoid overwhelming home assistant with 100ms updates, and ensure your window size is large enough, or keep your 10 second throttle filter in place too.

Yeah! It’s working very well. I don’t know how I want to handle it yet but right now the water company is “flushing” their system and I think I’m seeing it in my my new setup. The big dip right around 12:20am was me turning on the tub, faucet, and flushing the toilet for about 30 seconds to see the drop across the filter. I think it’s working VERY well.

I probably don’t need as much resolution as I’m already gathering but it sure is cool to see how much fluctuation there is in the supply.

Thanks again, you really know your stuff!

1 Like

wow that’s great Greg! Nah I’m still learning ESPHome too.

I’d be interested in seeing some of the hardware you’re using, like the pressure sensors and how you install them around the filter - thanks. In fact in Australia, we don’t have a whole house filters, just a water meter.

Sure thing Pat! Because of your request, I created this post to detail the project. Feel free to ask me any questions you have.

1 Like

Okay, I’ve migrated the delta_water_pressure from an HA template sensor to an ESPHome template sensor and it works very well:

  - platform: template
    name: "Delta Water Pressure"
    id: delta_water_pressure
    update_interval: never
    unit_of_measurement: "psi"
    icon: "mdi:kettle-alert"
    accuracy_decimals: 1
    lambda: |-
      return (id(incoming_water_pressure).state - id(outgoing_water_pressure).state);

and I added the delta_water_pressure component to be updated on_value change (of outgoing pressure):

    on_value:
      then:
        - component.update: incoming_water_pressure
        - component.update: delta_water_pressure

However, I really want to try to figure out how to make it publish to HA on a cadence like this:

  • During water demand within the house record every 5 or 10 seconds (while water is running)
  • Otherwise record ever 5 or 10 minutes

I think the water demand can only be determined by a fairly positive drop across the two sensors (when incoming is greater than outgoing by more than 1 psi).

I’m thinking I might use Pat’s first suggestion and make the raw values internal sensors (updating very often 250ms or something) then push those values to template sensors that might or might not be published to HA based on the delta value. Then I can put more filtering and decision making on the template sensor set but I’m having a hard time framing it up…

EDIT

I haven’t uploaded this to my esp8266 yet, mainly because I don’t quite know the order of operations around the lambda inside on_raw_value and the filters:. Also, I’m doubting that I can use the lambda to publish_state() the delta value from the delta on_raw_value.

In the following block, the raw outgoing value from the sensor triggers the update of incoming and delta. The delta then uses on_raw_value to update itself if the delta value is above 1.0 or throttled to 10s when changed by more than 0.5psi or heartbeat of 300s. The delta also uses on_value to update the outgoing HA-visible incoming and outgoing sensors.

Will this work? Please critique (I’m afraid…):

sensor:
  - platform: wifi_signal
    name: ${friendly_name} WiFi Signal Strength
    update_interval: 600s
  - platform: ads1115
    multiplexer: 'A0_GND'
    gain: 6.144
    id: incoming_water_pressure_raw
    update_interval: never
    accuracy_decimals: 1
    internal: True
    filters:
      - calibrate_polynomial:
          degree: 4
          datapoints:
            - 0.455 -> 0.0  
            - 0.722 -> 10.0
            - 0.824 -> 15.0
            - 0.948 -> 20.0
            - 1.061 -> 25.0
            - 1.178 -> 30.0
            - 1.280 -> 35.0
            - 1.398 -> 40.0
            - 1.501 -> 45.0
            - 1.622 -> 50.0
            - 1.738 -> 55.0
            - 1.859 -> 60.0
            - 1.977 -> 65.0
            - 2.097 -> 70.0
            - 2.209 -> 75.0
  - platform: ads1115
    multiplexer: 'A1_GND'
    gain: 6.144
    id: outgoing_water_pressure_raw
    update_interval: 1s
    accuracy_decimals: 1
    internal: True
    on_value:
      then:
        - component.update: incoming_water_pressure_raw
        - component.update: delta_water_pressure
    filters:
      - calibrate_polynomial:
          degree: 4
          datapoints:
            - 0.539 -> 0.0
            - 0.775 -> 10.0
            - 0.854 -> 15.0
            - 0.963 -> 20.0
            - 1.051 -> 25.0
            - 1.148 -> 30.0
            - 1.232 -> 35.0
            - 1.348 -> 40.0
            - 1.432 -> 45.0
            - 1.530 -> 50.0
            - 1.637 -> 55.0
            - 1.743 -> 60.0
            - 1.854 -> 65.0
            - 1.971 -> 70.0
            - 2.078 -> 75.0
  - platform: template
    name: "Delta Water Pressure"
    id: delta_water_pressure
    update_interval: never
    unit_of_measurement: "psi"
    icon: "mdi:kettle-alert"
    accuracy_decimals: 1
    lambda: |-
      return (id(incoming_water_pressure_raw).state - id(outgoing_water_pressure_raw).state);
    on_raw_value:
      if:
        condition:
          sensor.in_range:
            id: delta_water_pressure
            above: 1.0
        then:
          - lambda: |-
              id(delta_water_pressure).publish_state();
    on_value:
      then:
        - component.update: incoming_water_pressure
        - component.update: outgoing_water_pressure
    filter:
      - throttle: 10s
      - or:
        - heartbeat: 300s
        - delta: 0.5
  - platform: template
    name: "Incoming Water Pressure"
    id: incoming_water_pressure
    update_interval: never
    unit_of_measurement: "psi"
    icon: "mdi:gauge"
    accuracy_decimals: 1
    lambda: |-
      return id(incoming_water_pressure_raw).state;
  - platform: template
    name: "Outgoing Water Pressure"
    id: outgoing_water_pressure
    update_interval: never
    unit_of_measurement: "psi"
    icon: "mdi:gauge-low"
    accuracy_decimals: 1
    lambda: |-
      return id(outgoing_water_pressure_raw).state;
  - platform: ads1115
    multiplexer: 'A3_GND'
    gain: 6.144
    name: "${friendly_name} Illuminance"
    update_interval: 5s
    unit_of_measurement: lx
    accuracy_decimals: 0
    filters:
      - or:
        - heartbeat: 600s
        - delta: 0.05
      - lambda: |-
          return (x / 10000.0) * 2000000.0;
  - platform: hx711
    dout_pin: D0
    clk_pin: D1
    gain: 128
    name: "Salt Bin Weight"
    update_interval: 10s
    unit_of_measurement: lbs 
    icon: "mdi:shaker"
    accuracy_decimals: 1
    filters:
      - median:
          window_size: 61
          send_every: 60
          send_first_at: 6
      - lambda: return x * -0.0000911 + 23.3;

Thanks for the new post about your hardware setup, I’ll have a detailed read soon!

This is getting pretty complicated now, and I’m back at work so don’t have the time to study it. I think it could be simplified if you dynamically altered the update interval of your delta template, along the lines of what you already have with on_value, then if delta pressure > 1.0 psi, increase the update rate. Here’s the undocumented bit of code that might help with that - I’ve not tried it personally, but I don’t see why it shouldn’t work! Obviously you’ll have to re-jig your code to remove some of the component.updates you’ve got.

# Dynamically set the update interval of a sensor to 10s
- lambda: 'id(delta_water_pressure).set_update_interval(10000);' 

Let me know if this helps

Ooh, that’s very enticing but I started looking around and I found this ticket on github about using the set_update_interval option. It sounds like it doesn’t work the way we’re hoping because of how ESPHome initializes and runs. The guy who submitted this ticket was trying to use it to change the update_interval but he experienced that it didn’t do that once it was called. :slightly_frowning_face:

wow good find Greg. A pity though, and apologies for leading you astray :frowning_face:

I’m intrigued by your problem though, I’ll have a think over the next couple of days, there will be a solution!

Hi. I need a similar thing for a different use case. I’m trying to balance circuits of my floor heating. I taped a temperature sensor to each pipe (both incoming and outgoing), and I want to measure deltaT of each circuit.

The problem is that I have 12 sensors. Right after integrating them in HA I see there is up to 0.5C difference between them. Initially I wanted to just calibrate them with a fixed offset, but then I noticed they update at totally different times. So the offset probably won’t help until I can get them all to update at the same time.

Did you find a way to make multiple sensors update whenever any of them changes? I can’t really use this approach where only one sensor is updating on an interval, and triggering all the other updates. Different circuits are connected to different thermostats. The temperature changes much more rapidly when water is running through the circuit than when the valve is turned off. If the circuit of the “main” sensor it turned off, it may sit idle on the same temperature for a while, and miss all the changes in other circuits.

Does anyone have a suggestion how to do this?

Did you read the code I posted? I solved it by making all of the sensors update ‘never’, then using a separate trigger to get all the values so I could do the math and report it, simultaneously.