empowering creative people

Showcase Image

Introduction

Summary

A Raspberry Pi 3B is used to collect digital counts from an electronic rain gauge (1mm of rain per reed switch closure by magnetic see-saw). It has been running without issues for over 12 months. A UPS powers the Pi so it is immune from short power glitches.

The total count is displayed on an LCD screen with reset and inc/dec test buttons. “To HTML” and “To LCD” routines are also included to simplify changing display screens from HTML (standard monitor for debugging) to LCD (for running in compact format).

The Pi stores the data in a log file which is interrogated by WiFi (LAN) from a PC with an open document format spreadsheet using data linking.

Resetting the count causes the current count and timestamp to be written to the log file and a new sum to be initiated for the next line of the data file.

The system is checked using a manual tip-it-out rain gauge in a different location some metres away. When first installed the agreement was within a few percent. At the moment the Pi is showing about 12% more than the manual gauge. I regard that as reasonable agreement. It may need the collecting cups cleaned to reduce the discrepancy.


Project Description

Details

The Raspberry Pi 3B has built-in WiFi and is connected to the LAN using the router’s SSID and password. This makes file sharing a snap. The Open Document format spreadsheet used for data presentation has a link to the R Pi log file which gets a new line every time the counter is reset. The reset adds the previous total as a new timestamped line on the Pi file.

The three buttons on the LCD display are used in pairs (two buttons must be pressed together) to reduce the chance of accidental operation. This is implemented in the software using not(A or B) which is equivalent to (not A and not B) by De Morgan’s law.

The Python GPIO library is used to set the mode of operation of the digital input pins from the rain gauge (pin 40) and the three display switches K1 K2 and K3 (pins12, 16 and 18) as inputs with pull up resistors.

Other imports are guizero which provides functions to set up the graphical user interface on the LCD display, and some time functions.

The count, current time (now) and reset time (res) are declared as globals to make them available to the GUI and rainCounter() routines.

Main program

This is cunningly disguised by the if statement which was needed by the GUI setup process. This routine sets up the GUI to show the required data on the LCD. When the framework has been setup, the updateDisplay() routine is called. Then after a 50ms pause, the main loop is started by calling the routine updateCounter().

updateDisplay()

This routine sets up the values shown on the LCD. The current value of count (this is the mm of rain since the previous reset), and the current timestamp. If the count is zero, the timestamp for reset is set to the same as the current time. Note that the “current” time is actually the time of the last change in the count value.

updateCounter()

Calls the rainCounter() routine to wait for a rain gauge signal and tests for switch signals while waiting. Calls the updateDisplay() routine to refresh the LCD with new data from rainCounter() (globals count and times). If the count value is non zero, wait for the next rain gauge trigger. In any case, after 50ms recursively run updateCounter().

rainCounter()

The rainCounter() routine waits for a signal from the rain gauge reed switch. The reed switch closes when the rain gauge see-saw buckets change state (one bucket is full). This takes the normally high (pulled up) input at pin 40 low. While waiting for this event, the states of the K1, K2 and K3 switches are tested at 10ms intervals.

If K1 and K3 are found to be both pressed at the same time, the reset routine is entered. If the count value is zero, no further action is taken and the wait loop resumes. Otherwise, a writing flag is set, the log file is opened for writing in append mode, the formatted timestamp and count value are written to the file, the file is closed and the writing flag is cleared.

A wait loop waits for the writing to be completed, and another wait loop waits for K1 and K3 to be released. Then the counter is reset to zero and the main wait loop resumes.

If K1 and K2 are found to be both pressed at the same time, the count value is incremented. A wait loop waits for the combination of K1 and K2 to be released. Then the main wait loop resumes.

If K2 and K3 are found to be both pressed at the same time, the count value is decremented but is not allowed to be negative. A wait loop waits for the combination of K2 and K3 to be released. Then the main wait loop resumes.

If no button combinations are detected, the main wait loop resumes after 10ms.

When the rain gauge reed switch closure is detected (input 40 goes low), the counter is incremented.

Software

#!/usr/bin/python

from guizero import *
import RPi.GPIO as GPIO
import time, sys, datetime

global count, now, res #count needs to be global.
count=0
now=datetime.datetime.now()
res=now

