By: Andrew Schwartz || 🗓 April 10, 2025
Snowmelt Lysimeters measure the amount of liquid water coming out of the bottom of the snowpack and are useful in determining the amount of melt occurring and how that corresponds to various hydrometeorological conditions. There are no standards for snowmelt lysimeter engineering or manufacturing and, therefore, their development is often bespoke and based on site and/or project characteristics.
At their most basic, snowmelt lysimeters are pans that collect and measure water. In order to do so, they have a drain and method for transporting the water to a measurement location that often involves tipping buckets that use a simple technology, magnetic reed switches, for recording rainfall quantities. For the CSSL's four 2m X 2m snowmelt lysimeters, we used four RainWise Rainew tipping buckets placed under the southeast corner of each with a gentle slope towards that corner to ensure water moves into the tipping buckets rather than remaining stationary at the bottom of the pan. Given the simple measurement and instrumentation methods employed, an expensive and/or complicated datalogger system seemed unnecessary and we elected to create a datalogger using the well-known platform Raspberry Pi. This project doesn't need to be used with lysimeters - it can be used simply as a tipping bucket data logger or, if you want to do some customization, a datalogger for other instrumentation. The information that I'm going to include in this tutorial is based on what we did at the Snow Lab and the steps for the project will reflect that.
I find that the easiest way to think about an engineering project such as this one is by starting at the end with our overall goal informing the design:
What are we trying to measure?
What instrument is used for that measurement?
How does that instrument work and, therefore, how might it interface with our Raspberry Pi?
While determining how we can connect the instrument to the Raspberry Pi, we should also be considering what code we can use to run and make measurements of that sensor. Some sensors, such as our tipping buckets, are very basic and act as buttons with easy code to implement whereas others can be much more complicated.
NOTE: Knowing how instruments work has benefits far beyond these types of projects. Having this knowledge enables a better understanding of the benefits, limits, considerations, and potential sources of error for each type of technology and its data.
What components are needed to make the system work?
Once those questions are answered, we can begin designing and planning the build. It's fairly common to encounter roadblocks when doing these types of projects and that's ok, iterative projects that take a bit of extra time to get everything just right are never bad.
As mentioned above, we need to develop our list of components that we need to complete the project. For this project, it includes the Raspberry Pi, a micro-SD card for the Raspberry Pi's operating system, a way to connect the tipping buckets to the Raspberry Pi, and (optionally) a communication system. Many people like to use breadboards for Raspberry Pi projects but we've found that using a terminal block is easier and faster. Plus more terminal blocks are used widely by hydrometeorological instrumentation manufacturers, so working with them is a common skill in the field. For this project, you will need these:
Key components:
Raspberry Pi 5
Micro-SD card
Terminal block for Raspberry Pi
Tipping Buckets
Peripherals:
USB keyboard
USB mouse
Computer monitor with HDMI input
The Snow Lab is fortunate to have network infrastructure at the site for normal ethernet data communication and Power-Over-Ethernet (PoE) connections to supply devices with power. This allows us to use one ethernet cable for both communication and power to the Raspberry Pi and we designed our datalogger to take advantage of this by adding a PoE hat to the build. As such, there are several optional steps below that talk specifically about incorporation of the PoE hat - those can be ignored if you're using traditional power and/or communication sources.
Raspberry Pis are fully functional miniature computers that run on their own operating system, which is just a modified version of Linux. As with any computer, setup needs to be done with a monitor, keyboard, and mouse. Make sure you have a USB keyboard and mouse (not bluetooth) as you can plug them directly into the Raspberry Pi board.
Now that we have our components ready, we're going to install the Raspberry Pi Operating System (OS). The easiest way to do this is using the Raspberry Pi Imager and instructions on how to install and use the imager can be found here. When using the imager, you'll select the version of Rasperry Pi that you have (we have version 5), the version of the Raspberry Pi OS that you'd like to use, and the storage device to put the operating system on. Make sure you select you micro-SD card when choosing the storage device and not a local drive for your computer.
Once the OS is on your micro-SD card, insert the card into the slot on the Raspberry Pi, attach your keyboard, mouse, and monitor, and plug in your USB-C power supply to it. It will start up and then will take you through the setup steps using the connected monitor.
Assembly will be slightly different depending on whether you have chosen to keep your recorded tipping bucket data on the Micro-SD card or if you'd like communication with your Raspberry Pi via ethernet. An added benefit to PoE, as mentioned above, is that only one cable is required to provide communication and power whereas using other forms of communication and power will likely require more.
Standard Installation Using Micro-SD Card To Keep Data:
In order for us to enable easier interfacing between the Raspberry Pi and our tipping bucket(s), we need to add a terminal block. This allows us to attach the wires from the tipping buckets to the necessary Raspberry Pi channels without the need for a breadboard. This is relatively easy to do as it only requires the alignment of the pins on the top of the Raspberry Pi to the channels on the bottom of the terminal block. IMPORTANT: Damage to the Raspberry Pi and/or sensors can occur if the terminal block is mounted incorrectly, the labels on the terminal block directly correspond to the pins on the Raspberry Pi, so they have a right and wrong orientation. In order to ensure that the terminal block is mounted correctly, examine the Raspberry Pi pinout and then align the terminal block with the correct pins.
Connecting the tipping buckets:
The tipping buckets are quite easy to connect to the terminal block. Pick a GPIO channel/terminal to use and use that for the positive connection (red) to the tipping bucket and then choose a ground terminal. I like to use ground terminals that aren't right next to the positive connection as that reduces the likelihood of a short circuit from any stray bits of wire.
The connections that can be seen on the right is to GPIO pin/terminal 12 (GP12 red) and the ground terminal (GND black). This is defined in our code below as being for our southeast lysimeter/tipping bucket. You can connect as many or as few tipping buckets as you like as long as there are enough GPIO and ground connections.
Optional Installation With PoE For Power And Communication:
Our PoE kit comes with a few different parts:
The PoE hat with fan for cooling (blue)
Thermal paste squares to transfer heat between chips and heat sink (pink)
Heat sink (black)
Screws and standoffs (gold metal towers)
To install the PoE kit on your Raspberry Pi:
Remove plastic from both sides of the thermal paste squares and attach the largest one to the CPU (silver chip in the middle of the board) and the other two on the larger black chips on the board.
Attach the heat sink to the top of the Raspberry Pi and gently push the plastic pins with the springs on them into the holes on the board. NOTE: The two holes to use are not the ones in the corners but are next to them.
Attach the standoffs to the Raspberry Pi corners using the provided screws.
Attach the PoE hat the Raspberry Pi ensuring careful alignment with the PoE hat pins (4 pins next to ethernet port) and GPIO pins.
Attach PoE hat to standoffs using the screws provided.
Finally, attach the terminal block to the GPIO pins on the PoE hat ensuring correct alignment as discussed in the previous section.
Sometimes the biggest challenge for new projects can be determining which function works for the sensor that you're using. On a very basic level, reed switches act like buttons, they close an electrical circuit briefly - as the magnet on the tipping bucket moves past the switch, it closes the circuit briefly (similar to pressing a button) as it pulls the two pieces of metal towards each other. Since we want to count the number of times the bucket tips to get the correct quantity of rainfall, we can use the Button function from the GPIOZero package to tell the Raspberry Pi when the circuit has been closed. The Button function uses the Raspberry Pi's General Purpose Input and Output (GPIO) pins to make these measurements.
I like to use VIM to edit scripts but you can use any editing program or IDE that you would like to use to create the script. There are several built in code editors on Raspberry Pi 5, such as Thonny, that can also be used. For this new script, regardless of editing program, we're going to call it datalogger.py since it will be acting as the datalogger for our tipping buckets.
The very first line of our script is known as the shebang line, which tells Unix which interpreter to use to execute the script. As we're working in python, our shebang line will look like this:
#!/usr/bin/env python
It's always good practice to give information about the program, instrument used, author, and anything else pertinent in the comments at the top of the python script. This ensures that others that may use your script know what it's for but can also serve as a reminder if it has been a while since you've used it and have forgotten some of the information. Here, you can see the top of our script:
# Datalogger program for CSSL snowmelt lysimeters (datalogger.py)
# Author: Andrew Schwartz (emailaddress@organization.edu)
# Version: 0.1 (11/7/2024)
# Background info: RainWise Rainew buckets used
# Resolution: 0.01" (0.254 mm) per tip
# Data reporting period (s) - 5 mins
Next, we're going to import some packages to help us detect the tips (Button from gpiozero), keep track of time and output time in our data file (timedelta and datetime from datetime; time), and allow the script to interact with the Raspberry Pi's operating system (os). We will also define the number of seconds we want between measurements, since we want 5 minute measurements to match other snow lab data, we will list the period as 300 seconds.
import os
import time
from datetime import datetime, timedelta
from gpiozero import Button
period = 300 #Number of seconds between data outputs, 5 mins = 300
Now we're going to use our imported Button function to assign which pin(s) corresponds to our tipping bucket(s). There are 26 available pins that can be used and they are labeled as 'GPIO [number]' in this image. That means that we can connect anywhere from 1 to 26 tipping buckets to the Raspberry Pi. As we are installing tipping buckets under four snowmelt lysimeters at the lab, we'll have four pin assignments that you can see here:
NW_sensor = Button(22)
NE_sensor = Button(23)
SW_sensor = Button(5)
SE_sensor = Button(12)
As we want a five minute temporal resolution using UTC time for our data collection (generally preferable to using local time when collecting data), we're going to get our most recent time that was a multiple of 5. This can be changed to give any resolution you would like. If you're using a tipping bucket to examine extreme rainfall events, it would be worth it to use one minute observations. If you're just interested in precipitation accumulation totals, hourly or daily data may be enough.
# Bootstrap by getting the most recent time that had minutes as a multiple of 5
time_now = datetime.utcnow() # Or .now() for local time
prev_minute = time_now.minute - (time_now.minute % 5)
time_rounded = time_now.replace(minute=prev_minute, second=0, microsecond=0)
This section of code will wait until the next five minute interval and then will check whether there's already a data output file that it can append with new data. If an output file doesn't exist, it will create one to fill with the tipping bucket data.
while True:
# Wait until next 5 minute time
time_rounded += timedelta(minutes=5)
time_to_wait = (time_rounded - datetime.utcnow()).total_seconds()
if time_to_wait < 0:
time_to_wait = 0
time.sleep(time_to_wait)
# Check if the file exists before opening it in 'a' mode (append mode)
file_exists = os.path.isfile('CSSL_lysimeter_data.csv')
file = open('CSSL_lysimeter_data.csv', 'a')
if not file_exists:
file.write('Time (UTC), Northwest Lysimeter (mm), Northeast Lysimeter (mm), Southwest Lysimeter (mm), Southeast Lysimeter (mm)\n')
At the beginning of each five minute period we want to set our tip counts to be zero, otherwise, we would be infinitely adding tips and we'd have incorrect data for our five minute periods. Once we've defined that, we want to get the time and start a while loop that allows us to record tips (button presses) starting at the current time and lasting for five minutes. We then say that if one of the sensors records a tip (.is_pressed; we're still pretending it's a button), it's count number goes up by 1 (e.g. NW_count +=1).
IMPORTANT: The button function will record data for the length of time that it believes a button is being pushed. As there is a time lag between the magnet triggering the reed switch and its release, we need need to ensure that we only record the first moment of connection and not a prolonged connection before the magnet fully passes the reed switch. As such, we need to define a time for the program to sleep between tips (in seconds) because otherwise we could record multiple tips even though only one has occurred. Through bench testing with our tipping buckets, we determined that 0.25 seconds was ample to only give us one register per tip.
NW_count = 0
NE_count = 0
SW_count = 0
SE_count = 0
now = time.time()
while time.time() < now + period:
if NW_sensor.is_pressed:
NW_count +=1
time.sleep(0.25) #Time allowed between tips
if NE_sensor.is_pressed:
NE_count +=1
time.sleep(0.25)
if SW_sensor.is_pressed:
SW_count +=1
time.sleep(0.25)
if SE_sensor.is_pressed:
SE_count +=1
time.sleep(0.25)
Now, we have the number of tips being stored but that doesn't adequately portray the rainfall quantity actually being received. To get rainfall quantity rather than the number of tips, we just have to multiply the number of tips by the tipping bucket resolution. This was listed at the top of our script for our tipping buckets but can be found in any instrumentation specification documents.
A QUICK LYSIMETER NOTE: This step changes when using tipping buckets for lysimeters as the area collecting water is fundamentally different than the orifice size of the tipping bucket. Our lysimeters are 2m x 2m (area = 4x10⁶ mm²) and our tipping bucket orifice is 8" (203.2mm; area = 32429.27866 mm²) and, by employing some quick algebra and geometry, we can solve for the conversion rate from our lysimeter size to the tipping bucket measurement interval (1 mm/4x10⁶ mm² = X mm/ 32429.27866 mm² → X = 8.107319665x10⁻³ mm). This is what we'll put as our conversion factor but, if you're using the tipping buckets for precipitation, you can skip this step and include 0.254 mm instead since this is the tip resolution.
# Multiply tips by instrument resolution
NW_count_mm = NW_count * 8.107319665e-03 #0.254
NE_count_mm = NE_count * 8.107319665e-03 #0.254
SW_count_mm = SW_count * 8.107319665e-03 #0.254
SE_count_mm = SE_count * 8.107319665e-03 #0.254
Now that we have our collected data, we need to write it to the file that we opened or created above. Of course, we need a timestamp to align this data with the correct period, so we'll do that first. Finally, we specify how we want python to write the data.
# Get current time
timestamp_tz = datetime.utcnow()
# Write next line of data to file
file.write(timestamp_tz.strftime('%Y-%m-%d %H:%M:%S') + ', {:.3f}, {:.3f}, {:.3f}, {:.3f}\n'.format(NW_count_mm, NE_count_mm, SW_count_mm, SE_count_mm))
All of that work should leave you with this:
# Datalogger program for CSSL snowmelt lysimeters
# Author: Andrew Schwartz (emailaddress@organization.edu)
# Version: 0.1 (11/7/2024)
# Background info: RainWise Rainew buckets used
# Resolution: 0.01" (0.254 mm) per tip
# Data reporting period (s) - 5 mins
import os
import time
from datetime import datetime, timedelta
from gpiozero import Button
period = 300 #Number of seconds between data outputs, 5 mins = 300
NW_sensor = Button(22)
NE_sensor = Button(23)
SW_sensor = Button(5)
SE_sensor = Button(12)
# Bootstrap by getting the most recent time that had minutes as a multiple of 5
time_now = datetime.utcnow() # Or .now() for local time
prev_minute = time_now.minute - (time_now.minute % 5)
time_rounded = time_now.replace(minute=prev_minute, second=0, microsecond=0)
while True:
# Wait until next 5 minute time
time_rounded += timedelta(minutes=5)
time_to_wait = (time_rounded - datetime.utcnow()).total_seconds()
if time_to_wait < 0:
time_to_wait = 0
time.sleep(time_to_wait)
# Check if the file exists before opening it in 'a' mode (append mode)
file_exists = os.path.isfile('CSSL_lysimeter_data.csv')
file = open('CSSL_lysimeter_data.csv', 'a')
if not file_exists:
file.write('Time (UTC), Northwest Lysimeter (mm), Northeast Lysimeter (mm), Southwest Lysimeter (mm), Southeast Lysimeter (mm)\n')
NW_count = 0
NE_count = 0
SW_count = 0
SE_count = 0
now = time.time()
while time.time() < now + period:
if NW_sensor.is_pressed:
NW_count +=1
time.sleep(0.25) #Time allowed between tips
if NE_sensor.is_pressed:
NE_count +=1
time.sleep(0.25)
if SW_sensor.is_pressed:
SW_count +=1
time.sleep(0.25)
if SE_sensor.is_pressed:
SE_count +=1
time.sleep(0.25)
# Multiply tips by instrument resolution
NW_count_mm = NW_count * 8.107319665e-03 #0.254
NE_count_mm = NE_count * 8.107319665e-03 #0.254
SW_count_mm = SW_count * 8.107319665e-03 #0.254
SE_count_mm = SE_count * 8.107319665e-03 #0.254
# Get current time
timestamp_tz = datetime.utcnow()
# Write next line of data to file
file.write(timestamp_tz.strftime('%Y-%m-%d %H:%M:%S') + ', {:.3f}, {:.3f}, {:.3f}, {:.3f}\n'.format(NW_count_mm, NE_count_mm, SW_count_mm, SE_count_mm))
Save the script with all your changes and get ready to test it.
This is easiest done on the command line on your Raspberry Pi and is as simple as calling python to execute the script:
python3 datalogger.py
Often, it's better to run a process in the background so other tasks can be undertaken on the command line. To do this, we can add an ampersand to the end of the command:
python3 datalogger.py &
As mentioned earlier, we used our PoE hat as an efficient interface for both power to the Raspberry Pi and communication with it at our site. However, there are alternatives to using the PoE hat, inlcuding Bluetooth, which every Raspberry Pi 5 has the capability to use. As we didn't include that on our build, I'm not an authority on this and, instead, will point you to this helpful page to set up Bluetooth communications with your Raspberry Pi for data collection and monitoring.
Cronjobs are an easy way to schedule tasks and scripts without requiring a user to initiate them in a terminal each time. They can also be used to run a script when a computer reboots, which can regularly happen when stable power supplies can't be used during fieldwork. A power outage causing a script to stop and not start when the computer powers back on can be disastrous if data collection for a prolonged period is needed. As such, we're going to create a conjob to start our script when the Raspberry Pi 5 reboots.
For this, you'll need to edit the Cronjob file on the Raspberry Pi. As I mentioned before, I like to use VIM to edit in the terminal but nano can be used as well. To edit the file, type:
sudo crontab -e
This will bring up the cronjob file that we need to edit. Next, we'll put a line in the file that tells the computer to wait 60 seconds after reboot (this ensures required processes start running before we run our script) and then start our tipping bucket script. Replace '/home/cssl/' with the path to your script.
@reboot sleep 60 && python3 /home/cssl/datalogger.py &
Close the file and you should see something like this:
crontab: installing new crontab
At this point, to test our cronjob addition and make sure it works, we can reboot our Raspberry Pi:
sudo reboot
And then we can check the running python processes once it reboots and waits 60 seconds using this command:
ps aux | grep python
If it worked, you should see something like:
root 823 65.0 0.9 480352 20480 ? Ssl Mar15 43207:37 python /home/cssl/datalogger.py
With your script working correctly, data will start being logged every five minutes that will look like the table below if the quantity correction is applied for use with the lysimeters as we stated. If you're using the script for to log precipitation then the values will be in multiples of 0.254mm.
We can select a period to visualize to inspect the data a bit easier from our datalogger at the CSSL. Looking at a melt period between April 6th and April 15th 2025, we can see a significant amount of difference between the rates of the lysimeters despite them being collocated (~0.5 meters apart). This highlights why multiple lysimeters are required to paint a full picture of snowpack water discharge - preferential flow channels often develop in ways that may cause lateral flow in the snowpack, reducing the measured water in one or more lysimeters and increasing it in others. By having multiples deployed and making measurements, we can average the values to get an answer closer to what is actually occurring. We can also see that the Northwest lysimeter was recording some erroneous values and, by comparing those to the other three, we can determine that they can be omitted from analysis.
Congratulations on working your way through the tipping bucket tutorial! Now that you have a working tipping bucket datalogger, it's worth it to test it inside under various conditions and circumstances to ensure it keeps operating as desired in the field before placing it in service.
I sincerely hope that it was helpful and as enjoyable as possible. If you have feedback or suggestions on how to improve it, please feel free to reach out to us!