Tank Level Sensor ft. Home Assistant, ESPHome and Telegram.

DIYHome Assistant

15 Minute Read

A while back, the public road was going through a major overhaul, affecting electricity on most days. Thus my parents made me check the water level quite frequently. I hated it but one can obviously come up with a solution. Given the fact my brother was already running a Home Assistant instance on a Raspberry Pi, I wanted to give it a shot. So the idea is to get the distance between water and the tank, send it to Home Assistant, which will be used to convert it to water level in percentage. Along with this, Home Assistant Automations and Telegram Integration will be used to make it easy for my parents to access even if they aren't connected to the local WiFi network. We’ll also be predicting the pump state by taking the derivative on the sensor values in a custom set time frame. I have been using this setup for the past one and half years.

DIY Tank Level Sensor Dashboard
Home Assistant Dashboard

Circuit

Initially I was using a cheap ultrasonic sensor (HC-SR04) with an ESP8266. Even though it lasted for 6 months, it eventually got rusty since it’s deployed within the tank itself. The better solution was to use a waterproof ultrasonic sensor (JSN-SR04T), which was also much more stable in terms of readings. So it’s better to prefer the latter, but the firmware remains the same since it uses the same principle as well as the pinout. Also a 3D printed case was used for the setup.

TankLevelSensor Circuit
Circuit Diagram

Tank Level Sensor Hardware 01
Assembled Hardware with Case

ESP Home Configuration

Install the ESPHome integration wihtin Home Assistant, and use the following configuration for adding a new device. To reduce fluctuations, a moving window average was also used. Upload serially using the ESPHomeFlasher on another machine or through ESPHome Dashboard after connecting ESP8266 to Home Assistant deployed server with an USB Cable. Once the firmware is loaded, the device can be wirelessly (OTA) updated.

esphome: name: tanksensor esp8266: board: d1_mini # Enable logging logger: # Enable Home Assistant API api: ota: password: "677e4315fe34d0661fb05414c0c1d06a" wifi: manual_ip: static_ip: 192.168.1.189 gateway: 192.168.1.1 subnet: 255.255.255.0 ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "TankSensor" password: "ZMlTIiF0VOhX" captive_portal: sensor: - platform: ultrasonic trigger_pin: D2 echo_pin: D1 name: "Tank Empty Level`" update_interval: 0.4s accuracy_decimals: 3 timeout: 3.0m filters: - filter_out: nan - sliding_window_moving_average: window_size: 10 send_every: 10 send_first_at: 1 - platform: wifi_signal name: "Tank RSSI" update_interval: 10s binary_sensor: - platform: status name: "Tank Connection"

Once connected verify everything is working properly by checking the logs corresponding to the device added.

ESPHome Tank Sensor Logs

Home Assistant Config

Tank Level Configuration

The distance between sensor and water is returned from the Tank Sensor. This can be converted using the following configuration, utilizing a template sensor. We’ll be using availability_template to check whether the system is connected to home assistant, and it subsequently returns the percentage of water. So add the following configuration to configuration.yaml.

sensor: - platform: template sensors: water_tank_level: friendly_name: "Water Tank Level" unit_of_measurement: "%" availability_template: >- {% if states('binary_sensor.tank_connection') == 'on' -%} true {%- else -%} false {%- endif %} value_template: >- {%- set tank_sensor_var = { "tank_height": 1.2, "offset": 0.2, "debug_measured_distance": 0.033 } -%} {%- set tank_level = ((tank_sensor_var.tank_height - ((states('sensor.tank_empty_level') | float) - tank_sensor_var.offset)) * 100 / (tank_sensor_var.tank_height)) -%} {% if tank_level > 100 %} 100 {% elif tank_level < 0 %} 0 {% else %} {{ tank_level }} {% endif %} water_tank_level_int: friendly_name: "Water Tank Level (Int)" unit_of_measurement: "%" availability_template: >- {% if states('binary_sensor.tank_connection') == 'on' -%} true {%- else -%} false {%- endif %} value_template: >- {{ states('sensor.water_tank_level') | round | int }}

Pump State Configuration

You could predict whether the pump is on or off by considering a time frame of tank level and applying derivative on it. The following configuration exactly does the same thing. We’re not implementing any physical connection with the pump, we’re utilizing this just to know whether water is being filled or not. This also brings latency since we’re considering a time frame. Add the following configuration to configuration.yaml.

binary_sensor: - platform: template sensors: pump_state: friendly_name: "Pump State" availability_template: >- {% if states('binary_sensor.tank_connection') == 'on' -%} true {%- else -%} false {%- endif %} value_template: >- {%- set tank_sensor_var = { "tank_height": 1.2, "offset": 0.2, "debug_measured_distance": 0.033 } -%} {% if (((-(states('sensor.tank_empty_level_derivative') | float) / (tank_sensor_var.tank_height)) * 100) | round(4)) > 2.88 -%} on {%- else -%} off {%- endif %}

UI Configuration

The UI card is pretty much straight forward. Either make your own card or copy the following configuration and add it in your home assistant dashboard.

title: Tank type: vertical-stack cards: - type: entities entities: - entity: binary_sensor.tank_connection name: Status - entity: binary_sensor.pump_state name: Pump - hours_to_show: 24 graph: line type: sensor entity: sensor.water_tank_level_int icon: mdi:water-outline name: Water Level detail: 1

Automations

Here comes the coolest part, we’ll be using a Telegram Bot to notify whether the tank level goes below 40% or above 90%. Also a telegram command can be given to return the level of the tank at that instant. Home Assistant is capable of doing Push Notification through the App, but if the user is outside the coverage of local WiFi network, the user will be late to know the state. Hence we’ll be using a telegram bot to notify the user.