GPIO.setmode(GPIO.BOARD) # Pins are as numbered on the PCB 1-40.
GPIO.setup(40, GPIO.IN, pull_up_down=GPIO.PUD_UP) # set pin 40 (rain gauge contact closure) as input with pull-up.
GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_UP) # set pin 12 (switch K1) as input with pull-up.
GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_UP) # set pin 16 (switch K2) as input with pull-up.
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) # set pin 16 (switch K3) as input with pull-up.

def rainCounter(): # waits for the input to go low then increments the count.
Global count, now, res # must declare globals.
while GPIO.input(40): # input is normally high.
if not(GPIO.input(12) or GPIO.input(18)): # K1 and K3 buttons pressed = reset.
if count==0: # skip reset if count is already zero.
return
writing=1 # flag to wait for writing data to complete.
rLog=open("/home/pi/rainLog.txt", mode="a") # open log file in append mode.
rLog.write(now.strftime("%d-%m-%y, %H:%M:%S, ")+str(count)+" mm\n") # write current data.
rLog.close() # close log file.
writing=0 # clear flag.
while(writing): # idle loop to wait for data writing.
time.sleep(0.01) # wait 10ms between input tests.
while not(GPIO.input(12) or GPIO.input(18)): # avoid multiple resets.
time.sleep(0.01) # wait 10ms between input tests.
count=0 # reset counter.
return # exit rainCounter function without incrementing count.

if not(GPIO.input(12) or GPIO.input(16)): # K1 nd K2 buttons pressed = increment count.
count=count+1 # increment counter.
while not(GPIO.input(12) or GPIO.input(16)): # avoid multiple counts.
time.sleep(0.01) # wait 10ms between input tests.
return # exit rainCounter function.

if not(GPIO.input(16) or GPIO.input(18)): # K2 nd K3 buttons pressed = decrement count.
count=count-1 # reset counter.
if count0: # skip wait if count=0.
while not(GPIO.input(40)): # avoid multiple counts by waiting for the input to return to high.
time.sleep(0.01) # wait 10ms between input tests.
ctr.after(50, updateCounter) # recursive call - wait 50ms then wait for another input change.

def updateDisplay(): # change the displayed values in the GUI.
global count, now, res # must declare globals.
now=datetime.datetime.now()
if count==0:
res=now
ctr.set(count) # set the GUI values.
nowDT.set(now.strftime(" %d-%m-%y %H:%M:%S"))
resDT.set(res.strftime(" %d-%m-%y %H:%M:%S"))


if __name__ == '__main__': # execute the ff code if the module is running directly, not as an import.
app=App(title="Rain Gauge GUI", # create the GUI with grid layout.
layout="grid",
width=320,
height=230)
box1=Box(app, layout="grid", grid=[0,0]) # box with inner grid for switch labels.
k13=Text(box1, text="K1+K3", grid=[0,0]) # label for switches K1 and K3 (reset).
k13a=Text(box1, text="reset", grid=[1,0])
k12=Text(box1, text="K1+K2", grid=[3,0]) # label for switches K1 and K2 (incr).
k12a=Text(box1, text="incr", grid=[4,0])
k23=Text(box1, text="K2+K3", grid=[5,0]) # label for switches K2 and K3 (decr).
k23a=Text(box1, text="decr", grid=[6,0])

box2=Box(app, layout="grid", grid=[0,1]) # box at [0,1] with inner grid for date, count, date reset.
nowDT=Text(box2, text="time", size=12, color="red", grid=[0,0]) # current Date and time at the top row 0 col 0.
nowText=Text(box2, text="now", size=12, color="red", grid=[0,1])
ctr=Text(box2, text="0", size=80, color="blue", grid=[1,0]) # Rain Counter variable at box2 grid row 1 col 0.
unit=Text(box2, text=" mm", size=20, color="blue", grid=[1,1]) # rain unit label at box2 grid row 1 col 1.
resDT=Text(box2, text="time", size=12, color="red", grid=[2,0]) # date reset at box2 grid row 2 col 0
resText=Text(box2, text="reset", size=12, color="red", grid=[2,1])

updateDisplay() # initialise the GUI display.
ctr.after(50, updateCounter) # wait 50ms then start the loop.

app.display() # end of GUI creation.


Rain-gauge-data-logger-no-case

Rain-gauge-data-logger-no-case-side-view

Have a question? Ask the Author of this guide today!