https://d226lax1qjow5r.cloudfront.net/blog/blogposts/home-surveillance-system-with-node-and-a-raspberry-pi/Blog_Home-Surveillance_Node-RaspberryPi_1200x600.png

Sistema de vigilancia doméstica con Node y una Raspberry Pi

Publicado el May 19, 2020

Tiempo de lectura: 41 minutos

¿Alguna vez se ha preguntado cómo construir un sistema de vigilancia doméstica? ¿Quizá para vigilar a sus hijos, supervisar a personas vulnerables en su casa o para que sea su sistema de seguridad doméstica? Este tutorial le guiará a través del proceso de introducción para construir uno.

En este tutorial, se llega a construir un sistema de vigilancia del hogar pequeño y barato usando una Raspberry Pi 4 con un módulo de cámara Raspberry Pi y sensor de movimiento. El lado de software de esto será el uso de Video API de Vonage (anteriormente TokBox OpenTok) para publicar el flujo y la Mensajes API de Vonage para notificar al usuario que se detecta movimiento por SMS.

Estas son algunas de las cosas que aprenderás en este tutorial:

Requisitos previos

  • Raspberry Pi 4

  • Módulo de cámara Raspberry Pi

  • Sensor de movimiento (HC-SR501 PIR)

  • Cuenta TokBox

  • Node y NPM instalados en la Raspberry Pi

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Instalación y configuración de Raspberry Pi

La Fundación Raspberry Pi es una organización benéfica con sede en el Reino Unido que permite a personas de todo el mundo resolver problemas tecnológicos y expresarse de forma creativa utilizando el poder de la informática y las tecnologías digitales para el trabajo.

En su sitio web hay una guía paso a paso sobre qué es cada parte del dispositivo Raspberry Pi, cómo instalar el sistema operativo y cómo empezar a utilizar una Raspberry Pi. También hay muchos otros recursos para ayudar con la solución de cualquier problema que pueda tener, y un montón de otros proyectos que puedan interesarle.

Instalación de cámaras y sensores de movimiento

Instalación del módulo de cámara Raspberry Pi

Este tutorial utiliza una Raspberry Pi 4 y el módulo oficial Raspberry Pi Camera, aunque no debería haber problemas utilizando otras cámaras.

La fotografía de abajo es de la Raspberry Pi y un módulo de cámara utilizado en este artículo:

A Raspberry Pi with the Camera moduleA Raspberry Pi with the Camera module

Conecte el Módulo de Cámara a través del cable plano en el puerto del Módulo de Cámara de la Raspberry Pi. La siguiente fotografía muestra dónde debe instalar la cinta del módulo de cámara:

Raspberry Pi with Camera Ribbon InstalledRaspberry Pi with Camera Ribbon Installed

Habilitar SSH y Cámara

Secure Shell (SSH) es un paquete de software que permite una conexión segura y el control de un sistema remoto. La Raspberry Pi en este tutorial se ejecutará en modo headless, lo que significa sin monitor, teclado o ratón. Con SSH habilitado, usted será capaz de conectarse al dispositivo de forma remota en su ordenador o teléfono.

Para habilitar SSH, en el terminal Raspberry Pi, ejecute:

sudo raspi-config

Verás una pantalla con una imagen similar a la que se muestra a continuación:

Raspberry Pi ConfigurationRaspberry Pi Configuration

Elija la opción 5 - Interfacing Options

  • En el menú siguiente, seleccione la opción P1 para Cameray, a continuación, seleccione Yes,

  • A continuación, seleccione la opción P2 para SSHseleccione de nuevo Yes.

Ahora ha habilitado el módulo de la cámara y SSH en su Raspberry Pi.

Instalación del sensor de movimiento

El siguiente paso es conectar la Raspberry Pi a un sensor de movimiento. Este tutorial utiliza el sensor de movimiento HC-SR501 PIR; sin embargo, otros módulos de sensor de movimiento deberían funcionar bien. Por favor, consulta sus guías de cableado para conectarlos a tu Raspberry Pi.

Primero, coge el sensor y conéctale tres cables. He usado rojo para el vivo, azul para el GPIO y negro para tierra. Para el sensor en este ejemplo, el primer pin es tierra, segundo GPIO y tercero vivo como se muestra:

Example of Motion SensorExample of Motion Sensor

Un gran ejemplo para describir cada uno de los pines en la Raspberry Pi está en El sitio web de Raspberry Pi. El diagrama ilustra la disposición de los pines GPIO, como se muestra a continuación:

Diagram of Raspberry Pi GPIO PinsDiagram of Raspberry Pi GPIO Pins

