In questo simpatico progetto, si mostra come realizzare un Clapper, ovvero un sistema d’illuminazione azionato dal battito delle mani. Ottimo per far restare di stucco i propri amici!

Ma non è tutto, vogliamo aggiungere anche quel pizzico di atmosfera suggestiva, facendo in modo che oltre alle luci venga avviato persino un brano musicale preimpostato. Quello che più ci aggrada.

Quel di cui si ha bisogno nella realizzazione di un sistema del genere è:

1. Un microfono.
2. Arduino/Microcontrollore.
3. Attuatore (In questo progetto useremo Philips Hue, ma possiamo utilizzare anche un semplice relè come attuatore).

Se già abbiamo dimestichezza con arduino, sapremo che il collegamento da fare è simile a questo mostrato in figura:

Microfono arduino

Il microfono che si vede è amplificato, predisposto per essere piantato sulla breadboard, lo si trova in rete a pochi dollari spedizione gratuita inclusa (personalmente l’ho ritirato da un venditore cinese su ebay).

Il codice arduino usato per interfacciarsi al microfono, è stato sviluppato da Manoj Kunthu e modificato da me. Viene riportato qui di seguito:

Codice Microphone:

 
/*-----------------------------
*   Method Prototypes
*-----------------------------*/
 
void initialize();
void runDetector();
boolean clapDetected();
 
int detectClaps(int numClaps);
void indicateClaps();
void readMic();
 
void printStats();
 
/*-----------------------------
*   Variable Declarations
*-----------------------------*/
 
int TOTAL_CLAPS_TO_DETECT = 2; //The number of claps detected before output is toggled
int offset = 20;    // The point above average that the clap is detected
int CLAP_TIME=4000; // The time allowed between each clap
 
 
int sensorValue = 0; //the value read through mic 
 
int toggleOutput = -1;
 
int SIZE = 3;
int buffer[3];
int loopIteration = 0;
int average = 0;
int total = 0;
 
 
 
//BOARD INPUT MIC
const int inPin0 = A0; 
 
//BOARD OUTPUT SIGNALS
const int clapLed1 = 12, clapLed2 = 11, out = 10, readyPin = 13;
 
//CLAP STATE CONSTANTS
const int FINAL_DETECTED = 0, LOST_CONTINUITY = 1, CLAP_NOT_DETECTED = 2;
 
void setup() {
  Serial.begin(9600);
 
  //direct representation of the light bulb that toggles on/off  
  pinMode(out, OUTPUT);
 
  //once initialize() runs the ready pin turns on
  pinMode(readyPin, OUTPUT);
 
  //respective clap LEDs, more can be added
  pinMode(clapLed1, OUTPUT);
  pinMode(clapLed2, OUTPUT);
}
 
 
void loop() {
  initialize();
  runDetector();
}
 
/**
* Purpose: Prepares the buffer to recognize ambient noise levels in room.
*/
void initialize()
{
  loopIteration = 0; 
  total = 0;
  average =0;
 
  digitalWrite(clapLed1, LOW);
  digitalWrite(clapLed2, LOW);
  digitalWrite(out, LOW);
 
  for(int i = 0; i < SIZE; i++)
  {
    readMic();
 
    buffer[i] = sensorValue;
    total = total + sensorValue;
    average = (total/(i+1));
 
    Serial.print("INIT - AVE: ");
    Serial.print(average);
    Serial.print("    Total: ");
    Serial.print(total); 
    Serial.print("    Sensor: ");
    Serial.print(sensorValue); 
    Serial.print("    Change: ");
    Serial.println(sensorValue-average); 
 
    delay(50);
  }
  digitalWrite(readyPin, HIGH);
}
 
/**
* Purpose: Runs the detector algorithm. Developers can change the number of claps by adjusting TOTAL_CLAPS_TO_DETECT variable up at the top.
*/
void runDetector()
{
  while(true)
  {
    int clapState = detectClaps(TOTAL_CLAPS_TO_DETECT);
 
    if(clapState == FINAL_DETECTED || clapState == LOST_CONTINUITY)
    {
       Serial.println("--done--");
       indicateClap(0);//turn off any clap indicating lights
    }
  }
}
 
