Hello everyone!
Here’s my take on a simple and cheap water level sensor. I’m using this in a camper/RV build. I know I’m not the first person to do this. Nonetheless, hope this helps someone!
The Water Level Sensor described here features a 0-40kPa sensor, 2 physical buttons for calibrating the 0% and 100% levels, and a small OLED display to show the level. This will report report percentage and raw values to Home Assistant and exposes buttons for setting the 0% and 100% levels as well. However, it is not necessary to hook it up to HA. The device is also built on an ESP8266 board that sports 2 relays and 12V power input. I don’t use the relays in this project, but you could!
Parts:
- HX710B 0-40kPa Pressure Sensor (see important note about challenges below)
- ESP8266 2 Channel Relay Board
- I2C OLED Display 128x32
- Push Button Switches
- 4mm x 2.5mm tubing
- 4mm Push-to-connect Fitting
- 3mm screws from this kit
3D printed housing:
This housing holds the ESP8266 relay board, pressure sensor, and OLED display.
Here’s a link to the housing design in Onshape: Water Level Sensor.
You can export an STL for printing directly from that link, just right click on the Top and Bottom Housing Part in the bottom left and select Export. No user account is needed.
Note that the I2C OLED display is still being sold by Adafruit, but they have limited stock. If you buy a different product, you may need to adjust the housing design.
Steps for programming:
- Connect your FTDI to GND, RX<->TX, and TX<->RX.
- Set your FTDI for 3v programming, not 5v.
- Leave the FTDI VCC disconnected.
- Connect an external power supply to the board, such as a 12V source. I like to have a switch so I can easily power-on the device.
- Important: Add a jumper between GND pin and IO0.
- Load the YAML into your ESPHome web interface.
- Hold the button on the board.
- Connect power to the board.
- Connect FTDI to your PC.
- In ESPHome, click install via wired connection and select the COM port.
- Release the button on the board.
- The upload should begin. If not, try again.
- Power off the board and disconnect the FTDI.
- Remove the IO0 jumper.
- Plug it back in and you are done!
My ESPHome YAML configuration:
esphome:
name: water-level-sensor
friendly_name: Water Level Sensor
esp8266:
board: esp01_1m
restore_from_flash: True
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: !secret api_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Water-Level-Sensor"
password: !secret fallback_password
captive_portal:
# Note: a jumper is required between IO5 and RY1, IO4 and RY2.
switch:
- platform: gpio
pin: GPIO5
name: "Relay 1"
id: R1
restore_mode: ALWAYS_OFF
- platform: gpio
pin: GPIO4
name: "Relay 2"
id: R2
restore_mode: ALWAYS_OFF
sensor:
- platform: hx711
name: "Raw Value"
id: raw_value
dout_pin: GPIO2
clk_pin: GPIO15
gain: 128
update_interval: 60s
on_value:
- sensor.template.publish:
id: water_level
state: !lambda "return 100.0*((x-id(zero_value))/(id(full_value)-id(zero_value)));"
- platform: template
name: "Water Level"
id: water_level
unit_of_measurement: percent
binary_sensor:
- platform: gpio
pin:
number: GPIO1
mode:
input: True
pullup: True
name: "Tare Zero Button"
id: tzb
internal: True
on_press:
then:
- button.press: tarezero
- platform: gpio
pin:
number: GPIO3
mode:
input: True
pullup: True
name: "Tare Full Button"
id: tfb
internal: True
on_press:
then:
- button.press: tarefull
globals:
- id: zero_value
type: int
restore_value: True
initial_value: '-347000'
- id: full_value
type: int
restore_value: True
initial_value: '2400000'
button:
- platform: template
name: "Tare Zero"
id: tarezero
on_press:
then:
- lambda: |-
id(zero_value) = id(raw_value).state;
- platform: template
name: "Tare Full"
id: tarefull
on_press:
then:
- lambda: |-
id(full_value) = id(raw_value).state;
i2c:
sda: GPIO13
scl: GPIO12
display:
- platform: ssd1306_i2c
model: "SSD1306 128x32"
reset_pin: GPIO14
address: 0x3C
update_interval: 60s
lambda: |-
it.print(0, 0, id(roboto_16), "WATER");
it.print(0, 16, id(roboto_16), "LEVEL");
it.printf(127, 0, id(roboto_32), TextAlign::TOP_RIGHT, "%.0f%%", id(water_level).state);
font:
- file:
type: gfonts
family: Roboto
id: roboto_16
size: 16
- file:
type: gfonts
family: Roboto
id: roboto_32
size: 32
It exposes these entities in Home Assistant:
Issues I had along the way:
First, the initial batch of devices I ordered, well they leaked air slowly, which caused what appeared to be sensor drift. Just to confirm the sensor was leaking, I submerged it in water and looked for bubbles. The pressure head was only about 1 meter or 1.4 psi. You can see the small bubble slowly forming in this photo, indicating a leak.
It is critical that the plumbing and device do not leak. I would not recommend using this measurement technique in a critical application or inaccessible location. I ordered a second batch from a different reseller and they did not leak. The leaky units were sold by DWEII and the good units were sold by ATNSINC on Amazon. YMMV.
There is one other complication with using this technique to measure head pressure. At first, I figured I could just submerge the small tube (4mm OD, 2.5mm ID) to sense pressure at the bottom of the tank. That appears to work at first, but as the level in the tank slowly changes, it was not reporting a change in pressure. Sometimes the pressure change was happening in an oddly stepwise fashion. Turns out, surface tension of the water tends to keep it stationary in the tube. It resists small pressure changes simply by sticking to the walls. A quick google search indicated a tube needs to be something like 11mm in diameter to prevent surface tension from retaining the water. (Of course, we all know this phenomena too well. Any time you’ve ever lifted water out of a glass by placing your finger over the end of a straw. It is the same.) So anyways, I affixed the 4mm tube to the top of a 1" diameter pipe with a push-to-connect fitting. Any time the tank is fully emptied, the 1" pipe is also emptied. So the measurement can be zeroed at this time if needed.
IMPORTANT: read the paragraph above. You cannot simply submerge the 4mm tube and expect reliable measurements.
Of course, there are many other ways to measure water level in a tank. Many people have posted their solutions. If you want to spend a little more money, you could use a drop-in submerged pressure sensor like in this post, or use a threaded pressure sensor if you have a port that is located at the bottom of the tank.
Here’s a bonus picture of the prototype before I designed the housing for it:
Good luck making your own!