La parte final es conectar los cables a la Raspberry Pi. El cable de corriente (rojo) debe conectarse a una de las patillas de la Pi. 5V power pines de la Pi, en referencia al diagrama anterior he utilizado el pin 2. El cable de tierra (negro) debe conectarse a uno de los pines de la Pi, de nuevo en referencia al diagrama he utilizado el pin 6. GND en el Pi, de nuevo refiriéndose al diagrama utilicé el pin 6. El último cable a unir es el GPIO (azul), que debe conectarse a uno de los pines de la Pi. GPIO pines. En este ejemplo, he utilizado el pin 12, etiquetado como "GPIO 18".

A continuación se muestra la configuración final del cableado:

Sensor Writing Part 2Sensor Writing Part 2

Pruebas de detección de movimiento

Ahora todo el hardware está instalado y configurado, y es hora de construir el código para el proyecto. Sin embargo, en primer lugar, es necesario crear un proyecto Node, para realizar pruebas de movimiento y prepararse para el proyecto que tenemos por delante. Este proyecto es donde se escribirá todo el código de detección de movimiento y streaming de Video. Para crear un nuevo proyecto Node, haz un nuevo directorio, cambia a ese directorio y ejecuta npm init. Ejecutar los comandos listados a continuación hace estas tres cosas:

mkdir /home/pi/pi-cam/ cd /home/pi/pi-cam/ npm init

Siga las instrucciones solicitadas, establezca un nombre para el proyecto y deje el resto de entradas por defecto.

Los siguientes comandos crean un nuevo index.jsque almacenará la mayor parte de tu código, e instala un nuevo paquete llamado onoff que permite controlar los pines GPIO:

touch index.js npm install onoff

Dentro de su nuevo index.js copia el siguiente código que lee el pin GPIO 18 para alertar si se ha detectado movimiento, o alertar cuando el movimiento se ha detenido.

const gpio = require('onoff').Gpio;
const pir = new gpio(18, 'in', 'both');

pir.watch(function(err, value) {
    if (value == 1) {
        console.log('Motion Detected!')
    } else {
        console.log('Motion Stopped');
    }
});

Es hora de comprobar si el código anterior y la instalación del sensor de movimiento se ha realizado correctamente. Corre:

node index.js

Agite la mano delante del sensor de movimiento y observe el Terminal para ver "¡Movimiento detectado!". Unos segundos después verás la salida "Movimiento detenido".

Probar la cámara

En su línea de comandos Raspberry Pi, escriba el siguiente comando para tomar una foto fija de la vista de la cámara.

NOTA Si ha iniciado sesión como un usuario distinto del predeterminado pisustituya pi por su nombre de usuario.

raspistill -o /home/pi/cam.jpg

Si busca en el directorio /home/pi/ verá cam.jpg. Al abrirlo verás una foto de la vista actual de la cámara de tu Raspberry Pi.

Nodo y NPM

node --version npm --version

Tanto Node como NPM deben estar instalados y en la versión correcta. Vaya a nodejs.orgdescarga e instala la versión correcta si no la tienes.

Nuestra CLI

Configura tu CLI de Vonage con esta guía. Sólo necesitas la Instalación y Configuración paso.

Git (opcional)

Puede utilizar git para clonar la aplicación de demostración desde GitHub.

Para aquellos que no se sientan cómodos con los comandos git, no os preocupéis, os tengo cubiertos.

Siga esta guía para instalar git.

Instalar un servidor Mysql

En la Raspberry Pi, ejecute el siguiente comando para instalar el servidor de base de datos MySQL:

sudo apt install mariadb-server

Por defecto, el servidor MySQL se instala con el usuario root sin contraseña. Necesitas rectificar esto, para asegurarte de que la base de datos no es insegura. En la Pi ejecute el siguiente comando y siga las instrucciones.

sudo mysql_secure_installation

Ahora que root contraseña del usuario, es hora de crear una base de datos y un usuario para acceder a esa base de datos. Conéctate al servidor MySQL:

sudo mysql -u root -p

Ahora ejecute las siguientes consultas SQL para crear un nuevo usuario y concederle algunos privilegios en una nueva base de datos:

-- Creates the database with the name picam
CREATE DATABASE picam;
-- Creates a new database user "camuser" with a password "securemypass" and grants them access to picam
GRANT ALL PRIVILEGES ON picam.* TO `camuser`@localhost IDENTIFIED BY "securemypass";
-- Flushes these updates to the database
FLUSH PRIVILEGES;

