Una telecamera WiFi programmabile da pochi euro che, unita ad un sensore di movimento P.I.R. diventa uno strumento di sicurezza in grado di comunicare con i nostri smartphone attraverso Telegram
ESP32-Cam: la fotocamera economica per la videosorveglianza
Il tema della sicurezza e della videosorveglianza è sempre più discusso e attuale e rappresenta una materia di grande rilievo, soggetta a un continuo sviluppo grazie all’evoluzione tecnologica cui si assiste, specie in questi ultimi anni. Proprio l’affinamento e lo sviluppo delle tecnologie hanno permesso di applicare la videosorveglianza in numerosi settori e ambiti, fino alle più semplici situazioni quotidiane. Di fatti, oggi più che mai, la tecnologia mira a ridurre i costi e le dimensioni dell’impianto, così da ampliarne le modalità nonché i suoi campi di impiego; per non parlare del fatto che sono nate soluzioni basate sulle reti locali e su Internet, che hanno esteso notevolmente le possibilità applicative.
Cosa possiamo aspettarci, quindi dai nuovi sistemi? Sicuramente molto, dato che le nuove soluzioni ci permettono di utilizzare funzionalità maggiori, semplificate ed estremamente innovative. Un esempio di tutto ciò potrebbe essere una fotocamera economica, leggera, di dimensioni ridotte, già predisposta di WiFi e Bluetooth; questo è proprio quello che ci propone la AI-Thinker con la ESP32-Cam che con le sue dimensioni 40,5x27x4,5 potrebbe essere considerata il “coltellino svizzero” per ogni maker (Fig. 1).

Caratteristiche tecniche del modulo
Quest’ultima, successiva all’ormai nota e immancabile scheda basata su ESP8266, vede un modulo ESP32 dotato di un connettore per ospitare un modulo telecamera separato ed un alloggio per SD-Card fino ad un massimo di 4GB. Nel dettaglio, possiamo notare che il modulo ESP32-Cam si basa sull’ESP32, un microcontrollore programmabile con WiFi e Bluetooth integrati ed un’ulteriore RAM esterna di ben 4MB.
Non solo, il nuovo connettore per la fotocamera può ospitare un modulo OV2640 o OV7670: il primo viene fornito con il modulo stesso e presenta una risoluzione di 2 Megapixel.
Sulla scheda è presente anche un LED, ad alta luminosità, che può essere impiegato come flash o illuminatore della scena da riprendere. Naturalmente i numerosi GPIO presenti sulla scheda ci permettono eventualmente di montarne uno esterno nel caso di scarsa illuminazione. Per piccole applicazioni questo chip si presenta molto robusto e con una grande potenza di elaborazione, basandosi su due core di elaborazione a 32bit, operanti con clock a 120 MHz. Quest’ultima caratteristica le permette di avere un frame-rate abbastanza alto, ovviamente in funzione del formato e delle dimensioni: indicativamente si riesce ad arrivare ad un throughput fino a 8 JPEG in SVGA (800×600) al secondo.
Un sistema di controllo accessi
Vediamo ora come poter sfruttare ogni peculiarità di questo interessante dispositivo. In particolare le dimensioni, così ridotte da poter realizzare un sistema di controllo degli accessi anche all’interno di una sola stanza o di un garage. Tutto ciò acquisendo fotogrammi per ogni ingresso.
Ma dal punto di vista pratico vi potreste chiedere: cosa farne di queste foto? Quando controllarle? Esaminando i diversi livelli di difficoltà, al primo posto troviamo la necessita di acquisire un’immagine corretta e precisa.
Per fare ciò andremo a costruire una periferica in grado di condividere e segnalare in tempo reale questi accessi direttamente sui nostri smartphone o popolando un nostro cloud privato.
Principio di funzionamento
L’ESP32-Cam si occuperà di rilevare attraverso il sensore di movimento PIR (Fig. 3) l’accesso nello spazio sorvegliato e lo spostamento al suo interno.

