This shows you the differences between two versions of the page.
| en:tech:waterlevel [2022/11/10 20:33] – created bullar | en:tech:waterlevel [2026/08/12 13:38] (current) – claude | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== Water Level Sensor ===== | + | ====== Water Level Sensor ====== |
| + | |||
| + | ===== Motivation ===== | ||
| + | |||
| + | Our water supply here in Thailand comes from a deep well (120m) and is stored in two tanks (1000l + 2000l). There' | ||
| + | The fill levels of all tanks are controlled by mechanical float switches that turn on the respective pumps as needed. That works reliably most of the time, but a live fill-level display would be genuinely useful — especially once you've already started moving towards a smart home with [[https:// | ||
| + | |||
| + | {{ : | ||
| + | ===== Approach ===== | ||
| + | |||
| + | Ready-made solutions are, as always, quite expensive, and besides, the journey is the destination. I ruled out a mechanical solution from the start to stay maintenance-free. | ||
| + | I also didn't want a solution that requires drilling into the tanks below the maximum water level, where sealing could become a problem. | ||
| + | Since the tank geometry is known, an ultrasonic distance measurement of the water surface is a good fit. Even during pumping the water surface isn't rough enough to cause significant measurement errors. | ||
| + | Examples of DIY solutions can be found here too (some published after my own implementation in 2021): | ||
| + | - [[https:// | ||
| + | - [[https:// | ||
| + | |||
| + | Since we're dealing with water here, the common, cheap HC-SR04 ultrasonic module is out. But there' | ||
| + | |||
| + | <WRAP group> | ||
| + | <WRAP half column> | ||
| + | {{ : | ||
| + | </ | ||
| + | <WRAP half column> | ||
| + | {{: | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | The AJ-SR04M module has a number of nice properties: | ||
| + | - waterproof sensor head | ||
| + | - measuring range from 20cm to 450cm | ||
| + | - good resolution of 0.5cm | ||
| + | - five operating modes (pulse width (default), low power pulse width, automatic serial port, serial port trigger, ASCII code output) | ||
| + | - low standby current draw (< | ||
| + | |||
| + | From the HC-SR04 module we know pulse width measurement: | ||
| + | |||
| + | ^ Mode ^ R19 ^ Standby Current ^ Low Power Current ^ | ||
| + | ^ 1. Compatible HC-SR04 trigger mode (default) | open | <2mA | - | | ||
| + | ^ 2. Low Power mode | 300kΩ | <2mA | <40µA | | ||
| + | ^ 3. Automatic serial port mode | 120kΩ | <2mA | - | | ||
| + | ^ 4. Low power Serial port mode | 47kΩ | <2mA | <20µA | | ||
| + | ^ 5. ASCII code output mode | 0kΩ | <2mA | <20µA | | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | |||
| + | ===== Implementation ===== | ||
| + | |||
| + | I went with mode 4, where a trigger byte is sent over UART serial and the measurement result is then received serially. For that we need a 47KΩ resistor. An 0805 package is intended for R19, but thanks to the large pads a leaded resistor can easily be soldered on too. | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | The baud rate is 9600 baud, and after a measurement five bytes are transmitted in the following format: | ||
| + | |||
| + | <code C> | ||
| + | // AJ_SR04M_Sensor serial format: | ||
| + | // Trigger: 0x00 | ||
| + | // Response: Byte1 Byte2 Byte3 Byte4 | ||
| + | // Start Byte=FF | ||
| + | </ | ||
| + | |||
| + | The result is in millimeters. Thanks to the serial approach the sensor can now be read out with any PC (via USB-serial adapter) or any controller that has a UART. | ||
| + | So far we're only measuring the distance to the water surface. With a bit of math we can convert that distance into liters or a percentage value. | ||
| + | |||
| + | Thanks to [[https:// | ||
| + | |||
| + | < | ||
| + | |||
| + | In my case, as noted below, the following values apply to the water tank: | ||
| + | < | ||
| + | |||
| + | < | ||
| + | |||
| + | The formula could be simplified using the real values, but for clarity I implemented it exactly as shown. | ||
| + | |||
| + | ===== Home Assistant Integration ===== | ||
| + | |||
| + | With an ESP8266 module and ESPHome, integration into [[https:// | ||
| + | The following small C program writes the trigger byte on each update request and calculates the measurement result (in meters) from the five received bytes. | ||
| + | |||
| + | <file C AJ_SR04M_Sensor.h> | ||
| + | #include " | ||
| + | |||
| + | class AJ_SR04M_Sensor : public PollingComponent, | ||
| + | | ||
| + | |||
| + | AJ_SR04M_Sensor(UARTComponent *parent) : PollingComponent(5000), | ||
| + | |||
| + | void update() override { | ||
| + | |||
| + | char frame[5]; | ||
| + | int pos = 0; | ||
| + | float value = 0.0; | ||
| + | |||
| + | write(0x00); | ||
| + | while (available()) { | ||
| + | frame[pos] = read(); | ||
| + | pos++; | ||
| + | if(pos==5) { | ||
| + | if ((frame[0] == 0xFF) && (frame[4] == 0x00) && ((frame[1]+frame[2])==frame[3])) { | ||
| + | value = ((frame[1]<< | ||
| + | publish_state(value); | ||
| + | } | ||
| + | break; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | In ESPHome we create a new device and add our own code via an // | ||
| + | |||
| + | <code yaml> | ||
| + | esphome: | ||
| + | name: water-level-serial | ||
| + | comment: " | ||
| + | includes: | ||
| + | - AJ_SR04M_Sensor.h | ||
| + | </ | ||
| + | |||
| + | Next we add the UART interface. If we don't use pins from a hardware UART, a software UART is automatically implemented. Since I'm connecting a total of 3 modules in my solution, I run out of hardware UARTs anyway. | ||
| + | Finally the sensor implementation. The sensor with ID '' | ||
| + | |||
| + | <code yaml> | ||
| + | uart: | ||
| + | - id: uart_dist1 | ||
| + | tx_pin: GPIO14 | ||
| + | rx_pin: GPIO12 | ||
| + | baud_rate: 9600 | ||
| + | |||
| + | sensor: | ||
| + | - platform: custom | ||
| + | lambda: |- | ||
| + | auto my_sensor = new AJ_SR04M_Sensor(id(uart_dist1)); | ||
| + | App.register_component(my_sensor); | ||
| + | return {my_sensor}; | ||
| + | sensors: | ||
| + | unit_of_measurement: | ||
| + | accuracy_decimals: | ||
| + | name: "Water Tank Distance" | ||
| + | id: dist1 | ||
| + | - platform: template | ||
| + | name: 'Water Tank Level' | ||
| + | unit_of_measurement: | ||
| + | accuracy_decimals: | ||
| + | update_interval: | ||
| + | icon: mdi: | ||
| + | lambda: |- | ||
| + | return (((175-20)-((id(dist1).state*100)-20))/ | ||
| + | - platform: template | ||
| + | name: 'Water Tank Volume' | ||
| + | unit_of_measurement: | ||
| + | icon: mdi: | ||
| + | accuracy_decimals: | ||
| + | update_interval: | ||
| + | lambda: |- | ||
| + | | ||
| + | </ | ||
| + | |||
| + | ===== Mechanics ===== | ||
| + | |||
| + | The sensor head has clip tabs around its edge and is suited for an opening of about 20mm. | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | In thin-walled tank housings, a matching conduit connector lets you mount the sensor securely while also sheathing the cable with a cable sleeve. | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Here's the mounting on top of our well tank. This protects the sensor well against mechanical stress and environmental influences. It has now survived more than a year without a single fault. | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | In most cases the 2.5m sensor cable will probably be long enough. In my case, unfortunately, | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | The ESPHome module sits in a somewhat larger, waterproof enclosure. Its internals sit on a 3D-printed carrier. For two water tanks the serial signal arrives at the screw terminals via a 4-wire cable (with the adapter shown above in between), and the closer water tank is connected directly to the measuring module. As a power supply I always use these Apple-like [[https:// | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | |||
| + | ===== Results ===== | ||
| + | |||
| + | With the work done, the tank fill levels can now be monitored and actions derived from them. Here's a look at the tidy Home Assistant panel for the outdoor area, showing the tank fill levels live. | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Of course you can also look at the history: | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | The uneven fill level is due to manual topping-up, since the mechanical limit switch was broken at the time this article was written. | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | =====Links===== | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | |||
| + | ===== Donate ===== | ||
| + | |||
| + | If you'd like to support my work, feel free to buy me a cappuccino or so: . | ||
| + | |||
| + | < | ||
| + | |||
| + | <form action=" | ||
| + | <input type=" | ||
| + | <input type=" | ||
| + | <img alt="" | ||
| + | </ | ||
| + | |||
| + | </ | ||
| - | coming soon ... | ||