Tu Raspberry Pi está ahora configurada y lista para la parte de código de este tutorial.

Creación de la aplicación

Instalación de un certificado SSL

En la terminal de tu Raspberry Pi, cambia el directorio a la ruta de tu proyecto y ejecuta el siguiente comando para generar un certificado SSL autofirmado. Vonage Video API requiere HTTPS para acceder, por lo que se necesita un certificado SSL, incluso si es autofirmado. Ejecuta el siguiente comando para generar tus certificados SSL.

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

Se crean dos archivos key.pem y cert.pemmuévelos a una ubicación a la que tu código pueda acceder. Para este tutorial, están en el directorio del proyecto.

El servidor web

Express es un framework de aplicaciones web Node.js mínimo y flexible que proporciona un sólido conjunto de funciones para aplicaciones web y móviles.

Express es un framework Node.js muy ligero y flexible que es lo que necesitas en este proyecto. Para proporcionar puntos finales para que usted pueda acceder a su flujo de vídeo.

Instale Express en su aplicación con el siguiente comando:

npm install express --save

En la parte superior del archivo index.js debe importar los paquetes https, fs y express. Realice los siguientes cambios:

+ const express = require('express');
+ const https = require('https');
+ const fs = require('fs');
const gpio = require('onoff').Gpio;

+ const app = express();
const pir = new gpio(18, 'in', 'both');

pir.watch(function(err, value) {
    if (value == 1) {
        console.log('Motion Detected!')
-    } else {
-        console.log('Motion Stopped');
    }
});

Usted no necesita la else parte de la detección de movimiento para este tutorial. Así que elimina esa parte también, como se muestra arriba.

