As mentioned before, my fish tank is like my testing grounds for sensors and new “stuff” that might end up as part of my weather station or just remain as is on the fish tank. This post is about a new sensor I added, a distance sensor, to measure the water level in the tank and maybe some other insights as well, like how much does outside temperature and humidity play in evaporation. The plan for this build was pretty simple, I had the sensor, it was part of another project/set that I poached, now it needed a mount to the tank, and I needed to understand the wiring.

Housing / Mount

I used SketchUp to start designing the mount. I needed a housing that would protect it from the tank, i wanted it to be neat so that the wiring was organized and protected, and finally my toddler daughter likes to feed and play near the tank so once again protected, neat, and organized were key. I ended up with a tiny tower that would slide and mount onto a C-clamp and overhang the tank enough to get a reading but not too far.

The 3D model came out well I thought, I had where the sensor would be mounted and then a sort of tunnel through the supports where the wires (4 of them) could run and then come out behind the tank and out of sight. Due to the shape, I would need a lot of supports to print this as one continuous piece, so I decided to chop it in half at the “tower” and have the two pieces reassemble with so simple screw holes. I was pleased with how easy this turned out to be and that it allowed the printing to go that much smoother.

Here’s the design after being split, sensor mounts in the foreground piece and the clamp connects on to the background piece. My clamp mount was sized to fit a specific C-clamp I already had, which I believe is a 2 inch Irwin C-clamp I got at Lowe’s ages ago.

I printed in PETG, over PLA, because this would be outside and somewhat exposed to the elements as well as the fish tank itself.

Here it is sliced in Prusa Slicer. I did have trouble with the wire tunnel in the first couple of slices and I had to go back and adjust the model a few times, it was then I learned the importance of faces in SketchUp:

When making a model in SketchUp, the white face is for the outside of the model, the blueish face is for the inside of a model, so when assembling your shapes and slices, if you end up with blueish on the outside, you need to right click that plane and choose the reverse face option, otherwise the Slicer could get confused and drop or cover a piece it shouldn't be. For me, the slicer wanted to wall off the wire tunnel until I got all the planes/faces inside the tunnel to be white outer designation, instead of the blueish inside designation, a subtle but important detail I learned.

Hardware / Circuit

