in Informatica, Progetti, Tutorial, Tutorial Python

Python e Hobbystica – Una lampada accesa con un battito di mani

Questo periodo è stato pieno di impegni che, come spesso capita, mi hanno impedito di potere dedicare del tempo a hobby e passatempi, a cose di cui sono appassionato e che mi hanno fatto avvicinare originariamente al mondo dell’informatica ed alla programmazione, come Python. 

Una di queste è il perdersi in piccoli progetti auto-contenuti per realizzare applicazioni provando e sperimentando fino ad ottenere qualche risultato. Spesso, oltre al risultato, otteniamo qualcosa in più, come conoscenza e know-how. Qualche giorno fa ho sentito la necessità di riprendere questa attività per staccare dalla routine ed è proprio di questo processo di prototipazione al volo che voglio parlare.

Da un po’ di tempo mi ritrovo in casa una lampada smart, che uso tramite un’interfaccia che ho realizzato in Python/Flask come spesso ho discusso su antima in articoli come questo. Python è uno degli strumenti migliori per progetti come questi, grazie all’incredibile ecosistema di librerie a disposizione ed alla sua interattività e capacità di far sì che chiunque conosca le basi del linguaggio possa velocemente implementare una versione funzionante della sua idea.

Schema riassuntivo dell'interfaccia della lampadina

L’idea

L’idea che già da un po’ avevo voglia di realizzare era quella di potere accendere e spegnere la lampada tramite due battiti di mani; per prima cosa mi sono chiesto: come potrei catturare l’evento relativo ad un singolo battito?

L’esperienza proveniente da precedenti progetti mi ha ricordato dell’esistenza di una libreria Python per l’acquisizione di dati audio: PyAudio, che altro non è se non un insieme di bindings per PortAudio. Avevo utilizzato PyAudio insieme alla scheda audio AudioInjector per RaspberryPi e ho utilizzato lo stesso setup usando un Pi3 B+.

A questo punto ho iniziato a testare l’acquisizione di dati audio, per capire come il battito di mani fosse visto lato applicazione. Ho utilizzato la funzione audioop.rms dalla libreria audioop per avere una misura dell’intensità dell’evento audio ed ho annotato il valore relativo al mio battito di mani:

import pyaudio
import audioop

chunk = 2048

py = pyaudio.PyAudio()
stream = py.open(format=pyaudio.paInt16,
                channels=1,
                rate=48000,
                input=True,
                frames_per_buffer=chunk)

if __name__ == "__main__":
    while True:
        in_data = stream.read(chunk)
        rms_value = audioop.rms(in_data, 2) # paInt16 => data width = 2
        print(rms_value)

Una volta ottenuto il valore per cui si vuole far scattare il meccanismo di accensione, è stato necessario passare ad un approccio meno improvvisato per quanto riguarda il funzionamento dell’applicazione.

Ho deciso di acquisire i dati audio ed organizzarli in finestre d’analisi di dimensione fissata. Ho notato inoltre, che il primo battito di mani deve rappresentare uno stato che sia condizione necessaria all’accensione.

Mettendo un attimo da parte il criterio da usare per valutare se una finestra contiene un battito o meno, lo schema del funzionamento a cui sono arrivato è il seguente:

Diagramma di attività dell'applicazione per l'elaborazione audio

Con un po’ di test, dopo avere implementato l’idea della finestra, mi sono reso conto che nel mio caso andasse bene considerare una finestra come rappresentante un battito se al suo interno si trovano un numero di elementi tra 1 e 4 con valore superiore alla soglia.

Questo test è stato effettuato modificando il codice in questa maniera:

if __name__ == "__main__":
    window = []
    running = True

    while running:
        try:
            in_data = stream.read(chunk)
            rms_value = audioop.rms(in_data, 2) # paInt16 => data width = 2
            window.append(rms_value)

            if len(window) == window_size:
                print(window, lamp_state, first_clap)

Implementazione

Trascrivere il diagramma di attività in un’applicazione Python è piuttosto semplice a questo punto.

Prima di tutto, ho applicato quanto scoperto col test ed ho implementato una funzione per applicare il criterio di presenza di battito:

def clap(target_list, target_elem):
    th_count = [element > target_elem for element in target_list].count(True)
    return 1 <= th_count < 4

Dopodiché ho semplicemente seguito il diagramma passo passo per implementarne le funzioni descritte:

if len(window) == window_size:
    if not clap(window, clap_threshold):
        first_clap = False
        window.clear()
        continue
    
    if not first_clap:
        first_clap = True
        window.clear()
        continue
    
    lamp_state = not lamp_state
    Thread(target=requests.post, args=("http://localhost/status", {"open": lamp_state})).start()
    first_clap = False
    window.clear()

Nelle ultime righe si può vedere come alla ricezione di due battiti di fila lo stato della lampada venga invertito; ho utilizzato la libreria requests di Python3 per inviare una richiesta tramite HTTP POST al mio servizio di accensione luci per inoltrare il nuovo stato.

Conclusioni

Questo è un tipico progetto affrontabile nel tempo libero, in una o due sere, ma che apre la mente a molte idee interessanti. Per esempio si potrebbe migliorare o cambiare la modalità tramite cui i battiti vengono riconosciuti.

Inoltre, progetti come questi danno la possibilità di approcciarsi a librerie utilissime in molti contesti, come pyaudio o requests.

Potete trovare l’intero codice sul mio profilo github all’interno della seguente gist. Come accennato ad inizio articolo, ho fatto girare tutto sul mio fidato Raspberry Pi3 B+ con una scheda audio AudioInjector. Il setup del sistema non è lo scopo di questo articolo: vi basta però sapere che il tutto funziona tranquillamente su un normalissimo pc.

Potete usare questa applicazione come scheletro per fare eseguire comandi alla ricezione di un battito di mani. Basterà sostituire la chiamata POST con qualsiasi altro comando, modificando un po’ la logica.

La dimensione della finestra e la soglia andranno modificate in base a quanto vogliate che i battiti siano distanziati, alla sensibilità del vostro microfono, ecc.

Per questo articolo è tutto, per ogni chiarimento, dubbio o curiosità lasciate pure un commento nella sezione sottostante, al prossimo articolo!

Scrivi un commento

Commento