Necesitas un servidor web para acceder a tu flujo de Video a través de la red o Internet. Es hora de crear un método para iniciar un nuevo servidor con un endpoint de ejemplo. Arriba pir.watch(function(err, value) { añadir

async function startServer() {
  const port = 3000;

  app.get('/', (req, res) => {
    res.json({ message: 'Welcome to your webserver!' });
  });

  const httpServer = https.createServer({
    // The key.pem and cert.pem files were created by you in the previous step, if the files are not stored in the project root directory
    // make sure to update the two lines below with their correct paths.
    key: fs.readFileSync('./key.pem'),
    cert: fs.readFileSync('./cert.pem'),
    // Update this passphrase with what ever passphrase you entered when generating your SSL certificate.
    passphrase: 'testpass',
  }, app);

  httpServer.listen(port, (err) => {
    if (err) {
      return console.log(`Unable to start server: ${err}`);
    }

    return true;
  });
}

Ahora se necesita una forma de acceder a esta función, debajo de su función startServer() {} añade una llamada a la función como se muestra:

startServer();

Para probar que esto funciona, en tu Terminal, ejecuta:

node index.js

Nota: Si estás conectado a tu Raspberry Pi a través de SSH o teclado/tv, en el Terminal escribe: ifconfig para averiguar la dirección IP local de tu Raspberry Pi.

Accediendo a la dirección IP de su Raspberry Pi en su navegador: https://<ip address>:3000/ devolverá

{"message":"Welcome to your webserver!"}

Instalación de Sequelize

Sequelize es una potente librería para Node que facilita la consulta de bases de datos. Es un Object-Relational Mapper (ORM), que mapea objetos a los esquemas de la base de datos. Sequelize cubre varios protocolos como Postgres, MySQL, MariaDB, SQLite y Microsoft SQL Server. Este tutorial utilizará el servidor MariaDB porque es el servidor SQL disponible en la Raspberry Pi.

# DotEnv is used to access your .env variables # Sequelize is an ORM for your DATABASE # mysql2 is what you're using as a database. Sequelize needs to know this. npm install --save dotenv sequelize mysql2 # Sequelize-cli allows you to generate models, migrations and run these migrations. npm install -g sequelize-cli # Initializes Sequelize into the project, creating the relevant files and directories sequelize init

Dentro del directorio de su proyecto, cree un nuevo archivo .envy actualice los valores siguientes con las credenciales correctas para su base de datos.

DB_NAME= DB_USERNAME= DB_PASSWORD= DB_HOST=127.0.0.1 DB_PORT=3306

Dentro del directorio config cree un nuevo archivo llamado config.js. Este archivo es donde se almacenan los ajustes de la base de datos del proyecto, y siendo javascript, puede acceder al archivo .env archivo :

require('dotenv').config();

module.exports = {
  development: {
    database: process.env.DB_NAME,
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    dialect: 'mysql',
    operatorsAliases: false
  },
}

Ahora en models/index.jsbuscar y reemplazar:

- const config = require(__dirname + '/../config/config.json')[env];
+ const config = require(__dirname + '/../config/config.js')[env];

En el archivo index.js importe el archivo models/index.js para que tu aplicación acceda a tus modelos de base de datos:

const db = require('./models/index');

Generar y ejecutar una migración

Cuando se crea una sesión de Vonage Video, se devuelve un ID de sesión, este ID de sesión debe almacenarse en algún lugar para que puedas conectarte a él de manera remota. La mejor manera de hacerlo es en una tabla de base de datos. Utilizando la CLI de Sequelize recientemente instalada, ejecuta el siguiente comando. Crea una nueva tabla llamada Session, con dos nuevas columnas:

  • sessionId (que es una cadena),

  • activo (que es un booleano).

# Generate yourself a Session model, this is going to be used to store the sessionId of the video feed sequelize model:generate --name Session --attributes sessionId:string,active:boolean

Dos nuevos archivos se crean después de este comando tiene éxito, estos son:

  • models/session.js

  • migrations/<timestamp>-Session.js

El nuevo modelo, session.jsdefine lo que la base de datos espera en términos de nombres de columnas, tipos de datos, entre otras cosas.

El nuevo archivo de migraciones define lo que se guardará en la base de datos cuando la migración se realice correctamente. En este caso, crea una nueva tabla de base de datos llamada sessions con cinco nuevas columnas:

  • id

  • sessionId

  • activo

  • createdAt

  • updatedAt

Ejecute esta migración utilizando el comando CLI de Sequelize con los parámetros db:migrate:

sequelize db:migrate

El resultado será el mismo que el del ejemplo siguiente:

== 20200504091741-create-session: migrating =======
== 20200504091741-create-session: migrated (0.051s)

Ahora tiene una nueva tabla de base de datos que utilizará más adelante para almacenar el ID de sesión.

Vonage Video

Estás a punto de instalar dos librerías que el proyecto necesita, Vonage Video (antes TokBox OpenTok), y Puppeteer.

Vonage Video (anteriormente TokBox OpenTok) es un servicio que ofrece sesiones de vídeo interactivo en directo a personas de todo el mundo. La API de Video de Vonage (antes TokBox OpenTok) utiliza el estándar de la industria WebRTC. Permite crear experiencias de vídeo personalizadas en miles de millones de dispositivos, ya sean aplicaciones móviles, web o de escritorio.

Puppeteer es una libreria Node que proporciona un metodo para controlar Chrome o Chromium mediante programacion. Por defecto, Puppeteer se ejecuta en un modo headless, pero también puede ejecutarse en un modo no headless de Chrome o Chromium. Un navegador headless es un navegador sin interfaz gráfica de usuario, (como por ejemplo sin monitor que el usuario pueda ver).

Instale ambas bibliotecas ejecutando el siguiente comando:

npm install opentok puppeteer

Copie las adiciones al código en su index.js como se muestra a continuación. Este código importa tres bibliotecas en su proyecto.

  • OpenTok (Para publicar/suscribirse a la transmisión de video con Vonage Video)

  • Puppeteer (Para que tu Raspberry Pi abra un navegador en modo headless para publicar el stream)

  • DotEnv (Para acceder a las variables .env)

Se inicializa un objeto OpenTok utilizando tu clave de API de Vonage y las variables Secret .env que aún tienes que agregar.

const gpio = require('onoff').Gpio;
+ const OpenTok = require('opentok');
+ const puppeteer = require('puppeteer');
+ const dotenv = require('dotenv');

const app = express();
const pir = new gpio(23, 'in', 'both');

+ dotenv.config();

+ const opentok = new OpenTok(
+   process.env.VONAGE_VIDEO_API_KEY,
+   process.env.VONAGE_VIDEO_API_SECRET,
+ );

Necesitarás tu clave y secreto de API de Video de Vonage. Puedes encontrarlos ingresando a tu cuenta de Video API de Vonage.

A continuación, cree un nuevo proyecto. Una vez creado, verá el panel de control de su proyecto, que contiene la clave y el secreto de la API.

Dentro de tu .env añade las credenciales de Vonage Video como se indica a continuación (actualiza los valores dentro de < y > con tus credenciales):

VONAGE_VIDEO_API_KEY= VONAGE_VIDEO_API_SECRET=

Cómo crear una sesión de Video de Vonage

En tu archivo index.js encuentra la parte del código que inicializa el objeto OpenTok y añade tres variables llamadas:

  • canCreateSessiondetermina si el proyecto puede crear una sesión o no (si ya hay una sesión activa).

  • sessiones la variable que contiene el objeto de sesión actual

  • url es la variable para guardar la URL actual de la sesión (en este caso, una URL Ngrok)

const opentok = new OpenTok(
  process.env.VONAGE_VIDEO_API_KEY,
  process.env.VONAGE_VIDEO_API_SECRET,
);

+ let canCreateSession = true;
+ let session = null;
+ let url = null;

Es hora de crear una sesión y almacenar el ID de sesión devuelto en la base de datos para utilizarlo cuando el usuario haga clic en el enlace para ver el flujo publicado. Copie el código de abajo para añadir las funciones que logran esto:

async function createSession() {
  opentok.createSession({ mediaMode: 'routed' }, (error, session) => {
    if (error) {
      console.log(`Error creating session:${error}`);

      return null;
    }

    createSessionEntry(session.sessionId);

    return null;
  });
}

function createSessionEntry(newSessionId) {
  db.Session
    .create({
      sessionId: newSessionId,
      active: true,
    })
    .then((sessionRow) => {
      session = sessionRow;

      return sessionRow.id;
    });
}

La parte del observador de sesión del proyecto necesita ser actualizada para determinar si canCreateSession es true, si este es el caso, establécelo a false (para que no se creen otros flujos mientras este esté activo), luego crea la sesión llamando al método previamente añadido al proyecto createSession. Esto se hace actualizando el siguiente código:

pir.watch(function(err, value) {
-    if (value == 1) {
+    if (value === 1 && canCreateSession === true) {
+       canCreateSession = false;
        console.log('Motion Detected!');

+       createSession();
    }
});

Crear un editor y un abonado

Se necesita un nuevo directorio que contenga las páginas frontales para que Pi publique su flujo y el cliente (usted) se suscriba a un flujo. Cree un nuevo directorio public con su directorio css, jsy config con los siguientes comandos:

mkdir public mkdir public/css mkdir public/js mkdir public/config

Vas a necesitar algo de estilo para la página que ve el cliente, así que crea un nuevo archivo app.css dentro de public/css/ y copia el código de abajo en este archivo. El CSS de abajo asegura que el tamaño del contenido es 100% en altura, el color de fondo es gris, y el flujo de vídeo es de pantalla completa para una máxima visibilidad.

body, html {
    background-color: gray;
    height: 100%;
}

#videos {
    position: relative;
    width: 100%;
    height: 100%;
    margin-left: auto;
    margin-right: auto;
}

#subscriber {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: 10;
}

#publisher {
    position: absolute;
    width: 360px;
    height: 240px;
    bottom: 10px;
    left: 10px;
    z-index: 100;
    border: 3px solid white;
    border-radius: 3px;
}

