Videocitofono Smart DIY con un Raspberry ed Home Assistant

Videocitofono Smart DIY con un Raspberry ed Home Assistant

di Luigi Duchi

25 Dicembre 2021

Fai da te

Luigi Duchi

 Come promesso stiamo cercando di incrementare i progetti DIY (fai da te) che pubblichiamo sul nostro Blog e, anche se questo mi ha fatto passare diverse notti insonni, sono felicissimo di condividere con voi il mio lavoro.

Oggi parliamo di un vero videocitofono smart, costruito con un Raspberry.

raspberry

Il tutto integrato in Home Assistant in modo da ricevere notifiche direttamente sul nostro smartphone oltre ovviamente a poter vedere e parlare con chi ci ha suonato, il tutto sia sotto connessione WIFI che sotto connessione dati mobili. 

A tal proposto vi invito a fare riferimento alla nostra raccolta di articoli e guide dedicate proprio al Personal Hub Home Assistant al seguente link:

https://www.vincenzocaputo.com/home_assistant

Il progetto descritto di seguito, si basa su un progetto del MIT a cui abbiamo fatto riferimento ed al quale abbiamo apportato diverse modifiche e personalizzazioni.

Ma, prima di iniziare a parlare di questo progetto, voglio avvertirvi che i passaggi da replicare non sono pochi, quindi cercate di seguirli tutti molto attentamente.

Lista della spesa

Iniziamo con il vedere il materiale necessario per la realizzazione diq uesto progetto. A tal proposito vi suggerirò anche cosa non comprare e anche cosa comprare per migliorare il progetto:

Un Raspberry: ho iniziato il progetto con un pi3 b che avevo in casa ma ho dovuto ripiegare su un pi4 perché il progetto richiede una certa potenza di calcolo (soprattutto di gpu) e il 3b era veramente insufficiente (suppongo che il progetto possa funzionare discretamente con un 3b+ ma io avevo a disposizione solo un 3b o un 4).

Diciamo che non è un buon periodo per comprare un Raspberry, anzi diciamo pure il peggiore della storia, mai visti prezzi cosi alti, comunque nel caso un pi4 da 2gb è più che sufficiente, se ne avete uno da 4 è ancora meglio.

Vi segnalo un link QUI (ma sinceramente mi auguro che ne abbiate uno che vi avanza)

Una videocamera con tanto di led IR che potrete acquistare QUI

telecamera

Un pulsante alimentabile a 3,3v con due contatti come questo QUI

pulsante

Una scheda Mic+ dell'azienda raspiaudio.com che in genere si acquista su Amazon ma, nel momento in cui sto scrivendo questa guida, risulta esaurito.

mic+

Per quanto riguarda l'alimentazione avete due strade: o portate un cavo di rete e un alimentazione da un alimentatore esterno, oppure potrete portare alimentazione tramite il protocollo PoE magari prendendo alimentazione da uno switch PoE o da un PoE injector.

Non fate lo stesso errore che ho fatto io inizialmente: il Raspberry dovrà alimentare tramite le gpio la scheda mic+ che a sua volta dovrà alimentare il pulsanteIl Raspberry dovrà inoltre alimentare il modulo videocamera, di conseguenza il PoE splitter che avevo acquistato da 2,4A non è stato sufficiente. In vendita ne esistono anche di modelli da 4 Ampere, ve lo linko ma non l'ho testato in quanto alla fine ho optato per alimentazione separata. Potrete acquistarlo comunque QUI

poe splitter

- in alternativa avrete bisogno di un alimentatore usb c da 5V

- eventualmente, se vi dovessero mancare, prendete dei jumper che potrete acquistare QUI

jumper

