ESPHome water level sensor

I integrated it into Home Assistant today and to my surprise the ESPHome code I had written weeks ago works :smiley:
So I figured I might as well share it here (not claiming there aren’t better ways of doing it).
Important note is that this code is for when you are using an ESP8266 in combination with an ADS1115 analog to digital converter (costs €1 or so and is much more accurate than the built-in ADC in the ESP8266). Slight adjustments would be necessary if you use an ESP32 or don’t use the ADS1115.
Here’s the diagram of how I connected everything (a bit messy as I changed it a lot based on the helpfull instructions of @Maco65.

And here’s the code:

# setup ic bus
i2c:
  sda: 4 #for the ESP8266, most often 21 for the ESP32
  scl: 5 #for the ESP8266, most often 22 for the ESP32
  scan: True
#  id: bus_a #I believe this is necessary for the ESP32, but I'm using the ESP8266

# define the ads1115 analog to digital convrerter on the bus
ads1115:
  - address: 0x48 #This is the address used by the ads1115 if you connect ADDR with GND on the board or if you don't connect it to anything. Other addresses can be chosen (if this one is occupied allready) by making a diferent connection.
#    i2c_id: bus_a #This is probably necessary on the ESP32, the way I'm doing it might be incorrect, but in any case it's not necessary for me as I'm using the ESP8266

#These sensors assume one circular tank with the same radius along it's height. I will connect a second tank and to solve that issue I will create additional sensors and sum these with the original sensors into new Total sensors. I need to look at the usable height in the second tank (minimum water level when the siphon connecting the two tanks will start working, assuming that the inlet ofthe pump in the main tank is placed lower than that). I will then define the measured voltage at that level as 0cm. I then need to know the voltage when the second tank is at it's maximum height (i.e. when water in the main tank will start flowing to the drainage citern). I can then do the lineair calibration. Now I have the centimeters. Summing these centimeters with the other one is meaningless. For the liters sensor I need to know the surface m² of the new tank and multiply it with the new height sensor. That result I can indeed sum with the liters of the other tank into a new total. To create the percentage sensor I need to sum the new total liters sensor by the sum of the max liters of both tanks.
sensor:
  - platform: ads1115
    multiplexer: 'A1_GND'
    gain: 4.096
    id: watertank_voltage
    name: "Water Level Sensor Voltage"
    icon: 'mdi:water-well'
    unit_of_measurement: 'V'
    update_interval: 1s #the delta filter will ensure it only sends values when something changes. 
    filters:
      - sliding_window_moving_average:
          window_size: 10 #creates a moving average of the last 10 values
          send_every: 1 #sends the moving average at every measurement (but only if it passes the delta filter below)) 
          send_first_at: 1 #after startup immediately start sending the result rather than wait for the first 10 measurements
      - delta : 0.0015 #only send the result if the voltage difference with the last sent result is higher than this
  - platform: template
    name: "Water Level Sensor usable CM" #first X cm are below pump inlet and thus unuseable.
    id: watertank_cm
    icon: 'mdi:water-well'
    unit_of_measurement: 'cm'
    lambda: |-
        return id(watertank_voltage).state;
    update_interval: 1s #the delta filter will ensure it only sends values when something changes. 
    filters:
      - calibrate_linear:
          # Measured value of X volt maps to y cm
          - 0.0 -> 0
          - 2.5 -> 165.0
      - delta : 0.001 #only send the result if the difference with the last sent result is higher than this
  - platform: template
    name: "Water Level Sensor usable %"
    id: watertank_percent
    icon: 'mdi:water-well'
    unit_of_measurement: '%'
    lambda: |-
        return id(watertank_cm).state / 165.0 * 100; 
      #divide by max water level height to get a percentage
    update_interval: 1s #the delta filter will ensure it only sends values when something changes. 
    filters:
      - delta : 0.001 #only send the result if the difference with the last sent result is higher than this
  - platform: template
    name: "Water Level Sensor usable liters"
    id: watertank_liter
    icon: 'mdi:water-well'
    unit_of_measurement: 'l'
    lambda: |-
        return id(watertank_cm).state / 100 * 3.14159265 * 1.13751599 * 1.13751599 * 1000.0;
      #height (meters) times pi times radius (meters) squared times 1000 gives liters.
    update_interval: 1s #the delta filter will ensure it only sends values when something changes. 
    filters:
      - delta : 0.001 #only send the result if the difference with the last sent result is higher than this

The code is not ideal in that it only sends the Voltage/Cm/Liters/% to ESPHome when there is a change. I would like to to also send the value every hour or every day or so even when there is no change (but send the value immediately if there is a change). I had tried adding the filter - heartbeat : 60m to accomplish this but it creates a very illogical behaviour. The result is that my delta filter is completely ignored and it just sends the value every hour and does not send any values anymore in between those periods even if there is a change.

So it seems like you have to choose between

  • either sending the values at a regular interval (but then you won’t immediately be notified of a change) like every 5 minutes or so.
  • doing the measurements very frequently (e.g. every second) so that Home Assistant immediately is notified of a change, and then prevent spamming home assistant using the delta filter (but then Home Assistant won’t get any values when there is no change for days for example).

I could not find a solution to this, if anybody has a solution, that’s very welcome.

9 Likes