A continuación, deberás crear un nuevo archivo javascript que se utilizará del lado del cliente (es decir, en tu navegador como suscriptor). Este archivo inicializará una sesión de Vonage Video, obtendrá los detalles de la sesión del backend con una solicitud GET y, si la ruta es /serve publicará el flujo, si la ruta URL es /client se suscribirá al flujo de Video activo actual. En public/js/ cree un nuevo archivo app.js y copia el siguiente código en él:

let apiKey;
let sessionId;
let token;
let isPublisher = false;
let isSubscriber = false;
let url = '';

// Handling all of our errors here by alerting them
function handleError(error) {
  if (error) {
    console.log(error.message);
  }
}

function initializeSession() {
  const session = OT.initSession(apiKey, sessionId);

  // Subscribe to a newly created stream
  if (isSubscriber === true) {
    session.on('streamCreated', (event) => {
      session.subscribe(event.stream, 'subscriber', {
        insertMode: 'append',
        width: '100%',
        height: '100%',
      }, handleError);
    });
  }

  if (isPublisher === true) {
    // Create a publisher
    let publisher = OT.initPublisher('publisher', {
      insertMode: 'append',
      width: '100%',
      height: '100%',
    }, handleError);
  }

  // Connect to the session
  session.connect(token, (error) => {
    // If the connection is successful, publish to the session
    if (error) {
      handleError(error);
    } else if (isPublisher === true) {
      session.publish(publisher, handleError);
    }
  });
}

function setDetails(details) {
  apiKey = details.apiKey;
  sessionId = details.sessionId;
  token = details.token;

  initializeSession();
}

async function getDetails(publisher, subscriber, url) {
  const request = await fetch(url);
  const response = await request.json();

  if (publisher === true) {
    isPublisher = true;
  }

  if (subscriber === true) {
    isSubscriber = true;
  }

  setDetails(response);
}