Quando quest’ultimo viene attraversato, il PIR rileva l’irradiazione di calore da parte del corpo in movimento e viene triggerato; la sua uscita, che a riposo si trova a zero logico, si porterà quindi a livello alto attivando la camera, che andrà ad immagazzinare i byte in un puntatore prima NULL. In caso non si vada a popolare correttamente questa struttura interverrà la procedura di emergenza andando a resettare il nostro dispositivo.
Notificare l’intrusione: HTTP o MQTT
Rimane solo da capire come poter notificare all’esterno l’intrusione e che timing usare per poter scattare nuovamente. Per quanto riguarda la notifica abbiamo diverse soluzioni da poter sfruttare: per esempio le API http/htpps o il protocollo MQTT. Entrambe le soluzioni aprono diversi scenari e possono addirittura coesistere. Andiamo con ordine ed esaminiamo caso per caso.
1) API http/https: sono le classiche chiamate http (o in versione sicura https) POST o GET verso un server che si occuperà di prendere in carico la richiesta del caso; di questo tipo sono i servizi e le API di Telegram o servizi terzi per poter interagire con i nostri smartphone. Tale soluzione prevede una prima fase di connect verso il server remoto, se questa va a buon fine, il client, nel nostro caso il dispositivo, comincia ad inviare i byte della foto.
2) Protocollo MQTT: questo protocollo di recente pubblicazione è la base di molti oggetti di domotica che abbiamo in casa e prevede l’invio di semplici messaggi testuali, spesso incapsulati in JSON che vanno ad informare altri dispositivi smart.
Nel nostro esempio utilizzeremo le API http per comunicare con il server di Telegram al quale invieremo la nostra foto. Infine tareremo un delay finale per poter catturare una nuova foto.
Questo potrà essere anche nullo in caso si voglia ottenere una sequenza di foto ad alta intensità. Tale ritardo, come si vedrà nel codice, sarà molto importante per evitare di scattare foto a raffica. Naturalmente se a voi interessa invece effettuare riprese in rapida sequenza potete omettere tale ritardo.
Usare un pulsante al posto del PIR
Nel caso in cui non sia disponibile un sensore di movimento PIR (radar a infrarossi passivi), è possibile sostituirlo con un pulsante (Fig. 4) e quindi fornire il trigger manualmente; magari il contatto del pulsante potrà essere sostituito con uno a pedana, per fare riprese ogniqualvolta una persona mette i piedi in una certa zona, come ad esempio davanti a una porta.

Per quanto riguarda il sensore a infrarossi passivi, il circuito prevede che a “riposo” il segnale fornito sia a livello logico basso e che passi ad alto in caso di rilevamento.
Connettendo, invece, il pulsante di Fig. 4 direttamente alla linea riservata al PIR il comportamento sarà invertito se lo stesso sarà chiuso verso massa.
Collegamenti
Passiamo ora alla messa in funzione del modulo. Naturalmente prima creeremo il collegamento per la programmazione del firmware del ESP32-Cam attraverso Arduino. Questo passaggio può essere fatto anche attraverso un adattatore FTDI rendendo il cablaggio, almeno in fase di programmazione estremamente più facile.
Come primo passaggio identifichiamo i pin di TX e RX sul nostro Esp32-Cam, chiamati rispettivamente U0TXD e U0RXD e li andremo a collegare con i pin TX e RX dell’Arduino. Nel modulo della camera questi pin rappresentano anche i pin di GPIO1 e GPIO3.
Collegheremo i pin GND e GPIO0 di quest’ultimo a massa, insieme ai pin GND e RESET di Arduino e i due a 5V fra di loro. Per il collegamento possiamo aiutarci con una breadboard come quella in foto. Riassumendo in forma tabellare quanto espresso precedentemente si ha la situazione proposta nella Tabella 1.

Lo schema di cablaggio del circuito di programmazione è proposto nella Fig. 5.

Aggiungiamo ora il collegamento con il sensore PIR: oltre all’alimentazione, quindi i contatti 3V3 e GND da portare ai rispettivi VCC e GND del sensore, porteremo il cavo per il segnale di attuazione, partendo dal pin GPIO13 (Fig. 6) realizzando le seguenti connessioni:
Esp32->PIR
3V3->VCC
GND->GND
GPIO13->OUT
Una volta programmato il modulo ESP32-Cam, è necessario rimuovere il collegamento tra GPIO0 e GND. L’intero circuito si alimenta attraverso Arduino Uno, ossia fornendo a quest’ultimo tensione compresa fra 8 e 12V con un alimentatore stabilizzato dotato di plug DC idoneo a quello di Arduino; la corrente richiesta è dell’ordine dei 500 milliampere.