- il pi4 è noto per scaldare abbastanza, il mio consiglio è quello di prendere un case in alluminio (in questo caso senza ventole, dato che il rumore di quest'ultima potrebbe rientrare nel microfono). Vi consiglio questo modello QUI che, incluso nella confezione, ha anche le prolunghe per le gpio.

 

case raspberry

Installazione sistema operativo

 Da un po' di tempo a questa parte Raspberry ha rilasciato un suo Tool per flashare sulla SD i suoi sistemi operativi (e non solo) che di fatto pensiona il famosissimo Balena Etcher.

Tra le altre cose questo Tool permette di impostare molti parametri prima ancora del flash. Si potrà ad esempio abilitare SSH, la rete WIFI e l'hostname che dovrà mostrare il vostro Raspberry.

Di fatto avrete molti meno passaggi iniziali una volta avviato per la prima volta il vostro Raspberry.

Per scaricare il TOOL vi dovrete recare nella pagina ufficiale Raspberry OS che trovate QUI e scaricare la versione per il sistema operativo che utilizzate.

pagina ufficiale download raspberry

Una volta aperto il Raspberry Pi imager (questo è il nome del tool) dovrete cliccare su SCEGLI SO

tool raspberry

Successivamente dovrete cliccare su Raspberry PI OS (other)

schermata 1

Dovrete selezionare Raspberry PI OS Lite (Legacy) 

schermata 2

Inutile dirvi che dovrete precedentemente aver inserito una microSD all'interno del vostro computer magari tramite un adattatore usb.

Cliccate quindi su SCEGLI SCHEDA e selezionate la scheda che avete inserito.

Puntate il mouse sulla pagina del tool e cliccateci sopra con il tasto sinistro. Dopodiché utilizzate la combinazione sulla tastiera CTRL+ALT+x per aprire il menù opzioni avanzate.

schermata 3

Spuntate la casella imposta nome host e date un nome di vostro gradimento, io ad esempio ho utilizzato rasberrycitofono.local

Spuntate anche la casella Abilita SSH e impostate una password di vostro gradimento per l'utente pi.

schermata 4

Opzionalmente potrete anche spuntare l'abilitazione della rete wifi, ma vi consiglio vivamente di connettere il Raspberry tramite cavo ethernet per una maggiore stabilità e prestazione del segnale.

Se optate per la rete WiFi, una volta spuntata la casella, dovrete inserire la vostra SSID e relativa password, oltre a selezionare IT alla voce nazione WiFi.

Spuntate anche la voce imposta configurazioni locali e selezionate Europe/Rome come fuso orario e it come Layout della tastiera.

schermata 5

Infine cliccate su SALVA.

A questo punto siete pronti pere scrivere la vostra microSD, lo potrete fare cliccando appunto il pulsante SCRIVI

Iniziamo il montaggio

 Allora, come vi ho anticipato, vi faccio vedere i collegamenti dei vari dispositivi in maniera molto spartana. Ometterò il PoE splitter ed il case di alluminio per far capire meglio i vari passaggi. Ovviamente sono altamente consigliati, come vi dicevo durante la lista del materiale da acquistare, anche per raffreddare il pi 4 che, senza dissipazione, scalda non poco.

La prima cosa da fare sarà quella di fissare il modulo mic+ sulle gpio del Raspberry. Non dovreste trovare difficoltà in quanto il modulo occuperà tutte le gpio replicandole nella parte superiore.

mic+ montato

 Successivamente sarà necessario montare la cam che arriverà in due parti.

Perciò vi dovrete occupare di fissare i led ir al corpo principale mediante le 4 vitine incluse nella confezione.

dettaglio videocamera

Una volta montata, dovrete inserire il flat all'interno dell'apposita sede.

flat videocamera

Prestando attenzione al verso di montaggio: la parte blu dovrà essere in direzione delle prese usb del Raspberry.

videocamera montata

Infine dovrete saldare 4 jumper al pulsante. Vi consiglio di lasciare dal lato delle gpio la femmina del jumper e tagliare l'altra estremità saldando ai 4 pin del pulsante come nella seguente foto

pulsante montato

I pin che sono presenti sul pulsante sono + (cavo rosso) - (cavo nero) e i due contatti ai quali ho saldato rispettivamente il filo arancione e il filo marrone.

Per quanto riguarda i collegamenti alle gpio vi lascio la foto sottostante.

gpio

Collegate i jumper alle gpio evidenziate con le relative x del colore del jumper.

Ovviamente il modulo mic+ coprirà le gpio del Raspberry, quindi in realtà i jumper dovrete collegarli alle gpio del modulo mic+ che sono esattamente identiche a quelle del Raspberry al quale il modulo è collegato.

Inserite la microSD, precedentemente flashata con il Raspberry PI imager, dentro il Raspberry e, una volta connesso anche il cavo di rete, alimentate il dispositivo.

Configurazione

 Appena acceso il dispositivo vi dovrete collegare in SSH (questa versione di Raspberry OS non ha interfaccia grafica, ciò la rende molto meno pesante e soprattutto a noi, per questa guida, non serve l'interfaccia grafica).

Andate nel vostro router, controllate quale indirizzo ip è stato assegnato al vostro Raspberry.

Lo dovreste trovare facilmente perché, come nome esposto, dovrebbe avere proprio l'hostname che avete configurato prima di flashare la microSD (nel mio caso ricorderete essere raspberrypicitofono). Vi consiglio di prenotare l'indirizzo ip del vostro Raspberry in modo che il router gli assegni sempre lo stesso.

Per connettervi tramite SSH al Raspberry in ambiente Windows ci viene in aiuto il noto software gratuito Putty. Se non lo avete installato nel vostro pc (oltre a dovervi vergognare e frustarvi con un cilicio sulla schiena per cercare redenzione) andate a scaricarlo ed installarlo (https://www.putty.org/).

Una volta aperto, semplicemente digitando l'indirizzo ip su Putty, accederete ad una pagina di terminale dove vi verrà chiesto la username del Raspberry (che è pi) e successivamente la vostra password che è quella scelta in fase di abilitazione dell'SSH prima di flashare la microSD dal tool di Raspberry.

Non vi preoccupate se la password non compare mentre digitate, in realtà recepisce lo stesso i caratteri quindi, una volta digitata la password, premete invio.

In ambiente Mac (o Linux) non dovrete scaricare alcun software per connettervi tramite ssh vi basterà aprire un terminale e digitare ssh [email protected] ip del vostro pi ad esempio ssh [email protected]

In questo caso sarà richiesta solo la password.

Da qui in poi le differenze tra Windows e Mac non ci saranno più.

Ora dovranno essere lanciati diversi comandi da terminale, quindi prestate la massima attenzione.

Iniziamo con configurare la scheda mic+ lanciando il seguente comando

sudo wget -O - mic.raspiaudio.com | sudo bash

 Questo installerà tutto il necessario per far lavorare la scheda mic+. Al termine dell'installazione vi chiederà di riavviare, ovviamente accettate.

Se non dovesse chiedervelo, lanciate il comando

sudo reboot

Una volta riavviato il Raspberry dovrete nuovamente entrare tramite SSH con la procedura elencata poco sopra.

Per testare che l'installazione sia andata a buon fine digitate il seguente comando. 

sudo wget -O - test.raspiaudio.com | sudo bash

Dopo che avrete digitato questo comando noterete che il pulsante quadrato giallo sopra la scheda mic+ si illuminerà.

Premetelo e attendete che la procedura di test arrivi a buon fine.

Il Raspberry pronuncerà "front left front right" e successivamente riprodurrà la registrazione ad indicare che sia altoparlante che microfono funzionano. Non vi preoccupate se sentite un po' di riverbero, in questa fase è normale.

Comunque potrete sempre regolare i livelli dello speaker e degli altoparlanti mediante il comando

alsamixer
alsamixer

Regolate i livelli come preferite e poi premete esc quando avrete terminato.

La parte riguardante la scheda audio è terminata. 

Digitate adesso il comando 

sudo rpi-update

Il vostro Raspberry inizierà ad aggiornarsi, confermate ogni volta che ve lo chiederà. 

Al termine della procedura digitate nuovamente il comando 

sudo reboot

attendete il riavvio e ri-loggatevi su SSH.

Digitate ora il comando 

sudo raspi-config

vi troverete di fronte a questa schermata

raspi-config 1

Qui dovrete fare diverse operazioni tra cui assegnare maggiore memoria alla GPU.

Selezionate la voce 4 performance options e premete su invio.

raspi-config 2

Qui dovrete selezionare la voce p2 GPU memory e premere invio.

scrivete 256

Una volta impostata vi potrebbe chiedere di riavviare se premete su finish, fatelo e tornate, una volta riavviato, a lanciare il comando 

raspi-config 2

naturalmente dopo esservi ri-collegati sul Raspberry tramite SSH.

Questa volta vi dovrete recare alla voce n.3 interface options e premere invio

Successivamente p1 camera e premere invio. Abilitate la camera e, anche in questo caso, all'uscita alla fine della procedura vi chiederà di riavviare, fatelo! 

Una volta che avrà eseguito il riavvio e sarete rientrati tramite SSH lanciate il seguente comando

curl https://www.linux-projects.org/listing/uv4l_repo/lpkey.asc | sudo apt-key add - 

successivamente lancia il comando 

cd

e poi lanciate il comando 

sudo nano /etc/apt/sources.list

nel file che si apre copiate questa stringa

deb http://www.linux-projects.org/listing/uv4l_repo/raspbian/stretch stretch main

esattamente come in foto

aggiungere deb

salvate premendo ctrl+x poi premeree successivamente invio.

lanciate il comando 

sudo apt-get update

ora dovrete lanciare una serie di comandi uno dietro un altro ovvero appena finisce di installare il primo lanciate il secondo e cosi via.

sudo apt-get install uv4l uv4l-raspicam

sudo apt-get install uv4l-raspicam-extras uv4l-server uv4l-mjpegstream uv4l-demos uv4l-xmpp-bridge

sudo apt-get install uv4l-webrtc

adesso riavviate il servizio con il seguente comando.

sudo service uv4l_raspicam restart

Ora aprite il browser, anzi aprite Chrome (questo progetto funziona solo su Chrome).

Digitate http://ipdelvostroraspberry:8080 ad esempio http://192.168.1.118:8080

se tutto è andato a buon fine vi dovrebbe comparire questa pagina.

uv4l

 Selezionando la voce Two Way audio/video e spuntando queste caselle dovreste già essere in grado di riprodurre l'audio a due vie e vedere la videocamera (una volta che avrete premuto il pulsante verde).

parametri uv4l

Benissimo questo è solo un primo step della guida, adesso serve avere i certificati SSL.

Li andremo ad abilitare con lets'encrypt, ma prima dovremo preoccuparci di creare un ddns su duckdns sul quale abilitare i certificati.

Recatevi quindi su duckdns (https://www.duckdns.org/) create un account (o entrate con Google) e create un dominio. Vi ricordo che il servizio di dyndns funzionerà se la vostra connessione internet è munita di un ip pubblico. 

Una volta creato un dominio che per comodità ho chiamato come esempio

citofono.duckdns.org

(ovviamente create il nome che più vi aggrada)

tornate sul terminale del nostro pi e digitate

cd

e successivamente 

sudo apt-get install python-certbot-apache

Una volta che Certbot risulterà installato potrete procedere con l’acquisizione di un certificato SSL per il vostro Raspberry Pi da Let’s Encrypt.

Prima di proseguire, dobbiamo prima assicurarci che sul nostro Router le porta 80 e 443 siano “nattate” (port forwarding) sull’IP interno del nostro Raspberry Pi.

Non vi spiegherò come fare perché la procedura varia da router a router ma di fatto, per generare i certificati, dovrete aprire e indirizzare la porta 80 e la porta 443 sull'ip del vostro Raspberry. Dato che ci siete aprite pure le porte 8080 e 8888, è molto probabile che aprendo queste ultime due porte il router esternamente vi assegni delle porte diverse prendetene nota, ad esempio potrete avere la porta 8080 internamente ma esternamente potreste avere aperta la porta 5678 (5678 è un numero puramente casuale).

Una volta aperte le porte potrete lanciare questo comando.

sudo certbot --apache

Rispondete a tutte le domande poste ed inserite la mail quando vi verrà chiesto e il vostro dominio duckdns quando ve lo chiederà.

I certificati verranno salvati a questo percorso

/etc/letsencrypt/live/citofono.duckdns.org/ (ovviamente come dominio ci sarà il vostro)

All'interno di queste cartelle troverete sia il file della catena completa (fullchain.pem) che il file della chiave privata del certificato (privkey.pem). Questi sono i file che mantengono sicura la tua connessione SSL e la identificano come connessione legittima.

Questi certificati hanno una validità limitata (3 mesi) tuttavia lo script sopra crea uno script in crontab che permette l'auto-rinnovo del certificato. Se dovesse fallire l'auto rinnovo ogni 3 mesi potrete lanciare il comando. 

sudo certbot renew

per il rinnovo manuale.

Potrete chiudere la porta 80, ma comunque è necessaria tenerla aperta per il rinnovo dei certificati, casomai apritela in quel frangente per poi richiuderla.

Ci siete sempre? Dai che la strada è ancora abbastanza lunga, ma siamo già a buon punto.

Lanciate il comando

cd

Successivamente lanciate il comando 

sudo nano /etc/uv4l/uv4l-raspicam.conf

Si aprirà un file e dovrete modificarlo come nelle foto sottostanti

file di configurazione 1
file di configurazione 2
file di configurazione 3

Dunque, per quanto riguarda i certificati, dovrete mettere il vostro percorso che, se avete seguito alla lettera, sarà nello stesso percorso dell'esempio (dovrete sostituire il vostro dominio duckdns)

Vi consiglio, verso la fine del file, di selezionare anche la password in modo che chiunque provi ad entrare si ritroverà una username (che di default è www) e una password (che sarà impostata da voi) da scrivere per accedere al vostro citofono.

Salvate con ctrl+x successivamente confermate con y e poi premete il tasto enter.

una volta usciti digitate il seguente comando e poi premete invio

cd

Adesso digitate il seguente comando

cd /usr/share/uv4l/demos

successivamente digitiate il comando per creare la cartella doorpi

sudo mkdir doorpi

e poi per andare in quella cartella questo comando

cd doorpi/

lanciate il comando 

sudo nano signalling.js

nel file che si apre copiate il contenuto qui sotto.

/*
 * window.mozRTCPeerConnection, window.mozRTCSessionDescription, window.mozRTCIceCandidate are now deprecated
 */

RTCPeerConnection = window.RTCPeerConnection || /*window.mozRTCPeerConnection ||*/ window.webkitRTCPeerConnection;
RTCSessionDescription = /*window.mozRTCSessionDescription ||*/ window.RTCSessionDescription;
RTCIceCandidate = /*window.mozRTCIceCandidate ||*/ window.RTCIceCandidate;

function signal(url, stream, onStream, onError, onClose, onMessage) {
    if ("WebSocket" in window) {
        console.log("opening web socket: " + url);
        var ws = new WebSocket(url);
        var pc;
        var iceCandidates = [];
        var hasRemoteDesc = false;

        function addIceCandidates() {
            if (hasRemoteDesc) {
                iceCandidates.forEach(function (candidate) {
                    pc.addIceCandidate(candidate,
                        function () {
                            console.log("IceCandidate added: " + JSON.stringify(candidate));
                        },
                        function (error) {
                            console.error("addIceCandidate error: " + error);
                        }
                    );
                });
                iceCandidates = [];
            }
        }

        ws.onopen = function () {
            /* First we create a peer connection */
            var config = {"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]};
            var options = {optional: []};
            pc = new RTCPeerConnection(config, options);
            iceCandidates = [];
            hasRemoteDesc = false;

            pc.onicecandidate = function (event) {
                if (event.candidate) {
                    var candidate = {
                        sdpMLineIndex: event.candidate.sdpMLineIndex,
                        sdpMid: event.candidate.sdpMid,
                        candidate: event.candidate.candidate
                    };
                    var request = {
                        what: "addIceCandidate",
                        data: JSON.stringify(candidate)
                    };
                    ws.send(JSON.stringify(request));
                } else {
                    console.log("end of candidates.");
                }
            };

            if ('ontrack' in pc) {
                pc.ontrack = function (event) {
                    onStream(event.streams[0]);
                };
            } else {  // onaddstream() deprecated
                pc.onaddstream = function (event) {
                    onStream(event.stream);
                };
            }

            pc.onremovestream = function (event) {
                console.log("the stream has been removed: do your stuff now");
            };

            pc.ondatachannel = function (event) {
                console.log("a data channel is available: do your stuff with it");
                // For an example, see https://www.linux-projects.org/uv4l/tutorials/webrtc-data-channels/
            };

            if (stream) {
                pc.addStream(stream);
            }

            /* kindly signal the remote peer that we would like to initiate a call */
            var request = {
                what: "call",
                options: {
                    // If forced, the hardware codec depends on the arch.
                    // (e.g. it's H264 on the Raspberry Pi)
                    // Make sure the browser supports the codec too.
                    force_hw_vcodec: false,
                    vformat: 30, /* 30=640x480, 30 fps */
                    trickle_ice: true
                }
            };
            console.log("send message " + JSON.stringify(request));
            ws.send(JSON.stringify(request));
        };

        ws.onmessage = function (evt) {
            var msg = JSON.parse(evt.data);
            var what = msg.what;
            var data = msg.data;

            console.log("received message " + JSON.stringify(msg));

            switch (what) {
                case "offer":
                    var mediaConstraints = {
                        optional: [],
                        mandatory: {
                            OfferToReceiveAudio: true,
                            OfferToReceiveVideo: true
                        }
                    };
                    pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)),
                            function onRemoteSdpSuccess() {
                                hasRemoteDesc = true;
                                addIceCandidates();
                                pc.createAnswer(function (sessionDescription) {
                                    pc.setLocalDescription(sessionDescription);
                                    var request = {
                                        what: "answer",
                                        data: JSON.stringify(sessionDescription)
                                    };
                                    ws.send(JSON.stringify(request));
                                }, function (error) {
                                    onError("failed to create answer: " + error);
                                }, mediaConstraints);
                            },
                            function onRemoteSdpError(event) {
                                onError('failed to set the remote description: ' + event);
                                ws.close();
                            }
                    );

                    break;

                case "answer":
                    break;

                case "message":
                    if (onMessage) {
                        onMessage(msg.data);
                    }
                    break;

                case "iceCandidate": // received when trickle ice is used (see the "call" request)
                    if (!msg.data) {
                        console.log("Ice Gathering Complete");
                        break;
                    }
                    var elt = JSON.parse(msg.data);
                    let candidate = new RTCIceCandidate({sdpMLineIndex: elt.sdpMLineIndex, candidate: elt.candidate});
                    iceCandidates.push(candidate);
                    addIceCandidates(); // it internally checks if the remote description has been set
                    break;

                case "iceCandidates": // received when trickle ice is NOT used (see the "call" request)
                    var candidates = JSON.parse(msg.data);
                    for (var i = 0; candidates && i < candidates.length; i++) {
                        var elt = candidates[i];
                        let candidate = new RTCIceCandidate({sdpMLineIndex: elt.sdpMLineIndex, candidate: elt.candidate});
                        iceCandidates.push(candidate);
                    }
                    addIceCandidates();
                    break;
            }
        };

        ws.onclose = function (event) {
            console.log('socket closed with code: ' + event.code);
            if (pc) {
                pc.close();
                pc = null;
                ws = null;
            }
            if (onClose) {
                onClose();
            }
        };

        ws.onerror = function (event) {
            onError("An error has occurred on the websocket (make sure the address is correct)!");
        };

        this.hangup = function() {
            if (ws) {
                var request = {
                    what: "hangup"
                };
                console.log("send message " + JSON.stringify(request));
                ws.send(JSON.stringify(request));
            }
        };

    } else {
        onError("Sorry, this browser does not support Web Sockets. Bye.");
    }

salvate con ctrl+x confermate con y e premete invio

Adesso digitate questo comando.

sudo nano main.js

Nel file che si andrà aprire copiate il contenuto qua sotto.

(function () {
    var signalObj = null;

    window.addEventListener('DOMContentLoaded', function () {
        var isStreaming = false;
        var start = document.getElementById('start');
        var stop = document.getElementById('stop');
        var video = document.getElementById('v');

        var audio_video_stream = null;

        start.addEventListener('click', function (e) {
            var address = document.getElementById('address').value;
            var protocol = location.protocol === "https:" ? "wss:" : "ws:";
            var wsurl = protocol + '//' + address;

            var isFirefox = typeof InstallTrigger !== 'undefined';// Firefox 1.0+
            var localConstraints = {};
            localConstraints['audio'] = isFirefox ? {echoCancellation: true} : {optional: [{echoCancellation: true}]};
            if (navigator.getUserMedia) {
                navigator.getUserMedia(localConstraints, function (stream) {
                    audio_video_stream = stream;
                    if (!isStreaming) {
                        signalObj = new signal(wsurl,
                                audio_video_stream,
                                function (stream) {
                                    console.log('got a stream!');
                                    //var url = window.URL || window.webkitURL;
                                    //video.src = url ? url.createObjectURL(stream) : stream; // deprecated
                                    video.srcObject = stream;
                                    video.play();
                                },
                                function (error) {
                                    alert(error);
                                },
                                function () {
                                    console.log('websocket closed. bye bye!');
                                    video.srcObject = null;
                                    isStreaming = false;
                                },
                                function (message) {
                                    alert(message);
                                }
                        );
                    }
                }, function (error) {
                    alert("An error has occurred. Check media device, permissions on media and origin.");
                    console.error(error);
                    stop();
                });
            } else {
                console.log("getUserMedia not supported");
            }
        }, false);

        stop.addEventListener('click', function (e) {
            if (signalObj) {
                signalObj.hangup();
                signalObj = null;
            }
            if (audio_video_stream) {
                try {
                    if (audio_video_stream.getVideoTracks().length)
                        audio_video_stream.getVideoTracks()[0].stop();
                    if (audio_video_stream.getAudioTracks().length)
                        audio_video_stream.getAudioTracks()[0].stop();
                    audio_video_stream.stop(); // deprecated
                } catch (e) {
                    for (var i = 0; i < audio_video_stream.getTracks().length; i++)
                        audio_video_stream.getTracks()[i].stop();
                }
                audio_video_stream = null;
            }
        }, false);

        // Wait until the video stream can play
        video.addEventListener('canplay', function (e) {
            if (!isStreaming) {
                isStreaming = true;
            }
        }, false);

        // Wait for the video to start to play
        video.addEventListener('play', function () {
            console.log('video playing')
        }, false);
    });
})();

salvate con ctrl+x confermate con y e premete invio.

Adesso vi mancherà un ultimo file 

Quest'ultimo non sarà un file json come i precedenti ma sarà un file html. Per caricarlo sul raspberry vi suggerisco un trucchetto molto facile.

per prima cosa scaricate sul vostro computer il file index.html cliccando QUI

Successivamente andate su questo sito

https://transfer.sh/

Trascinate il file index.html esattamente dove c'è il cerchio rosso

transfer.sh

Noterete che vi comparirà un indirizzo del tipo 

https://transfer.sh/abcdef/index.html

(ovviamente varierà di volta in volta)

Tornate sul terminale del vostro Raspberry, assicuratevi di essere su questo percorso

percorso

e lanciate questo comando (inutile dirvi che dovrete inserire il vostro url che avete ricavato dal sito transfer.sh)

sudo wget https://transfer.sh/abcdef/index.html

il file verrà caricato nella cartella doorpi assieme agli altri

aprite il file appena caricato, con il seguente comando

sudo nano index.html

Dovrete sostituire le xxxxxxx prima di duckdns in fondo al file con il vostro dominio.

Se il vostro router, quando avete aperto la porta 8080, esternamente vi avesse assegnato una porta diversa da quest'ultima, allora in questo file dovrete inserire quella porta dopo il dominio duckdns (ricordate l'esempio 5678?)

Successivamente salvate con ctrl+x confermate con y e premete invio.

Benissimo vi dovreste trovare sempre nel percorso 

percorso

da questa posizione lanciate il comando 

ls

dovreste vedere 3 file elencati come nella foto di esempio.

3 file presenti

benissimo, se cosi fosse, lanciate l'ultimo comando per riavviare il servizio.

sudo service uv4l_raspicam restart

Se tutto è andato a buon fine aprite una pagina di Chrome e digitate il seguente indirizzo https://xxxxx.duckdns.org:8888 

(ovviamente dovrete sostituire xxxx con il vostro dominio e 8888 lo dovrete sostituire con la porta esterna assegnata dal vostro router alla porta interna 8888 che potrebbe essere, se non in uso, tranquillamente anche 8888).

Vi dovreste trovare di fronte ad una situazione del genere

immagine videocamera

Sicuramente vi chiederà username e password se è la prima volta che vi collegate: username: www password: quella che avete scelto nel file di configurazione.

Se premerete accept la comunicazione a due vie avrà atto fino a che non premerete decline.

Benissimo la parte più tosta l'abbiamo superata, andiamo adesso a configurare le gpio tramite MQTT per gestire il pulsante.

Gpio MQTT

Per prima cosa lanciate il comando

cd

Dopodiché dovrete installare mqtt-io, per farlo dovrete lanciare questo comando

pip3 install mqtt-io

Nel caso vi dia un errore potete controllare se avete la versione di Python con il seguente comando

python3 --version

se avete già installato Python vi consiglio di rimuoverlo e reinstallarlo con i seguenti comandi

sudo apt-get remove python3-pip

E poi

sudo apt-get install python3-pip

E successivamente rilanciate il comando

pip3 install mqtt-io

nel caso che invece sia andato al primo colpo ignorate tutto questi passaggi

Lanciate il comando 

sudo nano config.yml

si aprirà un vero e proprio file yaml proprio come ci ha abituato a vedere il noto hub domestico Home Assistant.

copiate questi dati nel file che si è aperto

mqtt:
  host: 192.168.1.xx
  port: 1883
  user: "xxxxx"
  password: "xxxx"
  topic_prefix: doorbell
  ha_discovery:
    enabled: yes

gpio_modules:
  - name: raspberrypi
    module: raspberrypi
    cleanup: yes

digital_inputs:
  - name: button_1
    module: raspberrypi
    pin: 17
    on_payload: "on"
    off_payload: "off"
    pullup: yes
    pulldown: no
    Inverted: yes
    ha_discovery:
      enabled: yes

Ovviamente dovrete sostituire l'indirizzo ip dopo la parola host con l'indirizzo ip del vostro broker mqtt che coincide con l'ip di Home Assistant se avete installato il broker li.

Dovrete anche inserire user e password del vostro broker.

Il resto, se avete collegato il pulsante come nell'esempio che vi ho fatto nella sessione collegamenti ad inizio guida, potrete lasciarlo invariato.

salvate con ctr+x confermate con y e poi premete invio.

Adesso non vi rimane che automatizzare all'avvio il servizio mqtt-io (Grazie Diego!)

Lanciate quindi il comando

sudo nano /etc/systemd/system/mqttio.service

Nel file che si aprirà copiate quello che c'è scritto qua sotto.

[Unit]
Description=MQTTIO
After=syslog.target

[Service]
Type=simple
User=pi
Group=pi
WorkingDirectory=/home/pi
ExecStart=/usr/bin/python3 -m mqtt_io /home/pi/config.yml
SyslogIdentifier=piface
StandardOutput=syslog
StandardError=syslog
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

salvate con ctr+x confermate con y e poi premete invio.

Ci siamo quasi ragazzi, gli ultimi 3 comandi in sequenza e abbiamo finito con la configurazione.

sudo systemctl daemon-reload

sudo systemctl enable mqttio.service

sudo systemctl start mqttio.service

Passiamo adesso ad Home Assistant, necessario per impostare le notifiche e far suonare un Google Home alla pressione del pulsante sul Raspberry.

HOME ASSISTANT

Come prerequisito dovrete avere la connessione alla vostra istanza Home Assistant tramite https. Dovrete avere inoltre la companion app di Home Assistant configurata con notifiche abilitate.

Anche i package dovranno essere abilitati.

Create un file chiamato doorpi.yaml e dentro copiateci tutto il codice qua sotto

input_boolean:
  campanello:
    name: Campanello
    initial: off
    icon: mdi:bell-ring

input_select:
  campanello:
    name: Campanello
    options:
      - suoneria abilitata
      - nessuna suoneria
    initial: suoneria abilitata
    icon: mdi:bell-ring
################################################################################
############################automazioni#########################################
################################################################################
automation:
  - alias: campanello premuto
    initial_state: true
    trigger:
      - platform: state
        entity_id: binary_sensor.campanello
        to: 'on'
    action:
      service: input_boolean.turn_on
      entity_id: input_boolean.campanello

  - alias: suona il campanello
    initial_state: true
    trigger:
      platform: state
      entity_id: input_boolean.campanello
      to: 'on'
    action:
     - service: script.turn_on
       entity_id: script.notificacampanello
     - service: script.turn_on
       entity_id: script.suoneria_da_mediaplayer


  - alias: Il campanello suona ma è spento
    initial_state: true
    condition:
      condition: template
      value_template: "{{ is_state('input_select.campanello', 'nessuna suoneria') }}"
    trigger:
      platform: state
      entity_id: input_boolean.campanello
      to: 'on'
    action:
     - service: homeassistant.turn_off
       entity_id: automation.suona_il_campanello
     - service: input_boolean.turn_off
       entity_id: input_boolean.campanello
     - delay:
         milliseconds: 300
     - service: homeassistant.turn_on
       entity_id: automation.suona_il_campanello


  - alias: reimposta campanello ogni mattina
    initial_state: true
    trigger:
      platform: time
      at: '7:30:00'
    action:
      service: input_select.select_option
      data:
        entity_id: input_select.campanello
        option: suoneria abilitata
################################################################################
####################################camera######################################
################################################################################
camera:
  - platform: mjpeg
    name: videocamera del campanello
    mjpeg_url: https://xxxxxxx.duckdns.org:8080/stream/video.mjpeg
    verify_ssl: false


################################################################################
#######################################script###################################
################################################################################
script:
  notificacampanello:
    sequence:
      - service: notify.mobile_app_xxxxxx
        data:
          message: hanno suonato al campanello
          data:
            image: "https://xxxxxx.duckdns.org:8080/stream/snapshot.jpeg?delay_s=0"
            color: '#2DF56D'
            actions:
              - action: URI
                title: 'Vai a rispondere al videocitofono '
                uri: https://xxxxxx.duckdns.org:8888/
      - delay: '00:00:30'
      - service: input_boolean.turn_off
        entity_id: input_boolean.campanello

  suoneria_da_mediaplayer:
    sequence:
      - condition: template
        value_template: "{{ not is_state('input_select.campanello', 'nessuna-suoneria') }}"
      - service: media_player.volume_set
        data:
          entity_id: media_player.xxxxxxx
          volume_level: 1
      - service: media_player.play_media
        target:
          entity_id: media_player.xxxxxxx
        data:
          media_content_type: "mp3"
          media_content_id: "media-source://media_source/local/campanello.mp3"

Ovviamente dovrete configurarlo con i vostri parametri sostituendo le x nel dominio con il nome del vostro dominio ed eventualmente le porte se esternamente il vostro router non abbia assegnato la 8080 e 8888.

Dovrete sostituire le x nel servizio di notifica con il nome del servizio di notifica che vi avrà creato la vostra companion app. Per vedere come si chiama dovrete cercare il servizio notify.mobile_app_xxxxx nei servizi nel tab strumenti per sviluppatori su Home Assistant.

Dovrete anche mettere il nome del mediaplayer (o dei mediaplayer) che vorrete far suonare a seguito della pressione del pulsante.

Se notate nel package c'è anche un input select, quello vi permetterà di disabilitare il suono se qualcuno preme il pulsante, una sorta di non disturbare, la notifica sullo smartphone arriverà comunque.

Ogni mattina il non disturbare si disabiliterà da solo.

c'è anche una voce che è commentata con # la voce color. Essa serve a cambiare il colore della notifica, ma questa funzione è valida solo per Android. Quindi non de-commentatela se avete iOS.

Sarà ovviamente possibile cambiare il colore cambiando il codice dopo la voce color. Troverete in rete i vari codici Hex dei vari colori.

Vi rimane da fare un'ultima cosa cioè, tramite samba, caricare un file mp3 per simulare il suono del campanello nel browser multimediale di Home Assistant (chiamatela campanello.mp3 oppure chiamatela come volete basta che vi ricordiate di cambiare il nome anche nel package).

Se tutto è andato a buon fine, quando premerete il pulsante sul citofono, il vostro mediaplayer riprodurrà un suono, vi arriverà una notifica con tanto di screenshot della videocamera sul vostro smartphone.

notifica 1
notifica 2

 e se cliccherete su "vai a rispondere al videocitofono" vi si aprirà una pagina web dove avrete accesso alla schermata con video e audio a due vie.

Spero di essere stato abbastanza chiaro, prestate molta attenzione e seguite i passaggi con calma.

Vi lascio adesso al video di fine articolo, direttamente dal nostro canale YouTube, dove potrete vedere il sistema in funzione!

Produrre e aggiornare contenuti su vincenzocaputo.com richiede molto tempo e lavoro. Se il contenuto che hai appena letto è di tuo gradimento e vuoi supportarmi, clicca uno dei link qui sotto per fare una donazione.

Luigi Duchi

Luigi Duchi

Nato a Grosseto il 24 Dicembre 1982 perito elettrotecnico che lavora nel mondo della domotica e installazione di impianti elettrici, impianti di allarmi, videosorveglianza e automazioni in genere. Appassionato da sempre di tecnologia e aperto alla conoscenza di nuove soluzioni.

Disqus loading...