Water Tanks

Molly has 3 water tanks, 2 fresh water and 1 gray water. Each has a tank level sensor connected to the display on the main control panel. This works of course, but just a little bit of work, we upgraded the monitoring. Now we can see how the levels change over time, record our usage and therefore predict more much longer the water will last.

The Sensor

Each tank is fitted with a BEP Marine Ultrasonic Tank Sender, model number TS1.

Operating Depth

0 to 2,000 mm

Output Signal

0 to 5 volts (dc)

Temperature Range

4 to 65 ºC

Analog to Digital Converter
Seeed ADC

We will use the Raspberry Pi to monitor the tank level. But to do this, we need to convert the 0 to 5 volt analog signal representing the tank level into a digital form. This is achieved with the Seeed 4-Channel 16-Bit ADC board based on the Texas Instruments ADS1115 chip.

We tapped into the existing wires connected to the control panel display and ran new wires to the Raspberry Pi. Drivers were installed as per the directions on the Seeed site and soon enough we could see the voltage representing the tank levels of the three tanks.

Just a note on the Seeed board, has the supported input voltage range is a little unclear from their website. The ADS1115 chip in theory can measure 0 to 5 volts, but in practice it is limited to 0 to 2 volts. This is because the chip is powered with 3.3 volts and not 5 volts, and the driver software is configured out of the box for only 2 volts. Both of these could be adjusted with some effort, like soldering and reprogramming. Neither of which we are going to do.


Creating a node-red function to convert the voltage into a tank level could be done with a simple scaling function. But instead we wanted to more accurately calibrate each tank. This was done by completely filling each tank, and then draining the tank in increments of 500 ml until it was empty and recording the voltage level at each increment. This might sound like a lot of work, but because we can record the voltage sensor, we actually only need to empty the tank and the system does the rest.

Once the tank level to tank volume relationship is known, it is easy to write a function to convert level to volume remaining. This was al relatively easy. The more difficult task was to work out how to record consumption (for the fresh water tanks) and discharge (for the gray water tank).

The fresh water tanks can hold more water than the sensor can measure. As the tanks are filled, the sensor will register 100%, but it is still possible to add about xxx liters of water. The same happens in reverse, the tank shows 100% fill until at xxx liters has been removed. The consumption function assumes the tank is always filled to the very top, and this is our usual practice.

The tank level will obviously fall until the next time the water tank is filled, and this lowest measured level will be used to calculate consumption. To avoid potentially false readings, the low point will only be considered low is the standard deviations of 20 readings spaced 5 seconds apart is below a set threshold. That is, the water level needs to be stable, and then the mean of these twenty readings will be used. This is mainly to avoid the situation of getting a low reading when driving on bumpy roads.

Once the tank is at 100% fill for at least 20 readings, then the tank is considered full again. The previous low point is then used to calculate consumption and the counters are all reset for the next cycle. This can be achieved with the following node-red function code.

var currentLevel = msg.payload;

var minLevel = context.get('minLevel')||100;

// Reset if new low level
if (currentLevel < minLevel) {
    context.set('minLevel', currentLevel);

// Write to database if tank is full with consumption
if ((currentLevel == 100)  && (minLevel < 100)){
    msg.payload = [{
        consumed: 100 - minLevel
    return msg;

Might add some code to reset minimum tank level if it has been stable for 15 minutes, even if it is been higher.

var lastLevel = context.get('LastLevel')||0;
var count = context.get('count')||0;

// Reset if persistently higher level
if (Math.abs(currentLevel - lastLevel) > 1) {
    context.set('LastLevel', currentLevel);
    context.set('count', 0);
} else {
    context.set('count', count += 1);
    if (count > 20) {
        context.set('minLevel', currentLevel);

The tank level will be sampled every 5 seconds and then filtered through a filter to average to the last 10 samples. There will a small lag when the tank is being used, but will settle out after a minute. This is done to avoid false low readings.


Each tank will will have the following display values

  • Amount of water currently available
  • Estimated time remaining
  • Tank level trend over time
  • Total consumption over time
Raspberry Pi with Seeed HAT attached. Just testing the system prior to installation in Molly.