Firmware
Prima di procedere alla programmazione dovremmo impostare ArduinoIDE, ovvero il nostro ambiente di sviluppo, per poter scrivere correttamente sul modulo. Apriamo quindi l’IDE Arduino e andiamo in File->Impostazioni e aggiungiamo negli URL aggiuntivi per i gestori delle schede questo link https://dl.espressif.com/dl/package_esp32_index.json e facciamo clic su OK (Fig. 7).

Se si dispone già uno o più URL si usa la virgola per concatenarli, proprio come nell’esempio. Ora scarichiamo correttamente il gestore della scheda: rechiamoci in Strumenti-> Scheda: XXXX-> Gestore schede.. , cerchiamo “esp32” e installiamo l’unico risultato della ricerca (Fig. 8).

Uscendo selezioniamo nuovamente Strumenti-> Scheda: XXXX e noteremo una nuova voce ESP32 Arduino, la quale al suo interno ha diverse altre scelte. Selezioniamo ESP32 Wrover Module, allorché comparirà una nuova riga sempre in quel menu: Partition scheme, all’interno del quale andremo a scegliere Huge APP (Fig. 9).

Ora abbiamo tutto il necessario per poter caricare il firmware sul dispositivo, colleghiamo quindi l’USB e selezioniamo la porta corretta (ossia quella cui il sistema ci dice che è collegata la scheda) e poi clicchiamo Carica.
A questo punto abbiamo completato l’installazione e la configurazione dell’IDE, quindi possiamo caricare il firmware, che di seguito descriviamo.
Lo sketch
Passiamo quindi a vedere il codice relativo al firmware del progetto, che per brevità non riporteremo interamente in queste pagine, fermo restando che è disponibile per il download sul nostro sito www.elettronicain.it insieme ai file di questo numero della rivista.
Riportiamo alcuni estratti del firmware, esponendo nel Listato 1 la parte più sostanziosa, che è quella riguardante la routine per la gestione dell’invio dell’immagine ripresa dalla telecamera on-board tramite Telegram.
Listato 1
String alerts2Telegram(String token, String chat_id)
{
const char* myDomain = “api.telegram.org”;
String getAll=””, getBody = “”;
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if(!fb)
{
Serial.println(“Camera capture failed”);
delay(1000);
ESP.restart();
return “Camera capture failed”;
}
WiFiClientSecure client_tcp;
if (client_tcp.connect(myDomain, 443))
{
Serial.println(“Connected to “ + String(myDomain));
String head = “--India\r\nContent-Disposition: form-data; name=\”chat_id\”; \r\n\r\n” + chat_id + “\r\n--India\r\nContent-Disposition: form-data; name=\”photo\”; filename=\”esp32-cam.jpg\”\r\nContent-Type: image/jpeg\r\n\r\n”;
String tail = “\r\n--India--\r\n”;
uint16_t imageLen = fb->len;
uint16_t extraLen = head.length() + tail.length();
uint16_t totalLen = imageLen + extraLen;
client_tcp.println(“POST /bot”+token+”/sendPhoto HTTP/1.1”);
client_tcp.println(“Host: “ + String(myDomain));
client_tcp.println(“Content-Length: “ + String(totalLen));
client_tcp.println(“Content-Type: multipart/form-data; boundary=India”);
client_tcp.println();
client_tcp.print(head);
uint8_t *fbBuf = fb->buf;
size_t fbLen = fb->len;
for (size_t n=0;n<fbLen;n=n+1024)
{
if (n+1024<fbLen)
{
client_tcp.write(fbBuf, 1024);
fbBuf += 1024;
}
else if (fbLen%1024>0)
{
size_t remainder = fbLen%1024;
client_tcp.write(fbBuf, remainder);
}
}
client_tcp.print(tail);
esp_camera_fb_return(fb);
int waitTime = 10000; // timeout 10 seconds
long startTime = millis();
boolean state = false;
while ((startTime + waitTime) > millis())
{
Serial.print(“.”);
delay(100);
while (client_tcp.available())
{
char c = client_tcp.read();
if (c == ‘\n’)
{
if (getAll.length()==0) state=true;
getAll = “”;
}
else if (c != ‘\r’)
getAll += String(c);
if (state==true) getBody += String(c);
startTime = millis();
}
if (getBody.length()>0) break;
}
client_tcp.stop();
Serial.println(getBody);
}
else {
getBody = “Connection to telegram failed.”;
Serial.println(“Connection to telegram failed.”);
}
return getBody;
}Qui di seguito riportiamo la prima porzione di codice che riguarda la definizione dei pin utilizzati per gestire la telecamera: notate che tutti i pin sono collegati “internamente” sulla board ad eccezione del pin 13, esterno, cui è stato collegato il sensore di movimento PIR. Tale definizione è gestita da queste linee di codice:
#define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 int gpioPIR = 13; //PIR Motion Sensor
Nel ciclo di loop viene verificato lo stato del PIR, quando l’uscita del sensore va alta (1) viene chiamata la routine “alerts2Telegram”:
void loop()
{
pinMode(gpioPIR, INPUT_PULLUP);
int v = digitalRead(gpioPIR);
if (v==1){
alerts2Telegram(token, chat_id);
delay(5000);
}
delay(500);
}Come si nota dai commenti nel codice, le prime quattro costanti devono essere cambiate rispettivamente con le proprie credenziali del WiFi e altri due parametri forniti invece da Telegram.
Configurare il bot Telegram con BotFather
Recuperiamo quindi questi ultimi due valori. Apriamo l’applicazione Telegram sul nostro smartphone e clicchiamo sulla lente per ricercare BotFather (Fig. 10).