/**
* Purpose:  Detects the number of claps specified. This method is recursive
*/
int detectClaps(int numClaps)
{
  int clapNum = numClaps;
 
  //Base Case - if clapNum is 0, then all claps have been accounted.
  if(clapNum == 0)
  {
    //the output can now be toggled.
    toggleOutput *= -1;
    indicateClap(clapNum);
 
    Serial.println("-----  Clap Limit Reached - Output Toggled -----");
    Serial.println("OK"); // Stampiamo in seriale "OK"
 
    return FINAL_DETECTED;
  }
 
  //Read from mic and update ambient noise levels.
  readMic();
 
  total = (total - buffer[loopIteration]) + sensorValue; 
  average = (total/SIZE);
  buffer[loopIteration] = sensorValue;
 
  loopIteration = (loopIteration+1)%SIZE;
 
  if(clapDetected())
  { 
    Serial.print("detectClaps - Claps:");
    Serial.println(TOTAL_CLAPS_TO_DETECT + 1 - numClaps); 
 
    printStats();
    indicateClap(clapNum);
 
    delay(100);
    for(int i = 0; i < CLAP_TIME; i++)
    {
      int clapState = detectClaps(clapNum - 1);   
 
      if(clapState == FINAL_DETECTED || clapState == LOST_CONTINUITY)
      {
         return clapState;
      }
    }
    return LOST_CONTINUITY;
  }
  return CLAP_NOT_DETECTED;
}
 
/**
* Purpose: Turns the LED on appropriately to signal a clap detection.
*/
void indicateClap(int clapNum)
{
  if(clapNum == 0)
  {
    if(toggleOutput == 1)
    {
      digitalWrite(out, HIGH);
    }
    else
    {
      digitalWrite(out, LOW);
    }
    digitalWrite(clapLed1, LOW);
    digitalWrite(clapLed2, LOW);
  }
  else if(clapNum == 1)
  {
     digitalWrite(clapLed1, HIGH);
  }
  else if(clapNum == 2)
  {
     digitalWrite(clapLed2, HIGH);
  }
  delay(110);
}
 
/**
* Purpose: Prints basic statistics data for more info with sensor readouts and data points.
*/
void printStats()
{
  Serial.print("--- AVE: ");
  Serial.print(average);
  Serial.print("    Total: ");
  Serial.print(total); 
  //Serial.print("    iterNum: ");
  //Serial.print(loopIteration); 
  Serial.print("    Sensor: ");
  Serial.print(sensorValue); 
  Serial.print("    Change: ");
  Serial.println(sensorValue-average); //This is what I used to determine the 'offset' value
}
 
/**
* Purpose:  A clap is detected when the sensor value is greater than the average plus 
*     an offset.  The offset might need to be fine tuned for different sound sensors.
*/
boolean clapDetected()
{
    return sensorValue > average + offset;
}
 
/**
* Purpose: Reads mic input and stores it in a global variable.
*/
void readMic()
{
  sensorValue = analogRead(inPin0);  
}

Il codice python è scritto per funzionare con versioni di python 3 o superiori.

I file .py son 3. E se già abbiamo installato l’interprete python sul pc, non abbiamo bisogno di nient’altro. Mostriamo a questo punto quali sono i file essenziali del progetto:

Un modulo per l’avvio della canzone di sottofondo di default, ricercata all’interno di una specifica directory nel PC.

start_file.py:

import os
import sys
 
'''
Cerca un file in un percorso e lo avvia.
'''
 
def find(name, path): # funzione di ricerca (non ricerca in sub-directory ma solo nel path indicato)
    i = 0
    name = name.lower() # tutti i caratteri minuscoli
    name = name.strip() # via gli spazi a inizio o fine stringa
    for root, dirs, files in os.walk(path):
        while i < len(files):
            if name in files[i].lower():
                return path+"\\"+files[i] #os.path.join(root, name)
            i = i + 1
 