function fetchUrl() {
  return fetch('/config/config.txt')
   .then( r => r.text() )
   .then( t => { url = t} );
}

Se necesitan dos nuevos HTML para estos dos nuevos puntos finales /serve y /clientutilizan la biblioteca javascript del lado del cliente de Vonage Video para publicar o suscribirse a las sesiones activas actuales.

Cree un nuevo server.html dentro del directorio public/ con el siguiente contenido:

<html>
<head>
    <link type="text/css" rel="stylesheet" href="/css/app.css">
    <script src="https://static.opentok.com/v2/js/opentok.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <h1>Publisher view</h1>
    <div id="videos">
        <div id="publisher"></div>
    </div>

    <script type="text/javascript" src="/js/app.js"></script>
    <script type="text/javascript">
        getDetails(true, false, 'https://localhost:3000/get-details');
    </script>
</body>
</html>

Para el /client cree un nuevo archivo client.html dentro del directorio public/ y copia el siguiente código:

<html>
<head>
    <link type="text/css" rel="stylesheet" href="/css/app.css">
    <script src="https://static.opentok.com/v2/js/opentok.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <h1>Subscriber view</h1>
    <div>
        <button onclick="getDetails(false, true, url + 'get-details')">Watch Video Stream</button>
    </div>
    <div id="videos">
        <div id="subscriber"></div>
    </div>


    <script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

Todavía no tienes definidos los puntos finales en tu código backend (index.js), ¡así que es hora de crearlos! Encuentra el endpoint original que creaste:

app.get('/', (req, res) => {
  res.json({ message: 'Welcome to your webserver!' });
});

Sustitúyalo por el siguiente código:

// Adds the public directory to a publicly accessible directory within our new web server
app.use(express.static(path.join(`${__dirname}/public`)));
// Creates a new endpoint `/serve` as a GET request, which provides the contents of `/public/server.html` to the users browser
app.get('/serve', (req, res) => {
  res.sendFile(path.join(`${__dirname}/public/server.html`));
});

// Creates a new endpoint `/client` as a GET request, which provides the contents of `/public/client.html` to the users browser
app.get('/client', (req, res) => {
  res.sendFile(path.join(`${__dirname}/public/client.html`));
});

// Creates a new endpoint `/get-details` as a GET request, which returns a JSON response containing the active Vonage Video session, the API Key and a generated Token for the client to access the stream with.
app.get('/get-details', (req, res) => {
  db.Session.findAll({
    limit: 1,
    where: {
      active: true,
    },
    order: [['createdAt', 'DESC']],
  }).then((entries) => res.json({
    sessionId: entries[0].sessionId,
    token: opentok.generateToken(entries[0].sessionId),
    apiKey: process.env.VONAGE_VIDEO_API_KEY,
  }));
});

Si te fijas bien en el código anterior, estás utilizando una nueva librería llamada path. Así que en la parte superior del archivo index.js incluya la ruta como se muestra a continuación:

const path = require('path');

No pasa nada hasta que publiques la pantalla en la Raspberry Pi.

Dentro de .env añada otra variable (60000 milisegundos es el equivalente a 60 segundos):

VIDEO_SESSION_DURATION=60000

Volver dentro index.js añade una funcionalidad que cerrará el flujo cuando la función closeSession() sea llamada:

async function closeSession(currentPage, currentBrowser) {
  console.log('Time limit expired. Closing stream');
  await currentPage.close();
  await currentBrowser.close();

  if (session !== null) {
    session.update({
      active: false
    });
  }
}

Ahora es el momento de crear la publicación del flujo en modo headless, la función de abajo hace lo siguiente todo en modo headless:

  • Crea una nueva instancia del navegador,

  • Abre una nueva página / pestaña,

  • Anula los permisos para la cámara y el micrófono en el navegador,

  • Dirige la página al /serve para publicar el flujo de vídeo,

  • Crea un nuevo temporizador para detener el flujo de vídeo después de un tiempo determinado,

  • Crea otro temporizador para proporcionar un búfer entre la finalización del flujo y el momento en que se permite el inicio de otro.

Copie el código siguiente en su index.js archivo:

