I’m working on creating a sump pump sensor, and one of the features I’d like is to count the number of times the pump runs. Below is a graph of the water level in my sump, and the pump events are obvious every ~10 minutes. I’m working on eliminating the measurement noise, but that’s a separate problem.
While the events are very obvious to a human, I’m having trouble coming up with a robust way to detect them. This would admittedly be easier if I fixed the noise issue first, but I believe there should be a way to accomplish this in a robust manner with data as it exists currently.
Ideally what I’d like to do is:
Take the average of the sensor readings from between 15 seconds and 10 seconds in the past
Take the average of the sensor readings from between 5 seconds and 0 seconds in the past
Calculate the difference between the two. If the difference is > 0.05 meters, then increment a counter.
But I can’t come up with a way to do item #1; there doesn’t seem to be a way to get a buffer of stale data without also including the fresh data.
For now I’m using two max filters, one with a 20sec window and one with a 10 sec window, and calculating the difference between them every 10 seconds. But I’m finding the noise is causing this to not be robust at all.
Maybe a “hacky” solution, but I could think of an average sensor for the last 15 seconds for example, an input_number as helper entity and an automation that runs every minute.
The input_number stores the value of the average sensor from a minute ago.
The automation does:
Check if the current value of the average sensor is 0.05 lower than that of the input_number, then increase the counter
I should have mentioned I was hoping to contain all the code within the ESP32 device I’m using, so that the data is captured and calculated even if there is a HA shutdown or WiFi issue. The derivative would be great if there was a similar function within ESPHome.
As I was typing this you posted that you want it in an ESP32. So this probably won’t work for you. I’ll post anyway, in case someone else like me stumbles on this thread.
I wasn’t aware of the analog_threshold platform; I think this is exactly what I needed. I’m using global variables elsewhere in my code so I’ll use those to store the ‘stale’ readings.
I’ll come back and post my solution when I have it working but I’m confident I can get there now. Thanks!
Turns out that the comparion of past (“stale”) data to current data wasn’t necessary; the analog_threshold platform was all I really needed. Here’s the snippets I ended up with:
substitutions:
friendly_name: Sump Pump Sensor
pumped_out_below_height: '0.100' #when the level falls below this value, turn on the pump counter trigger
pumped_out_reset_height: '0.140' #when the level rises above this value, reset (turn off) the pump counter trigger
globals:
- id: sump_pump_counter_int
type: int
restore_value: true
initial_value: '0'
binary_sensor:
- platform: analog_threshold
name: "${friendly_name} Pumpout Event"
id: pumpout_event
sensor_id: level_actual
threshold:
upper: $pumped_out_below_height
lower: $pumped_out_reset_height
filters:
- invert:
- delayed_on: 10s #debounce after falling below_height
- delayed_off: 10s #debounce after rising above reset_height
on_press:
lambda: |-
// Add one to the global integer
id(sump_pump_counter_int) += 1;
// Force the sensor to publish a new state
id(sump_pump_counter).publish_state(id(sump_pump_counter_int));
sensor:
- platform: template
name: "${friendly_name} Pump Counter"
id: sump_pump_counter
update_interval: never
accuracy_decimals: 0
state_class: total_increasing
lambda: return id(sump_pump_counter_int);
- platform: ads1115
id: level_actual
... (rest of code for setting up and filtering the sensor)
Turns out that code didn’t work as I expected. When there was noise around the 0.140 level, it would register multiple triggers. I realized I could get what I wanted easier by using a template sensor instead. Below is my new sensor code if anyone is following along, although I haven’t tested to confirm if the debounces work as expected. But I can confirm the hysteresis (and everything else) does.
# Hysteresis sensor to trip when sump pump is emptied. When tripped, increment the counter and calculate time between pumpouts
- platform: template
name: "${friendly_name} Pumpout Event"
id: pumpout_event
lambda: |-
if (id(level_actual).state < $pumped_out_below_height) {
return true;
} else if (id(level_actual).state > $pumped_out_reset_height) {
return false;
} else {
return {};
}
filters:
- delayed_on: 2s #debounce after falling below_height
- delayed_off: 5s #debounce after rising above reset_height
on_press:
# when state transisions from false to true, a pumpout event occurred.
lambda: |-
// Add one to the global integer
id(sump_pump_counter_int) += 1;
// Force the sensor to publish a new state
id(sump_pump_counter).publish_state(id(sump_pump_counter_int));