Dashboard Telegram Interface

Before doing anything, a new Bot has to be created on Telegram with BotFather and note down the API Key. Next is to find your Telegram User ID. If the bot is added to a group, the user ID of the group also should be found. User ID of group members can also be taken if you want to give access to accept commands to the respective users. I collected all the required Telegram User IDs and passed it to the following configuration which needs to be added in configuration.yaml.

telegram_bot: - platform: polling api_key: !secret telegram_apikey_home allowed_chat_ids: - !secret tg_id_group_home - !secret tg_id_user_abish - !secret tg_id_user_sasikala - !secret tg_id_user_dhanish - !secret tg_id_user_vijayan - !secret tg_id_user_gopika notify: - platform: telegram name: HomeGroup chat_id: !secret tg_id_group_home - platform: telegram name: HomeBot chat_id: !secret tg_id_user_abish

Navigate to the Automations section within settings and add the following configurations as automations.

  • Following is the Pump Turn Off Notifier automation.

    alias: Send Turn Pump Off - Telegram Bot description: "" trigger: - platform: numeric_state entity_id: sensor.water_tank_level above: "90" condition: - condition: template value_template: >- {% if states.automation.ultrasonic_telegram_notify.last_triggered is not none %} {% if as_timestamp(now()) | int - as_timestamp(states.automation.ultrasonic_telegram_notify.attributes.last_triggered) | int > 3600 %} true {% else %} false {% endif %} {% else %} false {% endif %} - condition: state entity_id: binary_sensor.tank_connection state: "on" - condition: state entity_id: binary_sensor.pump_state state: "on" action: - service: notify.homegroup data: message: | *Turn-Off Motor* (*{{ states('sensor.water_tank_level_int') }}%* Filled) mode: single
  • Following is the Pump Turn On Notifier automation.

    alias: Send Turn Pump ON - Telegram Bot description: "" trigger: - platform: numeric_state entity_id: sensor.water_tank_level above: "0.0" below: "45" condition: - condition: state entity_id: binary_sensor.tank_connection state: "on" - condition: state entity_id: binary_sensor.pump_state state: "off" - condition: template value_template: >- {% if states.automation.send_ultrasonic_distance_turn_on_telegram_bot.last_triggered is not none %} {% if as_timestamp(now()) | int - as_timestamp(states.automation.send_ultrasonic_distance_turn_on_telegram_bot.attributes.last_triggered) | int > 3600 %} true {% else %} false {% endif %} {% else %} false {% endif %} action: - service: notify.homegroup data: message: | *Turn-On Motor* (*{{ states('sensor.water_tank_level_int') }}%* Left) mode: single
  • Following is the Start Command for Telegram Bot, A Custom Keyboad will Come Below.

    alias: Start Command - Telegram Bot description: "" trigger: - platform: event event_type: telegram_command event_data: command: /start - platform: event event_type: telegram_command event_data: command: /start@NunusHomeBot condition: [] action: - service: notify.homegroup data: message: I'm a Bot for Nunus. Checkout the custom keyboard down below. data: keyboard: - /tank, /pump mode: single
  • Following is the Reply Handling When /tank command is received from Telegram.

    alias: Send Tank Status - Telegram Bot description: "" trigger: - platform: event event_type: telegram_command event_data: command: /tank - platform: event event_type: telegram_callback event_data: command: /tank - platform: event event_type: telegram_command event_data: command: /tank@NunusHomeBot - platform: event event_type: telegram_callback event_data: command: /tank@NunusHomeBot condition: [] action: - service: notify.homegroup data: message: > {% if is_state("binary_sensor.tank_connection", "on") -%} Water Left : *{{ states('sensor.water_tank_level_int') }}%* {%- else -%} Not Available (*Check Power*) {%- endif %} mode: single
  • Following is the Reply Handling When /pump command is received from Telegram.

    alias: Send Pump State - Telegram Bot description: "" trigger: - platform: event event_type: telegram_command event_data: command: /pump - platform: event event_type: telegram_callback event_data: command: /pump - platform: event event_type: telegram_command event_data: command: /pump@NunusHomeBot - platform: event event_type: telegram_callback event_data: command: /pump@NunusHomeBot condition: [] action: - service: notify.homegroup data: message: | {% if is_state("binary_sensor.pump_state", "on") -%} Pump is ON {%- else -%} Pump is OFF {%- endif %} mode: single

Conclusion

Pheww, that was quite easy since Home Assistant does all the heavy lifting, truly a versatile and scalable home automation platform. I know this isn’t something hard, but this was literally the first time I spent some time with Home Assistant. Enough bragging!. This setup was deployed quite a while back and has been reliable since then. Initially the pump was usually turned off when water overflowed from the tank to the ground floor. Since this setup notifies the level above 90%, we could actually turn it off at that instant, thus reducing wastage of water too. There’s also a bit of complication for adding a controllable switch for the pump since the outdoor unit has additional switches.

Let me know how your version turned out, or if you're stuck somewhere or want to correct me for any mistake I made, feel free to comment down below. I’m also looking forward to making a DIY display to show the tank level as well as some other nifty features surrounding it, which can be deployed near the pump switch in our Kitchen. Please let me know if you’re interested in the same, I’ll post it right here, at 3iinc.xyz.

About Author

Abish Vijayan

Where secrets lie in the border fires, on a gathering storm comes a tall handsome man in a dusty black coat with a red right hand.

Latest Posts