Hue Clapper v1.0 - Philips Hue & Arduino
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:
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.
