October 2016

Collecting Sound Level Values with a Raspberry Pi

Quite a while back at one of our meetup groups one of the attendees wanted a way to monitor and document sound levels. We discussed a couple of approaches. One would be to use a microphone and monitor the amplitude of the sound and convert to a decibel reading. This would require some careful calibration with a good meter. Another approach would be to obtain an inexpensive sound pressure level meter that already is calibrated and met the appropriate standards. It turned out one model of meter at the time was only about $25 and not only gave a decibel readout but could also record to a micro sd card and had a USB serial interface as well. The meter is fairly easy to identify and can still be obtained inexpensively from a number of sources. Make sure it comes with the USB interface since the cable contains the serial converter -- the USB jack is non-standard. It can be powered solely from the USB cable as well.

The idea was to push the readings to the cloud in real time. With simple USB devices -- this can be accomplished fairly easily with the appropriate Python libraries on a Raspberry Pi. The Python program below was the one I used to push the readings to one of my sites that handles, graphs, and takes action based on streams of sensor data -- like the sound samples from this decibel meter. There are a number of free sites that can collect and display such data. A number of people have worked with the meter I chose before and some Python examples have been floating around for few years on how to read the USB to access the reading. Unfortunately as I'm writing this up now I don't have the original reference for the USB reading code (and perhaps this is circular because I posted my code to GitHub a couple years ago myself) but another recent use of the code is here. I utilized Python's PyUSB library to connect with the device (the usb.core.find line) and to transfer the decibel reading (the dev.ctrl_transfer line). In this example, I'm using the Python requests library to post the data to my site every second.

#!/usr/bin/python

import sys
import usb.core
import requests
import time

streams="Scott's Sound Level Meter:i"
tokens=""

dev=usb.core.find(idVendor=0x16c0,idProduct=0x5dc)

assert dev is not None

print dev

print hex(dev.idVendor)+','+hex(dev.idProduct)

while True:
    time.sleep(1)
    ret = dev.ctrl_transfer(0xC0,4,0,0,200)
    dB = (ret[0]+((ret[1]&3)*256))*0.1+30
    print dB
    msg="{'dB':'"+str(dB)+"'}"
    try:
        requests.post('https://temporacloud.com/connection/clientSend', data={'streams':streams,'tokens':tokens,'message':msg},verify=False)
    except: None 

The code can easily be adapted to store, display, or post the decibel readings in various ways. As I was writing this up I thought it might be nice to show display of the data on the Pi itself. So I stacked an Adafruit PiTFT Plus (320x240 2.8" Touch Display) on top of a Pi 3 and plugged in the meter. I then put together a really simple Python UI version of the program. I used the Raspbian image in the Adafruit Tutorial and followed the steps to add PyGame. Then I consulted Jeremy Bly's tutorial on the Pygame UI. The program is shown below:

#!/usr/bin/python

import usb.core
import os
import pygame
import pygameui as ui

os.putenv('SDL_FBDEV', '/dev/fb1')

dev=usb.core.find(idVendor=0x16c0,idProduct=0x5dc)

assert dev is not None

print dev

print hex(dev.idVendor)+','+hex(dev.idProduct)

class PiTft(ui.Scene):
    def __init__(self):
        ui.Scene.__init__(self)

        self.add_child(ui.Label(ui.Rect(0,0,320,50),'Raspberry Pi Sound Meter'))

        self.progress_view = ui.ProgressView(ui.Rect(20, 50, 280, 120))
        self.add_child(self.progress_view)

        self.db_value = ui.Label(ui.Rect(110, 170, 100, 30), '00.00 dB')
        self.add_child(self.db_value)

        self.progress = 0
        
    def update(self, dt):
        ui.Scene.update(self, dt)
        ret = dev.ctrl_transfer(0xC0,4,0,0,200)
        dB = (ret[0]+((ret[1]&3)*256))*0.1+30
        self.db_value.text = '%2.2f dB' % dB
        self.progress=(dB/100.0)
        self.progress_view.progress = self.progress
            
if __name__ == '__main__':
    ui.init('SoundMeter UI', (320, 240))
    ui.scene.push(PiTft())
    pygame.mouse.set_visible(False)
    ui.run()

You can see the PyGame UI interface in the video below. I used a cordless drill to increase the noise level for the demonstration. You can see that the meter lags the change in sound level a bit but the Pi is quickly getting the same reading transferred to it from the meter. This kind of recipe for using Python to read USB on the PI could be used to Internet enable many types of devices.