Raspberry PI controlled Light Clapper

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]

ATTENTION: UGLY CODE FOLLOWS.
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).

10 thoughts on “Raspberry PI controlled Light Clapper”

    1. i updated the post, adding the python code. I hope it helps, ask if something is unclear, i guess it will be. 🙂

  1. 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!

    1. 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:

              fin = open("/var/www/cgi-bin/clapper/killme", "r")
              if fin.readline() == "1n":
                  break;
              fin.close()
      

  2. 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

    1. 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.

  3. 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?

    1. 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.

Comments are closed.