def avvia_file(titolo_parziale, percorso_cartella):
    percorso_file = find(titolo_parziale, percorso_cartella ) # NOME FILE (o parte del nome), PERCORSO DOVE CERCARLA ex. D:\Musica
 
    if percorso_file:
        os.startfile(percorso_file) # eseguiamo il file
        print(titolo_parziale)
    else:
        print("File non trovato!")
 
 
if __name__ == "__main__":
    avvia_file("out of the shadows", "D:\Musica") # Titolo della canzone e percorso in cui cercarla

Il modulo per interfacciarsi al bridge di Philips Hue connesso alla nostra rete locale LAN. Richiede l’installazione della libreria requests.

hue_bridge.py:

import requests
import json
 
def toggle_light(light, effect=False):
    light = str(light)
 
    r = requests.get("http://www.meethue.com/api/nupnp") # url for getting hue bridge local ip address
    if len(r.text) > 3:
        j = str(r.text).strip("[]") # convertiamo stringa in json valido
 
        json_list = json.loads(j) # mappiamo la stringa json in una lista
        #print(json_list['internalipaddress'])
 
        # controlliamo se la luce è accesa o spenta:
        r = requests.get("http://"+json_list['internalipaddress']+"/api/newdeveloper/lights/"+light+"/")
        l = str(r.text).strip("[]") # convertiamo stringa in json valido
        luce = json.loads(l) # mappiamo la stringa json in una lista
 
        # se accesa la spegniamo e viceversa
        stato = "false" if luce['state']['on'] else "true" # operatore ternario in python
        effetto = "colorloop" if effect else "none" 
 
        payload = '{"on": '+stato+', "effect": "'+effetto+'"}' # RICHIESTA JSON DA MANDARE
        #print(payload)
        url_luce = "http://"+json_list['internalipaddress']+"/api/newdeveloper/lights/"+light+"/state"
        #print(url_luce)
        r = requests.put(url_luce, data=payload)
        if r.status_code == 200: # in base allo status code analizziamo la risposta
            print("200 - Richiesta inviata!")
        else:
            print("Errore nella richiesta!")
    else:
        print("Nessuna connessione internet attiva o servizio UPNP di meethue.com assente")
 
 
if __name__ == "__main__": # se eseguito direttamente questo file
    toggle_light(1, effect=True)

E il codice che rimane in ascolto sulla porta seriale, in attesa di un segnale audio rilevato da arduino e opportunamente processato.

clapper_listener.py:

import serial
import os
import hue_bridge
import start_file
 
# works on python 3.2 (with requests module installed) 
 
def scan():
    """scan for available ports. return a list of tuples (num, name)"""
    available = []
    for i in range(256):
        try:
            s = serial.Serial(i)
            available.append( (i, s.portstr))
            s.close()   # explicit close 'cause of delayed GC in java
        except serial.SerialException:
            pass
    return available
 
def connessione_seriale(port):
    """ funzione per connessione alla porta seriale """
    try:
        return serial.Serial(port, 9600)
    except serial.SerialException:
        print("verificatosi ERRORE...")
 
 
porte = scan()
if len(porte): # porte seriali libere
   porta = porte[0][1] # ci connettiamo alla prima porta disponibile
 
   ser = connessione_seriale(porta)
 
   play = False
   # restiamo in attesa di un comando da seriale.
   while True:
    comando = ser.readline()
    #print(comando) # DEBUG
    if comando.decode() == "OK\r\n": # toggle della luce. (Philips HUE, comunicazione in JSON).
        print(comando)
       # hue_bridge.toggle_light(3, effect=True) # on/off light bulb number 3
        hue_bridge.toggle_light(1, effect=True) # on/off light bulb number 1
        if play:
            os.system('taskkill /f /im vlc.exe') # chiudiamo vlc attraverso un comando windows
            play = False
        else:
            start_file.avvia_file("out of the shadows", "D:\Musica") # start the file found in the specified path
            play = True
    comando = ""
 
else:
   print("Nessun dispositivo seriale connesso oppure porta seriale occupata!")
 
'''
print("Found ports:")
for n,s in scan():
   print("(%d) %s" % (n,s))
'''

Per qualsiasi problema commentate pure. Mi riprometto di postare al più presto un video dimostrativo.