This is one of my first Raspberry PI projects, and consists of a Raspberry connected to a microphone which detects an hand clap and then controls via GPIO a relay that powers the lamp.
The code used for the detecting the clap (which is not perfect, because it analyzes only the volume of the microphone) is this:
[actually this is not the same code used in the video above, it has been improved, in fact the delay between the two claps is reduced in this script]
The code has been written only to provide a proof-of-concept project, and it’s written joining various pieces of code found in the Internet, so don’t expect this code to be bug free or well written. It is not.
You should use it to understand the way it works and to write something better or to adapt it to your own project.
#!/usr/bin/python import urllib import urllib2 import os, sys import ast import json import os import getpass, poplib from email import parser import RPi.GPIO as GPIO file = 'test.flac' import alsaaudio, time, audioop class Queue: """A sample implementation of a First-In-First-Out data structure.""" def __init__(self): self.size = 35 self.in_stack = [] self.out_stack = [] self.ordered = [] self.debug = False def push(self, obj): self.in_stack.append(obj) def pop(self): if not self.out_stack: while self.in_stack: self.out_stack.append(self.in_stack.pop()) return self.out_stack.pop() def clear(self): self.in_stack = [] self.out_stack = [] def makeOrdered(self): self.ordered = [] for i in range(self.size): #print i item = self.pop() self.ordered.append(item) self.push(item) if self.debug: i = 0 for k in self.ordered: if i == 0: print "-- v1 --" if i == 5: print "-- v2 --" if i == 15: print "-- v3 --" if i == 20: print "-- v4 --" if i == 25: print "-- v5 --" for h in range(int(k/3)): sys.stdout.write('#') print "" i=i+1 def totalAvg(self): tot = 0 for el in self.in_stack: tot += el for el in self.out_stack: tot += el return float(tot) / (len(self.in_stack) + len(self.out_stack)) def firstAvg(self): tot = 0 for i in range(5): tot += self.ordered[i] return tot/5.0 def secondAvg(self): tot = 0 for i in range(5,15): tot += self.ordered[i] return tot/10.0 def thirdAvg(self): tot = 0 for i in range(15,20): tot += self.ordered[i] return tot/5.0 def fourthAvg(self): tot = 0 for i in range(20,30): tot += self.ordered[i] return tot/10.0 def fifthAvg(self): tot = 0 for i in range(30,35): tot += self.ordered[i] return tot/5.0 def wait_for_sound(): GPIO.setmode(GPIO.BOARD) GPIO.setup(11, GPIO.OUT) # Open the device in nonblocking capture mode. The last argument could # just as well have been zero for blocking mode. Then we could have # left out the sleep call in the bottom of the loop card = 'sysdefault:CARD=Microphone' inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE,alsaaudio.PCM_NONBLOCK, card) # Set attributes: Mono, 8000 Hz, 16 bit little endian samples inp.setchannels(1) inp.setrate(16000) inp.setformat(alsaaudio.PCM_FORMAT_S16_LE) # The period size controls the internal number of frames per period. # The significance of this parameter is documented in the ALSA api. # For our purposes, it is suficcient to know that reads from the device # will return this many frames. Each frame being 2 bytes long. # This means that the reads below will return either 320 bytes of data # or 0 bytes of data. The latter is possible because we are in nonblocking # mode. inp.setperiodsize(160) last = 0 max = 0 clapped = False out = False fout = open("/var/www/cgi-bin/clapper/killme", "w") fout.write('0n'); fout.close() queue = Queue(); avgQueue = Queue(); n = 0; n2=0; while True: fin = open("/var/www/cgi-bin/clapper/killme", "r") if fin.readline() == "1n": break; fin.close() # Read data from device l,data = inp.read() if l: err = False volume = -1 try: volume = audioop.max(data, 2) except: print "err"; err = True if err: continue queue.push(volume) avgQueue.push(volume) n = n + 1 n2 = n2 + 1 if n2 > 500: avgQueue.pop() if n > queue.size: avg = avgQueue.totalAvg() print "avg last fragments: " + str(avg) low_limit = avg + 10 high_limit = avg + 30 queue.pop(); queue.makeOrdered(); v1 = queue.firstAvg(); v2 = queue.secondAvg(); v3 = queue.thirdAvg(); v4 = queue.fourthAvg(); v5 = queue.fifthAvg(); if False: print "v1: "+str(v1)+"n" print "v2: "+str(v2)+"n" print "v3: "+str(v3)+"n" print "v4: "+str(v4)+"n" print "v5: "+str(v5)+"n" #if v1 < low_limit: print str(n)+": v1 ok" #if v2 > high_limit: print str(n)+": v2 ok" #if v3 < low_limit: print str(n)+": v3 ok" #if v4 > high_limit: print str(n)+": v4 ok" #if v5 < low_limit: print str(n)+": v5 ok" if v1 < low_limit and v2 > high_limit and v3 < low_limit and v4 > high_limit and v5 < low_limit: print str(time.time())+": sgaMED" out = not out GPIO.output(11, out) queue.clear() n = 0 time.sleep(.01) wait_for_sound()
The code was found on the Internet and then adapted for my purposes. It uses a standard USB microphone, and should work with most of the linux-compatible USB mics (I think even webcam integrated mics).
python code?
i updated the post, adding the python code. I hope it helps, ask if something is unclear, i guess it will be. 🙂
Hi,
I found your project via Google, it looks really interesting. I’m trying to get it work with the provided code, but I think I’m missing /var/www/cgi-bin/clapper/killm. Is that right?
Thanks!
Hi! The file /var/www/cgi-bin/clapper/killme is the file I use to stop the clapper. I need it because I run clapper.py from a web interface and I can’t kill it with Ctrl+C, so I made another program (launchable from the web interface) which just writes an ‘1’ in the killme file, and this makes the clapper stop. You can probabily avoid using it, by removing the following lines:
Cool, that did the trick, thanks! The code runs now, and prints the avg last fragments, but it also prints the line ‘err’. This is what’s repeated:
avg last fragments: 112.038
err
avg last fragments: 112.04
err
avg last fragments: 112.06
err
avg last fragments: 112.034
err
I got it working now, pretty cool. Thanks a lot for providing the script.
I got it working too but one question:
I don’t understand the v1 – v5 and the high and low limit code. Is this how loud I have to clap or?! I don’t get it please help ^-^
They are debug info of the mic level detected.
The script works this way: It records the volume of the mic in five different moments, and tries to find a pattern: it should print the average volume for each of the five ranges and if it founds a low-high-low-high-low pattern it will trigger the GPIO. I don’t remember exactly how I set the limits, maybe you should adjust them depending on your mic sensibility.
That also means that you have to clap every time with same interval beetween claps 🙂 It should be improved, but it wasn’t my purpose when I wrote it, I just needed it to work.
hello.. is it resistant to the other noise (not a clap) that we dont want it to trigger the output? like cars passing by, dropping stuffs, sneezing, etc?
Hello, I don’t think it’s a really robust algorithm. It uses only the volume of the microphone, so every noise with the right timing and volume will trigger the light. Infact, I had some problems of false positives, the algorithm should be improved by matching the sound frequencies too, but I just wanted to do it quickly. Thank you for the interest, if you improve the system, feel free to share and I may link to your solution.