async function startPublish() {
  // Create a new browser using puppeteer
  const browser = await puppeteer.launch({
    headless: true,
    executablePath: 'chromium-browser',
    ignoreHTTPSErrors: true,
    args: [
      '--ignore-certificate-errors',
      '--use-fake-ui-for-media-stream',
      '--no-user-gesture-required',
      '--autoplay-policy=no-user-gesture-required',
      '--allow-http-screen-capture',
      '--enable-experimental-web-platform-features',
      '--auto-select-desktop-capture-source=Entire screen',
    ],
  });

  // Creates a new page for the browser
  const page = await browser.newPage();

  const context = browser.defaultBrowserContext();
  await context.overridePermissions('https://localhost:3000', ['camera', 'microphone']);

  await page.goto('https://localhost:3000/serve');

  let sessionDuration = parseInt(process.env.VIDEO_SESSION_DURATION);
  let sessionExpiration = sessionDuration + 10000;

  // Closes the video session / browser instance when the predetermined time has expired
  setTimeout(closeSession, sessionDuration, page, browser);

  // Provides a buffer between the previous stream closing and when the next can start if motion is detected
  setTimeout(() => { canCreateSession = true; }, sessionExpiration);
}

Es hora de hacer uso de la función que acabas de poner en tu proyecto, busca y añade startPublish() a tu código:

createSessionEntry(session.sessionId);
+ startPublish();

Ya casi puedes probar tu código. Has creado nuevos puntos finales, accesibles como editor o suscriptor del vídeo. A continuación, desea tener una URL para acceder a la secuencia si se encuentra en una ubicación remota.

Ngrok

Si desea conectarse a la corriente de la cámara de forma remota, fuera de la red, la Raspberry Pi se ha conectado a, y tendrá que exponer su servidor web a Internet. Es hora de instalar y utilizar Ngrok.

Ejecutando el siguiente comando, Ngrok sólo se instalará localmente para el proyecto:

npm install ngrok

Ahora necesitas implementar el uso de Ngrok en tu proyecto. Así que en la parte superior del archivo index.js incluye el paquete ngrok paquete:

const ngrok = require('ngrok');

Ahora necesitas crear una función que se conecte a Ngrok. Cuando tenga éxito, guardará la URL devuelta en un archivo public/config/config.txt que se recupera en el archivo creado en los pasos anteriores public/client.html. En su index.js añade lo siguiente:

async function connectNgrok() {
  let url = await ngrok.connect({
    proto: 'http',
    addr: 'https://localhost:3000',
    region: 'eu',
    // The below examples are if you have a paid subscription with Ngrok where you can specify which subdomain
    //to use and add the location of your configPath. For me, it was gregdev which results in
    //https://gregdev.eu.ngrok.io, a reserved subdomain
    // subdomain: 'gregdev',
    // configPath: '/home/pi/.ngrok2/ngrok.yml',
    onStatusChange: (status) => { console.log(`Ngrok Status Update:${status}`); },
    onLogEvent: (data) => { console.log(data); },
  });

  fs.writeFile('public/config/config.txt', url, (err) => {
    if (err) throw err;
    console.log('The file has been saved!');
  });
}

Ahora que todo esto ha sido configurado, puede llamar a Ngrok llamando a la función connectNgrok() como se muestra a continuación:

httpServer.listen(port, (err) => {
  if (err) {
    return console.log(`Unable to start server: ${err}`);
  }

+   connectNgrok();

  return true;
});

Ahora puede probar su flujo. Ejecute lo siguiente, mientras que en la Terminal Raspberry Pi:

node index.js

Después de unos 10 segundos (para que el servicio se inicialice), agita la mano delante del sensor de movimiento. Si tiene éxito, verá un Motion Detected! en la ventana Terminal. Ahora ve al archivo de tu Raspberry pi public/config/config.txtcopia esta URL y pégala en tu navegador. Añade /client al final de la URL. Para mí, esto fue https://gregdev.eu.ngrok.io/client. Tu navegador mostrará ahora el stream publicado desde tu Raspberry pi, que ha abierto una instancia del navegador Chromium headless y navegado a su IP local: https://localhost/serve.

Instalación de mensajes de Vonage

Para usar la nueva Messages API de Vonage, que envía mensajes SMS cada vez que se detecta movimiento, deberás instalar la versión beta de nuestro SDK de nodo. Ejecuta el siguiente comando:

npm install @vonage/server-sdk

La Messages API requiere que crees una aplicación en el portal para desarrolladores de Vonage, y una a private.key que se genera al crear la aplicación. Al ejecutar el siguiente comando se crea la aplicación, se configuran los webhooks (que no son necesarios en este momento, así que déjalos entre comillas) y, por último, un archivo de claves llamado private.key.

vonage apps:create "My Messages App" --messages-inbound-url=https://example.com/webhooks/inbound-message --messages-status-url=https://example.com/webhooks/message-status

