Introduction: Microphonic Rain Gauge

I have a webcam in my conservatory that is connected to a Raspberry Pi. You can see it at the top right of the picture with the red LED just under the air-conditioning unit. When it rains the sound of the raindrops on the roof is very loud and I thought I could use the microphone in the webcam to estimate the amount of rain. The Raspberry Pi also controls my irrigation system, and I wanted to reduce the water consumption by stopping the irrigation when it has rained.

What you need:

  1. Raspberry Pi - mine is a Pi 2 model B with Rasbian Jessie on a 16Gbyte microSD card.
  2. Power Supply for the Raspberry Pi - mine can supply 2A, but I guess that anything over 1A would be OK.
  3. Internet connection for the Raspberry Pi - either wired or Wi-Fi.
  4. USB webcam, with microphone, that works on the Raspberry Pi - mine is an old Logitech QuickCam.
  5. Python program that analyses the sound from the microphone.

Yes, there is no other hardware, just the microphone in the USB webcam. I guess it would also work with any other microphone input, e.g. a USB soundcard with a microphone attached.

The results are pretty good - so long as there is no other source of noise in the conservatory. See the calibration results below. The program also detects when there is wind noise and attempts to avoid counting wind as rain. This works pretty well in my conservatory - yours may be different!

I guess it would work in a green house or a caravan, or anywhere where the rain makes a loud noise and where it is normally quiet.

Step 1: Get the Webcam Working With the Raspberry Pi

Follow one of the many excellent guides for setting up your Raspbery Pi, connecting it to the internet and logging in to it using ssh from your favourite computer. You will need to use a few linux commands. If you need it, this looks useful. I use nano (or pico, which looks pretty much the same, I think) as my text editor.

Make sure that everything is up to date with:

sudo apt-get update
sudo apt-get upgrade

Shutdown the Pi, plug your webcam into a spare USB port then restart. Run

dmesg

and should include something like this depending on your exact setup

[1490297.271752] usb 1-1.5: new high-speed USB device number 9 using dwc_otg
[1490297.372032] usb 1-1.5: New USB device found, idVendor=0409, idProduct=0059 [1490297.372070] usb 1-1.5: New USB device strings: Mfr=0, Product=0, SerialNumber=0 [1490297.373214] hub 1-1.5:1.0: USB hub found [1490297.373522] hub 1-1.5:1.0: 4 ports detected [1490297.651633] usb 1-1.5.1: new high-speed USB device number 10 using dwc_otg [1490297.917838] usb 1-1.5.1: New USB device found, idVendor=046d, idProduct=08cc [1490297.917861] usb 1-1.5.1: New USB device strings: Mfr=0, Product=0, SerialNumber=2 [1490297.917874] usb 1-1.5.1: SerialNumber: 852690BE [1490297.920911] uvcvideo: Found UVC 1.00 device (046d:08cc) [1490297.965656] input: UVC Camera (046d:08cc) as /devices/platform/soc/3f980000.usb/usb1/1-1/1-1.5/1-1.5.1/1-1.5.1:1.0/input/input2 [1490297.991837] usb 1-1.5.1: Warning! Unlikely big volume range (=3072), cval->res is probably wrong. [1490297.991859] usb 1-1.5.1: [5] FU [Mic Capture Volume] ch = 1, val = 4608/7680/1

and if you run

lsusb

you should get something like

Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. Bus 001 Device 008: ID 0d8c:0014 C-Media Electronics, Inc. Bus 001 Device 009: ID 0409:0059 NEC Corp. HighSpeed Hub Bus 001 Device 010: ID 046d:08cc Logitech, Inc. Mic (PTZ)

where one line will correspond to your webcam. And

ls -l /dev/video0

should give something like

crw-rw---T 1 root video 81, 0 Oct 17 19:47 /dev/video0

If you get this your webcam is working for video and you can install your favourite video program. I use motion.

You can install it with

sudo apt-get install motion

This instructable is about the microphone so I won't go into the details of how to get motion to work.

Step 2: Get the Microphone Working

Actually if you got similar results to mine when you were getting the video to work, the microphone is already working!

Run

arecord -l

and you should see something like

**** List of CAPTURE Hardware Devices ****
card 0: Device [USB Audio Device], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0 card 1: U0x46d0x8cc [USB Device 0x46d:0x8cc], device 0: USB Audio [USB Audio] Subdevices: 0/1 Subdevice #0: subdevice #0

I have 2 USB sound input devices. The webcam is obviously "card 1" as the description includes the USB ID as listed in lsusb above. You will need to use this card number for the further tests in this step.

I changed the snd-usb-audio line in /etc/modprobe.d/alsa-base.conf to

options snd-usb-audio index=0,1 vid=0x0d8c,0x046d pid=0x0014,0x08cc