Attraverso questa chat potremmo creare il nostro primo bot, ovvero un utente non umano capace di svolgere azioni più o meno complesse; se preferite, un robot… Apriamo quindi la chat di BotFather, digitiamo /newbot e seguiamo le indicazioni a video.
Alla fine della procedura ci verrà rilasciato il token da sostituire nella nostra terza costante, ovvero token (Fig. 11).

Procuriamoci ora l’ultima costante, clicchiamo quindi nuovamente sulla lente e ricerchiamo il bot appena creato: una volta trovato ci basterà aprirlo e premere su Avvia. Sarebbe inoltre opportuno scrivere dentro a questa chat qualcosa ed inviare. Nel nostro caso scriviamo anche solo “Ciao” e inviamo il messaggio.
Apriamo ora una sessione del browser e incolliamo il seguente link:
https://api.telegram.org/bot<BOTToken>/getUpdates
dove BOTToken è esattamente il token estratto al punto precedente. In risposta a questa azione, sul browser apparirà un json, ovvero una struttura dati formattata in un determinato modo, simile alla seguente:
{
“ok”:true,
“result”:[
{
“update_id”:YYYYYY,
“message”:{
“message_id”:18,
“from”:{
“id”:XXXXXXXX,
“is_bot”:false,
“first_name”:”NOME COMPLETO TELEGRAM“,
“username”:”IL TUO USERNAME TELEGRAM“,
“language_code”:”it”
},
“chat”:{
“id”: XXXXXXXX,
“first_name”:”NOME COMPLETO TELEGRAM”,
“username”:”IL TUO USERNAME TELEGRAM”,
“type”:”private”
},
“date”:1600324371,
“text”:”Ciao”
}
}
]
}Questa struttura sono le interazioni con il bot da parte di utenti. A noi interessa l’id della chat dove poco fa abbiamo inviato “Ciao”, ovvero quello in grassetto nell’esempio. Questo ID rappresenta esattamente l’ultimo pezzo di cui avevamo bisogno da sostituire nel codice a chat_id.
Alternativa controllo allarme
Ora che siamo riusciti ad avere una soluzione base per il controllo degli accessi sarebbe opportuno avere un controllo di questa da remoto, non solo una mera visualizzazione. Sarebbe utile riuscire ad azionare questo controllo senza il bisogno di essere fisicamente li per accendere e spegnere il dispositivo.
Per fare questo è basilare disporre di un server web. Può essere uno spazio noleggiato attraverso servizi come Aruba, Register o altri servizi simili, oppure semplicemente un Raspberry Pi connesso alla rete e con una base LAMP, ovvero una soluzione che vede una base Unix/Linux, un motore http server ed un linguaggio PHP per poter scrivere gli script.
In realtà LAMP sarebbe l’acronimo dei suoi componenti e prevederebbe il sistema operativo Linux, Apache (motore http server), MySql (motore database) e infine il linguaggio PHP/Python. Nel nostro caso il database è superfluo ma potrebbe essere una soluzione più corretta ed avanzata.
All’interno del nostro server metteremo tre file: lock.php, lock.txt ed infine read.php. Il primo è il servizio “scrittore”, ovvero quello che andrà a settare lo stato del nostro device. Qui di seguito il codice:
<?php echo getcwd(); $myfile = fopen(“lock.txt”, “w”) or die(“Unable to open file!”); $txt = $_GET[‘status’]; fwrite($myfile, $txt); fclose($myfile); ?>
Il secondo è il file, che useremo come database, il quale contiene lo stato del nostro device: è semplicemente un file di testo nel quale inserire lo stato “on” oppure “off”. Per impostazione predefinita setteremo “on”.
Ed infine avremo il servizio “lettore”, ovvero quello che inserito dentro al codice del nostro ESP32 andrà a leggere lo stato remoto e azionerà o meno le notifiche. Di seguito il codice di quest’ultimo:
<?php $myfile = fopen(“lock.txt”, “r”) or die(“Unable to open file!”); echo fread($myfile,filesize(“lock.txt”)); fclose($myfile); ?>
Aggiornare lo sketch per il controllo remoto
Viene quindi da chiedersi come cambierà il codice del nostro ESP32Cam; ebbene, davvero poco, perché infatti dovremo semplicemente aggiungere un nuovo controllo oltre a quello già presente della connessione a Telegram.
Qui infatti dovremmo inserire una chiamata al servizio di lettura in modo tale da capire in quale stato ci troviamo e se la foto appena scattata è di nostro interesse o meno.
Vediamo come modificarlo: nella parte in alto del codice dove vi sono i vari include dovremmo aggiungere questa riga:
#include <HTTPClient.h>
Ottenendo quindi:
#include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> #include “soc/soc.h” #include “soc/rtc_cntl_reg.h” #include “esp_camera.h”
Ora aggiungiamo il controllo a fianco della connessione a Telegram. All’interno della funzione alerts2Telegram infatti aggiungeremo questa istruzione:
&& control()
ottenendo quindi:
String alerts2Telegram(String token, String chat_id)
{
const char* myDomain = “api.telegram.org”;
String getAll=””, getBody = “”;
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if(!fb)
{
Serial.println(“Camera capture failed”);
delay(1000);
ESP.restart();
return “Camera capture failed”;
}
WiFiClientSecure client_tcp;
if (client_tcp.connect(myDomain, 443) && control())
{
Serial.println(“Connected to “+String(myDomain));Infine creiamo quindi la funzione control citata al passo precedente e la inseriamo in fondo a tutto il codice:
boolean control(){
HTTPClient http;
boolean ret = false;
String serverPath = “http://SERVER_ADDRESS/read.php”;
http.begin(serverPath.c_str());
int httpResponseCode = http.GET();
if (httpResponseCode>0) {
Serial.print(“HTTP Response code: “);
Serial.println(httpResponseCode);
String payload = http.getString();
Serial.println(payload);
if(payload==”on”){
ret = true;
}
}
else {
Serial.print(“Error code: “);
Serial.println(httpResponseCode);
}
http.end();
return ret;
}Come potete notare, con poche righe aggiuntive abbiamo quindi remotizzato il controllo del dispositivo.
Ora ci basterà invocare, attraverso un normale browser, la seguente URL:
http:// SERVER_ADDRESS /lock.php?status=on
Per attivare, mentre invece per disattivare andremo a cambiare il valore status da on ad off.
Conclusioni
Bene, terminiamo qui la descrizione di questo progetto dedicato alla videosorveglianza assistita da Telegram; quelle che vi abbiamo dato sono idee di sviluppo personalizzabili secondo le vostre esigenze a partire dal firmware e dall’hardware di base, che come avete visto è molto semplice, essendo ridotto praticamente al modulo ESP32-Cam e ad un sensore di presenza.