Ahora que ha creado la aplicación, es necesario configurar algunas variables de entorno. Encontrará sus API key y API secret en el Panel del desarrollador de Vonage.

La dirección VONAGE_APPLICATION_PRIVATE_KEY_PATH es la ubicación del archivo que generó en el comando anterior. Este proyecto lo tenía almacenado en el directorio del proyecto, así por ejemplo: /home/pi/pi-cam/my_messages_app.key

El VONAGE_BRAND_NAME no se utiliza en este proyecto, pero es necesario tener un conjunto de Messages API, lo he mantenido simple HomeCam

Por último, el TO_NUMBER es el destinatario que recibe la notificación SMS.

VONAGE_API_KEY= VONAGE_API_SECRET= VONAGE_APPLICATION_PRIVATE_KEY_PATH= VONAGE_BRAND_NAME=HomeCam TO_NUMBER=

En la parte superior del archivo index.js importa el paquete de Vonage:

const Vonage = require('@vonage/server-sdk');

Para crear el objeto de Vonage que se utiliza para realizar las solicitudes a la API, en la definición del objeto OpenTok, añade lo siguiente:

const vonage = new Vonage({
  apiKey: process.env.VONAGE_API_KEY,
  apiSecret: process.env.VONAGE_API_SECRET,
  applicationId: process.env.VONAGE_APPLICATION_ID,
  privateKey: process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH,
});

Dentro, y al final de tu función connectNgrok() agrega una funcionalidad que actualice tu aplicación de Vonage con webhooks para manejar mensajes entrantes y el estado del mensaje con la URL correcta (la URL de Ngrok):

vonage.applications.update(process.env.VONAGE_APPLICATION_ID, {
  name: process.env.VONAGE_BRAND_NAME,
  capabilities: {
    messages: {
      webhooks: {
        inbound_url: {
          address: `${url}/webhooks/inbound-message`,
          http_method: 'POST',
        },
        status_url: {
          address: `${url}/webhooks/message-status`,
          http_method: 'POST',
        },
      },
    },
  },
},
(error, result) => {
  if (error) {
    console.error(error);
  } else {
    console.log(result);
  }
});

Enviar un SMS

El método de notificación elegido para este tutorial es SMS, enviado a través de Messages API. La librería de Vonage ya ha sido instalada en este proyecto, por lo que no es necesario configurarla. En el archivo index.js añade una nueva función llamada sendSMS()que toma la URL y el número en el que esperas recibir el SMS. Luego, usando el Messages API, envía una notificación SMS de que la cámara ha detectado movimiento.

function sendSMS() {
  const message = {
    content: {
      type: 'text',
      text: `Motion has been detected on your camera, please view the link here: ${url}/client`,
    },
  };

  vonage.channel.send(
    { type: 'sms', number: process.env.TO_NUMBER },
    { type: 'sms', number: process.env.VONAGE_BRAND_NAME },
    message,
    (err, data) => { console.log(data.message_uuid); },
    { useBasicAuth: true },
  );
}

Ahora llama a la función sendSMS() añadiendo:

createSessionEntry(session.sessionId);
+ sendSMS();

Ya está. Todo lo que tienes que hacer ahora es SSH en tu Raspberry Pi e iniciar el servidor dentro de su directorio de proyecto en ejecución:

node index.js

Su servidor está ahora en marcha, y su Raspberry Pi es detectar el movimiento, que luego hará lo siguiente:

  • Inicia una sesión de OpenTok,

  • Guarde el ID de sesión en la base de datos,

  • Envía un SMS a tu número de teléfono predeterminado con un enlace al flujo,

  • Iniciar un flujo de publicación desde la Raspberry pi.

En poco tiempo habrá creado un sistema de vigilancia doméstica al que podrá acceder desde cualquier lugar del mundo.

El código completo de este tutorial se encuentra en el repositorio repositorio GitHub.

A continuación encontrarás algunos otros tutoriales que hemos escrito implementando la Video API de Vonage en proyectos:

No olvides que si tienes alguna pregunta, consejo o idea que quieras compartir con la comunidad, no dudes en entrar en nuestro espacio de trabajo espacio de trabajo comunitario Slack. Me encantaría saber si alguien ha implementado este tutorial y cómo funciona su proyecto.

Compartir:

https://a.storyblok.com/f/270183/250x250/b052219541/greg-holmes.png
Greg HolmesAntiguos alumnos de Vonage

Antiguo educador de desarrolladores @Vonage. Procedente de PHP, pero no limitado a un solo lenguaje. Un ávido jugador y un entusiasta de Raspberry pi. A menudo se le encuentra practicando escalada en rocódromo.