That ensures that my sound cards always have the same card numbers. If you only have one USB sound device, you probably don't have to do do this - the webcam microphone should always come up as card 1.

You can test the microphone by making a recording. Replace the plughw: number with the card number of your microphone

arecord -D plughw:1 --duration=10 ~/mictest.wav

Then play it with

aplay ~/mictest.wav

If you have working headphones or loudspeakers attached to your Raspberry Pi, you should hear the recording. If you hear nothing, first check your headphones or loudspeakers by trying to play a different .wav file, Next check the volume in the next part of this step.

It is important for this project that the microphone volume is fixed - or the calibration of rainfall vs. sound level won't be constant. Run

amixer -c 1

Where the number after the "-c" corresponds to the card number of your microphone.

You should get something like

Simple mixer control 'Mic',0
Capabilities: cvolume cvolume-joined cswitch cswitch-joined penum Capture channels: Mono Limits: Capture 0 - 3072 Mono: Capture 1536 [100%] [30.00dB] [on]

This means that the volume control is called 'Mic' so you can adjust it with

amixer -c 1 sset Mic 50%

And now

amixer -c 1

gives

Simple mixer control 'Mic',0
Capabilities: cvolume cvolume-joined cswitch cswitch-joined penum Capture channels: Mono Limits: Capture 0 - 3072 Mono: Capture 3072 [50%] [24.00dB] [on]

And if you add "amixer -c 1 sset Mic 50%" to your /etc/rc.local file, the volume will always be set to 50% after each reboot. If you need to do anything that changes the volume, make sure you reset it afterwards with "amixer -c 1 sset Mic 50%" You may need to change the 50% to suit your microphone.

Step 3: Get the Python Program Working

The python program needs to import a few standard libraries. Most are already installed in Jessie, except pyaudio. Install it with

sudo apt-get install python-pyaudio

Download the python program (rain.py) and from the same directory run it with

python -u rain.py

The -u ensures that the output of the program appears immediately on the terminal screen. Without it the output is buffered and can take some time to appear.

If your webcam is recognised correctly, the program should open the microphone and print a load of stuff and then something like this, with a new line of numbers appearing about every 6 seconds

........
We will open device 1
ave: 254 [  0 703 479 266 222 185 195 161 177 150] wind level: 0
ave: 274 [ 0 927 489 285 201 172 187 158 161 157] wind level: 0

On the other hand if you get this

No sound input device found. Try again with device_debug = 1 to see what devices PyAudio can find. Bye

you have some work to do to get pyaudio to recognise your microphone. At around line 29 of rain.py, check that

device_debug = 1

and run it again with

python -u rain.py

Towards the end of the load of stuff you should see a few lines looking something like

{'defaultSampleRate': 16000.0, 'defaultLowOutputLatency': -1.0, 'defaultLowInputLatency': 0.0239375, 'maxInputChannels': 1L, 'structVersion': 2L, 'hostApi': 0L, 'index': 2, 'defaultHighOutputLatency': -1.0, 'maxOutputChannels': 0L, 'name': u'USB Device 0x46d:0x0059: Audio (hw:1,0)', 'defaultHighInputLatency': 0.096}

One should correspond to your webcam. Look for

'maxInputChannels': 1L

This says it is an input i.e. a microphone. You will have to adjust the tests in about line 41 of rain.py to get it to connect to yours. For a single USB microphone similar to mine this worked:

if dev['maxInputChannels'] == 1 and dev['defaultSampleRate'] == 16000.0 :

Good luck!

Assuming that you succeed, you can change around line 29 to

device_debug = 0

Now run it again and look at the numbers - a new line should come every 6 seconds

ave: 269 [  0 873 411 304 224 185 195 161 180 158] wind level: 0
ave: 280 [ 0 937 475 281 209 185 195 174 176 167] wind level: 0 ave: 284 [ 0 791 649 296 228 171 188 173 182 164] wind level: 0

Each line shows the result of analysing one chunk of the audio signal from the microphone. The above 3 lines are typical of my microphone when there is not much sound.

The first part is the average power - around 280. The units are arbitrary - so we will have to calibrate it in the next step.

The second part - between the square brackets are the power levels in 10 frequency bands. The lowest one is forced to be zero.

The third part is an estimate of the level of wind. This is used to reduce the chance of wind noise from being registered as rain.

Now make some noise. Talk, put on some music, tap the webcam or wait for some noisy rain (if the webcam is already in your conservatory). You should see something like

ave: 307 [  0 739 584 400 317 229 231 210 189 173] wind level: 54
ave: 14138 [ 0 13832 21242 10181 14903 24935 13589 30922 7767 4013] wind level: 2155 ave: 10666 [ 0 14417 18740 16290 8441 9277 9960 17068 7375 5098] wind level: 6093