The distance sensor I poached from another set was a lucky accident. It’s the RCWL-1601 from Adafruit ( The HC-SR04 appears to be one of the most common distance sensors out there. However, a key difference is that the HC-SR04 uses 5V in and on the data lines, while the RCWL-1601 can use either 3.3V or 5V, and by choosing 3.3V that matches up with the Raspberry Pi which is going to use 3.3V on the GPIO pins. If we were using the HC-SR04, the 5V on the data lines potentially could damage the pi so we would need a more complex circuit with a voltage divider, while with the the RCWL-1601 using the 3.3V for input, means that we can hook it up directly to the Pi GPIO pins without issue. So be sure you know which device you have before hooking it up, to avoid any potential overload problems.

Pins are super simple, the distance sensor needs power (3.3V VCC)and ground (GND), then there is a pin for the Trigger Pulse Input (TRIG) which allows us to trigger the sensor to send an ultrasonic pulse, and the Echo Pulse Output (ECHO) which is the pin that will let us know if and when that pulse returns. The pin stays low at 0V until the pulse is detected and then it goes HIGH (for us 3.3V) for the duration of the pulse. Therefore, we can also use just about any of the GPIO pins on the Pi for ECHO and TRIG, in this case we’ll use GPIO 23 & 24, respectively.


Printing, Assembling, and Wiring went just fine. I used 4 strands off a ribbon cable for the sensor and put female Dupont Connectors on both ends for connecting to the sensor and the pi, The fit of the sensor, pins, and wires in the housing was definitely snug. It made me a little nervous just because after it’s in there, it’s hard to check the fit of the connectors on the pins or reseat that connection to make sure they were fully connected.

The housing slid on to a C-clamp and then was secured with zip ties, I did not make any channels or holders for the zip ties, so they can slide around but I wasn’t overly worried about this.

Due to the shape of the tank I use for my Koi, I had to use a piece of cork for the clamp, otherwise it would end up on a off-center angle.

Closer shot of the Water Level Sensor mount
Whole Tank, water level sensor at the top


I used python to pull data from the distance sensor and just incorporated it into the existing fish tank code for the temperature sensor. This meant getting the measurement, checking if it was within valid limits, and finally using a 10 sample rolling average for that distance measurement at a rate of roughly once every 5 seconds. I used this time to do some cleanup:

  • I renamed the file, since we aren’t dealing with just one sensor anymore.
  • By using the clock and timing I adjusted the sleep command to be more accurate and get us more consistent every 5 second measurements.
  • Added the rolling average feature with sanity limits on the data to give more consistent distance sensor limits.
  • Added sanity limits on the temperature measurements as well.
  • Did some, not all unfortunately, of this using proper Git etiquette via pull requests.

I used the rolling average because the water level should not be changing quickly, evaporation should really be the only factor which takes time, but the surface of the water is also not still due to the filter and the Koi disturbing it as well. A rolling average should help smooth out any of these disruptions and hopefully make the data look more like a slightly downward tilting line.

Default Pin Factory

When running the code I got the following message:

PWMSoftwareFallback: For more accurate readings, use the pigpio pin factory.See for more info
'For more accurate readings, use the pigpio pin factory.'

After a little investigation and reading the docs I thought that yes, I would like more accurate readings so I would switch to the pigpio pin factory. Doing so was not that difficult, in the python code it was one line of code at the beginning and it also involved starting the pigpio daemon in linux before running the code. Out of the gate I’m not sure how much more accurate my readings were but I had another problem in that it stopped the 1-wire temperature gauge from reading correctly and I ended up with a lot more “None’ values in my dataset. So after about a half of day of using the different pin factory and not making a lot of progress in troubleshooting the other sensor, I switched back to the default.

Data Values / Plotting

I fed the values from the distance sensor into the InfluxDB in the same way that the temperature values went in, I simply added another element to the JSON data structure. Super simple. Once in to InfluxDB I could start plotting the values in Grafana,

My initial impressions were that the data was noisy, there seemed to be a decent amount of fluctuations even with the smoothing/averaging function added in. Also, I wasn’t sure but maybe there was even a temperature dependence for the distance sensor, I could sorta see that as the temperature increased during the day there seemed to be corresponding jumps in the water level data as well, something to look into more and maybe with another sensor try and run some direct temperature tests.

I liked the results, obviously could see a large spike when the tank was refilled (see plot below) and overall across the days you could see the downward trend as the water evaporated away Something else to explore down the road would be seeing how temperature and humidity of the air effected the evaporation rate.

I also noticed that the temperature sensor was not giving 100% consistent results and occasionally these spikes or drops would occur, and I’m not sure why. I added some data limits into the code so that if the temperature was above a certain amount or dropped below another value to throw out that measurement, that helped and made sense since you wouldn’t see values below freezing for the water temperature, and it would be extremely rare to see those temperature values get higher than 100° Fahrenheit. This was an important lesson in that when collecting data it’s good to put limits or sanity checks on that data if you can, that way you can throw out or respond differently to bogus values and end up with a more accurate and cleaner data set. My code for both the temperature and water level now has these checks in before the data is recorded.

Failure Modes

After running successfully for a few weeks I did run in to trouble with my distance sensor, we were having the house painted and I think the painters when working in this area had the sensor drop into the water. After the sensor broke and I installed a replacement I took a closer look and there was rust on some of the sensor and minerals from the tank water had solidified on another part. Regardless of if the sensor ended up in the water or not this debris was going to interfere with either the transmission or reception, or both of those ultrasonic waves. In the code I saw the following error message:

DistanceSensorNoEcho: no echo received
warnings.warn(DistanceSensorNoEcho('no echo received'))

There is no timeout on the distance measurement in the code, and this library doesn’t support it either (though some of the Arduino libraries appear to have timeout support) so the code hangs when trying to get the distance value. This blocking error prevents the temperature sensor from getting it’s readings when it is working fine. I need to find a good solution to this issue, ideally we have a timeout so that if that sensor is broken or misaligned then the code and measurement can break a little more gracefully and continue on. If I’m not able to get a timeout working then another approach would be separating the measurements into their own python code, I have a feeling that the GPIO pin library might not like being called from multiple running programs so this might not be a valid solution either. But for now if the distance sensor stops working then everything stops, not good.


Overall, I’m happy with the outcome, the distance sensor is very easy to use and wire into the system. The 3D Print of the holder isn’t 100% perfect and maybe needs some improvement but it does the job OK for now. And finally additional data and seeing how that relates to our temperature data is always exciting and never matches my expectations, the data being as noisy as it is was an unexpected surprise, I really did expect a relatively smooth sloping line, and I learned a few important lessons on filtering the data as well as how to expect and handle failure. I’m glad I implemented this sensor and I feel comfortable enough with it that I can use it in other projects and have the knowledge and experience to know how it will behave and what kind of data to expect.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.