# Counting sump pump cycles

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:

1. Take the average of the sensor readings from between 15 seconds and 10 seconds in the past
2. Take the average of the sensor readings from between 5 seconds and 0 seconds in the past
3. 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.

Any tips appreciated.

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
• set input_number = current value

History Stats - Home Assistant tells you.

you can use the Derivative sensor to check whether the tank level is increasing or decreasing (positive vs negative derivative) and how fast.

I use something similar for my fish tank temperature to ensure the heater is working as expected.

``````sensor:
- platform: derivative
source: sensor.tank_sensor_temperature
name: Tank Temperature Derivative
round: 3
unit_time: h
time_window: "00:30:00"
unit: '°F/Hr'
``````

here is how it looks relative to the tank temp:

you could use that derivative sensor in an automation so that if the derivative is positive (>0) for a period of time then increase the pump counter.

you would just need to set the derivative config to smooth out the variations and I think it could work

Or there is a Trend sensor you might be able to use but I really don’t have any experience with that.

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.

OK, here’s a solution for dummies like me who aren’t good at statistical analysis:

Just plug the pump into a smart plug. Here’s one of my templates to catch cycles:

``````template:
- binary_sensor:
- name: "East Pump Cycle On"
unique_id: 'east_pump_on'
state: "{{ states('sensor.east_sump_pump_current')|float > 1 }}"
``````

And a sensor to count them over the past hour:

``````sensor:
- platform: history_stats
name: East Pump On 1H Count
entity_id: binary_sensor.east_pump_cycle_on
state: "on"
type: count
end: "{{ now() }}"
duration:
hours: 1
``````

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.

Yeah, that changes everything…

Sorry I can’t help with that.

1. Take the average of the sensor readings from between 15 seconds and 10 seconds in the past

sensor filter

1. Take the average of the sensor readings from between 5 seconds and 0 seconds in the past
2. Calculate the difference between the two. If the difference is > 0.05 meters, then increment a counter.

To do this on an esp you could try this approach

Then create a number component

Under the `analog_threshold` sensor use something along these lines

``````binary_sensor:
- platform: analog_threshold
# ...
on_state:
then:
- lambda: |-
auto call = id(my_number).make_call();
call.set_value( x + 1 );
call.perform();
``````

edit this will update on it turning on and off, so it’s not going to work as is

``````  if:
condition:
binary_sensor.is_on: my_binary_sensor
``````

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);
id: level_actual
... (rest of code for setting up and filtering the sensor)
``````
2 Likes

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));
``````
1 Like

I’m wanting to do this exact this.

1. Is this still working well for you? Any updates or upgrades?
2. Is this the code on the ESP32 or in Home Assistant?

I’m still running that code. It’s running on the ESP32 itself. Here’s the whole build if you’re interested:
https://community.home-assistant.io/t/monitor-for-sump-pump-with-battery-backup/

edit: this is the code I’m using, with the specific code highlighted:

1 Like