So we can see that louder sounds make higher numbers corresponding to higher power levels. If yours doesn't change, go back and check that your microphone is really working.

Leave it running for at least 5 minutes then you can see that it appends its results to the file /run/shm/rain1.csv in a similar format

21/10/2015 17:05:02,0,3171,1787,1636,959,362,244,424,256,204,204,0.0,1228

This is a csv (comma separated values) ready to be read by your favourite spreadsheet or web scripting language.

The first value is the date and time.

The next 10 are the power levels in the 10 frequency bands, averaged over the 5 minutes.

The next is the estimated rainfall so far that day.

The last is the average wind level over the last 5 minutes.

I found that the average value of the highest frequency band was the best correlation to rainfall. The 4th value was similar to the highest for rain, but was very sensitive to wind, so is used to avoid wind from counting as rain.

Every 24 hours, at midnight, it saves the day's rainfall in /home/pi/rain_daily.csv and resets the rainfall saved in /run/shm/rain1.csv

If you want, you can look at the comments in rain.py to see how it works. You will probably have to do this if you need to change anything or calibrate it for your microphone.

Attachments

Step 4: Calibrate the Microphone

The idea is to set the parameters in rain.py so that

  1. it gives as accurate as possible a measurement of any rain that there is
  2. it doesn't register any rain when there isn't any
  3. it doesn't say there is a lot of rain when there is only wind

There are 3 parameters to set.

  1. mm_per_million. This adjusts the sensitivity. A bigger number here makes the rain estimate bigger.

  2. quiet_noise. This sets the threshold below which it is assumed there is no rain.

  3. wind_noise. This sets the threshold that it uses when it thinks it is windy.

You can find them at around line 16 of rain.py.

First we need to set the noise levels. While it is quiet, look at the readings in highest frequency band - the one just before the ]. If you are getting something like

ave: 269 [  0 873 411 304 224 185 195 161 180 158] wind level: 0
ave: 280 [ 0 937 475 281 209 185 195 174 176 167] wind level: 0 ave: 284 [ 0 791 649 296 228 171 188 173 182 164] wind level: 0

then it is the 158, 167, 164 that you are looking at. I found that quiet_noise should be around 10 times this. So around 1600. I found that wind_noise should be 2 times quiet_noise, so 3200.

Next we need to measure some actual rainfall and compare with the estimate from the python program. Ideally you would collect data from periods of rainfall at different rates. It isn't obvious that the sound intensity will increase linearly with rainfall or that light rain will make the same cumulated sound intensity as the same amount of heavy rain. However my data (limited) suggests that it is right to within about a factor of 2. This is by no means perfectly accurate, but good enough to decide when to turn off the irrigation system!

I used a washing up bowl and a measuring cylinder. Leave it until some rain has fallen, then empty the water into the measuring cylinder and put the bowl out again. Note down the volume of water each time and the corresponding estimate from the python program. In the python program, adjust the parameter "mm_per_million". So if you measure 10mm rain in the cylinder and the python program gives 2mm, you need to multiply the "mm_per_million" by 5.

I got these measurements recently:

actual        from program
3.8mm 2.6mm
4.1mm 7.5mm
11mm 8.5mm
6mm 7.2mm

Next, the best thing is to leave it running for some days of quiet, wind, rain and both wind & rain. Ideally you would also measure the rainfall from time to time. Then look carefully at the file /run/shm/rain1.csv. You can see the values recorded every 5 minutes along with the cumulative rainfall estimated (reset each day at midnight) and the wind level. You may need to go back and adjust the parameters. It took me a week or 2 before I was happy with the results.

Good luck!

Step 5: Making It Run Automatically

Create /home/pi/rain.sh containing this

#!/bin/sh
/usr/bin/python -u /home/pi/rain.py >> /run/shm/rain_py.log 2>&1

and make it executable with

chmod u+x /home/pi/rain.sh

When this little script is run, it launches rain.py and sends all the output to a file /run/shm/rain_py.log You can follow the contents of this file by running

tail -f /run/shm/rain_py.log

Now add this to /etc/rc.local

/sbin/start-stop-daemon --start -b --chuid pi --exec /home/pi/rain.sh

and rain.py will be launched at boot. Your /etc/rc.local file should now look something like this

amixer -c 1 sset Mic 50%
/sbin/start-stop-daemon --start -b --chuid pi --exec /home/pi/rain.sh

Step 6: So What....?

Well I guess this is rather complicated and maybe not very accurate, but if you already have a USB webcam, it does give you a completely free way of estimating rainfall.

Sorry there aren't any nice photographs - you can imagine what a Raspberry Pi looks like with a USB cable plugged in!!

I used it to switch off the irrigation on days after a lot of rain. It saved some valuable water (and some money). The main thing is that I enjoyed doing it. Let me know if you like it!