https://a.storyblok.com/f/270183/49911/2fbe0055cb/blog_home-surveillance_node-raspberrypi_1200x600.png

Sistema de vigilância doméstica com o Node e um Raspberry Pi

Publicado em May 19, 2020

Tempo de leitura: 41 minutos

Você já se perguntou como montar um sistema de vigilância doméstica? Talvez para acompanhar seus filhos, cuidar de pessoas vulneráveis em casa ou como sistema de segurança residencial? Este tutorial vai orientá-lo nos passos iniciais para montar um.

Neste tutorial, você aprenderá a montar um sistema de vigilância doméstica pequeno e econômico usando um Raspberry Pi 4 com um módulo de câmera Raspberry Pi e um sensor de movimento. A parte de software utilizará Video API da Vonage (anteriormente TokBox OpenTok) para transmitir o vídeo e a Messages API da Vonage para notificar o usuário por SMS quando for detectado movimento.

Aqui estão algumas das coisas que você aprenderá neste tutorial:

Pré-requisitos

  • Raspberry Pi 4

  • Módulo de câmera do Raspberry Pi

  • Sensor de movimento (HC-SR501 PIR)

  • TokBox Account

  • Node e NPM instalados no Raspberry Pi

Instalação e configuração do Raspberry Pi

A Fundação Raspberry Pi é uma instituição de caridade sediada no Reino Unido que permite que pessoas em todo o mundo resolvam problemas tecnológicos e se expressem criativamente, utilizando o poder da computação e das tecnologias digitais no trabalho.

No site deles há um ótimo guia passo a passo sobre o que é cada parte do dispositivo Raspberry Pi, como instalar o sistema operacional e como começar a usar um Raspberry Pi. Há também muitos outros recursos para ajudar na solução de problemas que você possa estar enfrentando, além de vários outros projetos que podem ser do seu interesse.

Instalação de câmeras e sensores de movimento

Instalação do módulo de câmera do Raspberry Pi

Este tutorial utiliza um Raspberry Pi 4 e o módulo oficial de câmera do Raspberry Pi, embora não deva haver problemas ao usar outras câmeras.

A foto abaixo mostra o Raspberry Pi e um módulo de câmera utilizados neste artigo:

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

Conecte o módulo de câmera por meio do cabo de fita à porta do módulo de câmera do Raspberry Pi. A foto abaixo mostra onde você deve instalar o cabo de fita do módulo de câmera:

Raspberry Pi with Camera Ribbon InstalledRaspberry Pi with Camera Ribbon Installed

Ativando o SSH e a câmera

Secure Shell (SSH) é um pacote de software que permite uma conexão segura e o controle de um sistema remoto. O Raspberry Pi neste tutorial será executado no modo headless, ou seja, sem monitor, teclado ou mouse. Com o SSH ativado, você poderá se conectar remotamente ao dispositivo a partir do seu computador ou celular.

Para habilitar o SSH, no terminal do Raspberry Pi, execute:

sudo raspi-config

Você verá uma tela semelhante à imagem abaixo:

Raspberry Pi ConfigurationRaspberry Pi Configuration

Escolha a opção 5 - Interfacing Options

  • No próximo menu, escolha a opção P1 para Camera, e, em seguida, selecione Yes,

  • Em seguida, selecione a Opção P2 para SSH, selecione novamente Yes.

Você já ativou o módulo de câmera e o SSH no seu Raspberry Pi.

Instalação do sensor de movimento

O próximo passo é conectar o Raspberry Pi a um sensor de movimento. Este tutorial utiliza o sensor de movimento PIR HC-SR501; no entanto, outros módulos de sensor de movimento também devem funcionar bem. Consulte os guias de conexão correspondentes para conectá-los ao seu Raspberry Pi.

Primeiro, pegue o sensor e conecte três fios a ele. Usei o vermelho para a fase, o azul para o GPIO e o preto para o terra. Para o sensor deste exemplo, o primeiro pino é o terra, o segundo é o GPIO e o terceiro é a fase, conforme mostrado:

Example of Motion SensorExample of Motion Sensor

Um ótimo exemplo para descrever cada um dos pinos do Raspberry Pi está no site do Raspberry Pi. O diagrama ilustra a disposição dos pinos GPIO, conforme mostrado abaixo:

Diagram of Raspberry Pi GPIO PinsDiagram of Raspberry Pi GPIO Pins

A etapa final consiste em conectar os fios ao Raspberry Pi. O fio de fase (vermelho) precisa ser conectado a um dos 5V power pinos do Pi; consultando o diagrama acima, usei o pino 2. O fio de terra (preto) precisa ser conectado a um dos GND pinos do Pi; novamente, consultando o diagrama, usei o pino 6. O último fio a ser conectado é o fio GPIO (azul), que deve ser conectado a um dos GPIO pinos. Neste exemplo, usei o pino 12, identificado como “GPIO 18”.

A configuração final da fiação é mostrada a seguir:

Sensor Writing Part 2Sensor Writing Part 2

Testando a detecção de movimento

Agora que todo o hardware está instalado e configurado, é hora de desenvolver o código para o projeto. No entanto, primeiro é preciso criar um projeto Node para realizar os testes de detecção de movimento e nos prepararmos para o projeto que está por vir. É nesse projeto que você escreverá todo o código de detecção de movimento e transmissão de Video. Para criar um novo projeto Node, crie um novo diretório, navegue até ele e execute npm init. A execução dos comandos listados abaixo realiza essas três etapas:

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

Siga as instruções solicitadas, defina um nome para o projeto e deixe o restante das opções como padrão.

Os comandos a seguir criam um novo index.js, que armazenará a maior parte do seu código, e instalam um novo pacote chamado onoff que permite o controle dos pinos GPIO:

touch index.js npm install onoff

No seu novo index.js arquivo, copie o código a seguir, que lê o pino GPIO 18 para alertar se for detectado movimento ou quando o movimento parar.

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');
    }
});

É hora de verificar se o código acima e a instalação do sensor de movimento foram bem-sucedidos. Execute:

node index.js

Acene com a mão na frente do sensor de movimento e, em seguida, observe o Terminal para ver a mensagem “Movimento detectado!”. Alguns segundos depois, você verá a mensagem “Movimento interrompido”.

Testando a câmera

Na linha de comando do seu Raspberry Pi, digite o seguinte comando para tirar uma foto da imagem captada pela câmera.

NOTA Se você tiver feito login como um usuário diferente do padrão pi, substitua pi pelo seu nome de usuário.

raspistill -o /home/pi/cam.jpg

Ao acessar o diretório /home/pi/ , você verá agora cam.jpg. Ao abri-lo, você verá uma foto da imagem atual capturada pela câmera do seu Raspberry Pi.

Node e NPM

node --version npm --version

Tanto o Node quanto o NPM precisam estar instalados e na versão correta. Acesse nodejs.org, baixe e instale a versão correta, caso ainda não a tenha.

Nossa CLI

Configure o CLI do Vonage usando este guia. Você só precisa do Instalacão e Configurar sua configuração .

Git (opcional)

Você pode usar o git para clonar o aplicativo de demonstração do GitHub.

Para quem não se sente à vontade com os comandos do Git, não se preocupe, eu te ajudo.

Siga este guia para instalar o git.

Instalar um servidor MySQL

No Raspberry Pi, execute o seguinte comando para instalar o servidor de banco de dados MySQL:

sudo apt install mariadb-server

Por padrão, o servidor MySQL é instalado com o root usuário sem senha. É preciso corrigir isso para garantir que o banco de dados não fique vulnerável. No Pi, execute o comando abaixo e siga as instruções.

sudo mysql_secure_installation

Agora que a root senha do usuário está definida, é hora de criar um banco de dados e um usuário para acessá-lo. Conecte-se ao servidor MySQL:

sudo mysql -u root -p

Agora, execute as seguintes consultas SQL para criar um novo usuário e conceder a ele alguns privilégios em um novo banco de dados:

-- 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;

Seu Raspberry Pi já está configurado e pronto para a parte de programação deste tutorial.

Criação do aplicativo

Instalação de um certificado SSL

No terminal do seu Raspberry Pi, navegue até o diretório do seu projeto e execute o comando a seguir para gerar um certificado SSL autoassinado. A Video API da Vonage exige o uso de HTTPS para acesso; portanto, é necessário um certificado SSL, mesmo que seja autoassinado. Execute o comando abaixo para gerar seus certificados SSL.

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

São criados dois arquivos, key.pem e cert.pem, mova-os para um local ao qual seu código tenha acesso. Para este tutorial, eles estão no diretório do projeto.

O servidor web

Express é um framework minimalista e flexível para aplicativos web em Node.js que oferece um conjunto robusto de recursos para aplicativos web e móveis.

O Express é um framework Node.js muito leve e flexível, exatamente o que você precisa neste projeto. Ele fornece endpoints para que você possa acessar sua transmissão de Video.

Instale o Express em seu aplicativo com o seguinte comando:

npm install express --save

No início do index.js arquivo, você precisa importar os pacotes https, fs e express. Faça as seguintes alterações:

+ 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');
    }
});

Você não precisa da else parte da detecção de movimento para este tutorial. Portanto, remova essa parte também, conforme mostrado acima.

Você precisa de um servidor web para acessar sua transmissão de Video pela rede ou pela Internet. É hora de criar um método para iniciar um novo servidor com um endpoint de exemplo. Acima pir.watch(function(err, value) { adicionar

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;
  });
}

Agora é preciso encontrar uma maneira de acessar essa função; abaixo da sua função startServer() {} adicione uma chamada à função, conforme mostrado:

startServer();

Para verificar se isso está funcionando, no Terminal, execute:

node index.js

Observação: Se você estiver conectado ao seu Raspberry Pi via SSH ou teclado/TV, digite no Terminal: ifconfig para descobrir o endereço IP local do seu Raspberry Pi.

Acessando o endereço IP do seu Raspberry Pi no navegador: https://<ip address>:3000/ irá retornar

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

Instalando o Sequelize

Sequelize é uma biblioteca poderosa para o Node que facilita a consulta a bancos de dados. Trata-se de um mapeador objeto-relacional (ORM), que mapeia objetos para os esquemas de banco de dados. O Sequelize suporta vários protocolos, como Postgres, MySQL, MariaDB, SQLite e Microsoft SQL Server. Este tutorial utilizará o servidor MariaDB, pois esse é o servidor SQL disponível no 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 do diretório do seu projeto, crie um novo arquivo .enve atualize os valores abaixo com as credenciais corretas do seu banco de dados.

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

Dentro do config diretório, crie um novo arquivo chamado config.js. Esse arquivo é onde as configurações do banco de dados do projeto são armazenadas e, por ser em JavaScript, ele pode acessar o .env arquivo:

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
  },
}

Agora, em models/index.js, localize e substitua:

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

De volta ao seu arquivo principal index.js , importe o models/index.js arquivo para que seu aplicativo acesse os modelos do banco de dados:

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

Gerando e executando uma migração

Quando uma sessão do Vonage Video é criada, é retornado um ID de sessão; esse ID precisa ser armazenado em algum lugar para que você possa se conectar a ela remotamente. A melhor maneira de fazer isso é por meio de uma tabela de banco de dados. Usando o Sequelize CLI instalado recentemente, execute o comando abaixo. Ele cria uma nova tabela chamada Session, com duas novas colunas:

  • sessionId (que é uma string),

  • ativo (que é um valor 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

Após a execução bem-sucedida desse comando, são criados dois novos arquivos, a saber:

  • models/session.js

  • migrations/<timestamp>-Session.js

O novo modelo, session.js, define o que o banco de dados espera em termos de nomes de colunas, tipos de dados, entre outras coisas.

O novo arquivo de migrações define o que deve ser gravado no banco de dados quando a migração for bem-sucedida. Neste caso, ele cria uma nova tabela no banco de dados chamada sessions com cinco novas colunas:

  • id

  • sessionId

  • ativo

  • createdAt

  • atualizado em

Execute esta migração usando o comando da CLI do Sequelize com os parâmetros db:migrate:

sequelize db:migrate

O resultado será o mesmo do exemplo abaixo:

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

Agora você tem uma nova tabela no banco de dados que será usada posteriormente para armazenar o ID da sessão.

Vonage Video

Você está prestes a instalar duas bibliotecas necessárias para o projeto: a Vonage Video (anteriormente conhecida como TokBox OpenTok) e a Puppeteer.

O Vonage Video (anteriormente TokBox OpenTok) é um serviço que oferece sessões de vídeo interativas ao vivo para usuários em todo o mundo. A Video API do Vonage Video (anteriormente TokBox OpenTok) utiliza o padrão industrial WebRTC. Ela permite que os usuários criem experiências de vídeo personalizadas em bilhões de dispositivos, sejam eles aplicativos móveis, da web ou para desktop.

O Puppeteer é uma biblioteca do Node que oferece um método para controlar o Chrome ou o Chromium por meio de programação. Por padrão, o Puppeteer é executado no modo headless, mas também pode ser executado no modo não headless do Chrome ou do Chromium. Um navegador headless é um navegador sem interface gráfica de usuário (ou seja, sem monitor para o usuário visualizar).

Instale essas duas bibliotecas executando o comando abaixo:

npm install opentok puppeteer

Copie as alterações no código do seu index.js conforme mostrado abaixo. Esse código importa três bibliotecas para o seu projeto.

  • OpenTok (Para publicar/assinar um fluxo de Video com o Vonage Video)

  • Puppeteer (Para que seu Raspberry Pi abra um navegador no modo headless para publicar a transmissão)

  • DotEnv (Para acessar as variáveis do arquivo .env)

Um objeto OpenTok é inicializado usando sua chave e seu segredo da API da Vonage, variáveis .env que você ainda precisa adicionar.

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,
+ );

Você precisará da sua chave e do seu segredo da Video API do Vonage. Você pode encontrá-los fazendo login na sua Account da Video API da Vonage.

Em seguida, crie um novo projeto. Após a criação, você verá o painel do seu projeto, que contém a chave da API e o segredo da API.

No seu .env arquivo, adicione as credenciais do Vonage Video conforme abaixo (atualizando os valores dentro < e > com suas credenciais):

VONAGE_VIDEO_API_KEY= VONAGE_VIDEO_API_SECRET=

Criação de uma sessão de vídeo do Vonage

No seu index.js arquivo, localize a parte do código que inicializa o objeto OpenTok e adicione três variáveis chamadas:

  • canCreateSession, determina se o seu projeto pode criar uma sessão ou não (caso já haja uma sessão ativa)

  • session, é a variável que armazena o objeto da sessão atual

  • url é a variável que armazena a URL atual da sessão (neste caso, uma URL do 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;

É hora de criar uma sessão e armazenar o ID da sessão retornado no banco de dados para uso quando o usuário clicar no link para visualizar a transmissão publicada. Copie o código abaixo para adicionar as funções que realizam isso:

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;
    });
}

A parte do projeto responsável pelo observador de sessão precisa ser atualizada para determinar se canCreateSession é verdadeiro; se for esse o caso, defina-o como falso (para que nenhum outro fluxo seja criado enquanto este estiver ativo) e, em seguida, crie a sessão chamando o método adicionado anteriormente ao projeto createSession. Isso é feito atualizando o código a seguir:

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

+       createSession();
    }
});

Criação de um emissor e um assinante

É necessário um novo diretório que contenha as páginas de interface para que o Pi publique seu fluxo e para que o cliente (você) se inscreva nesse fluxo. Crie um novo public diretório com o arquivo correspondente css, js, e config com os comandos abaixo:

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

Você vai precisar de alguns estilos para a sua página, que o cliente vai ver; então, crie um novo app.css arquivo dentro public/css/ e copie o código abaixo nesse arquivo. O CSS abaixo garante que o conteúdo ocupe 100% da altura, que a cor de fundo seja cinza e que o Video seja exibido em tela cheia para máxima visibilidade.

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;
}

Em seguida, você precisará criar um novo arquivo JavaScript que será usado no lado do cliente (ou seja, no seu navegador, como assinante). Esse arquivo inicializará uma sessão do Vonage Video, obterá os detalhes da sessão do backend por meio de uma solicitação GET e, se a rota for /serve publicará o stream se o caminho da URL for /client ele se inscreverá no stream de vídeo ativo no momento. Em public/js/ Crie um novo app.js arquivo e copie o código a seguir nele:

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} );
}

São necessários dois novos HTML arquivos são necessários para esses dois novos pontos de extremidade /serve e /client, eles utilizam a biblioteca JavaScript do lado do cliente da Vonage Video para publicar ou assinar as sessões ativas no momento.

Crie um novo server.html arquivo dentro do public/ diretório com o seguinte conteúdo:

<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 o /client ponto de extremidade, crie um novo client.html arquivo dentro do public/ diretório e copie o código a seguir:

<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>

Você ainda não definiu os endpoints no seu código de backend (index.js), então é hora de criá-los! Encontre o endpoint original que você criou:

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

Substitua-o pelo seguinte 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,
  }));
});

Se você observar com atenção o código acima, verá que está usando uma nova biblioteca chamada path. Portanto, no início do index.js arquivo, inclua o caminho conforme mostrado abaixo:

const path = require('path');

Nada acontece até que você publique a exibição no Raspberry Pi.

No interior .env adicione outra variável (60.000 milissegundos equivalem a 60 segundos):

VIDEO_SESSION_DURATION=60000

De volta ao código index.js adicionar uma funcionalidade que feche o stream quando a função closeSession() for chamada:

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
    });
  }
}

Agora é hora de criar a publicação do stream no modo headless; a função abaixo realiza todas as seguintes ações no modo headless:

  • Cria uma nova instância do navegador,

  • Abre uma nova página/aba,

  • Substitui as permissões para a câmera e o microfone no navegador,

  • Redireciona a página para o /serve ponto de extremidade para publicar a transmissão de Video,

  • Cria um novo temporizador para interromper a transmissão de Video após um determinado período de tempo,

  • Cria outro temporizador para servir de intervalo entre o término de um stream e o momento em que outro pode começar

Copie o código abaixo no seu index.js arquivo:

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);
}

É hora de usar a função que você acabou de incluir no seu projeto; encontre-a e adicione-a startPublish() ao seu código:

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

Você está quase pronto para testar seu código! Você criou novos endpoints, acessíveis tanto como emissor quanto como assinante do Video. Agora, você precisa de uma URL para acessar a transmissão caso esteja em um local remoto.

Ngrok

Se você deseja se conectar remotamente ao stream da câmera, fora da rede à qual o Raspberry Pi está conectado, será necessário expor seu servidor web à Internet. É hora de instalar e usar o o Ngrok.

Ao executar o comando abaixo, o Ngrok será instalado apenas localmente para o projeto:

npm install ngrok

Agora você precisa implementar o uso do Ngrok no seu projeto. Portanto, no início do index.js arquivo, inclua o ngrok pacote:

const ngrok = require('ngrok');

Agora você precisa criar uma função que se conecte ao Ngrok. Se for bem-sucedida, ela salvará a URL retornada em um arquivo public/config/config.txt , que será recuperado no arquivo criado nas etapas anteriores public/client.html. No seu index.js arquivo, adicione o seguinte:

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!');
  });
}

Agora que tudo já está configurado, você pode acessar o Ngrok chamando a connectNgrok() função conforme mostrado abaixo:

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

+   connectNgrok();

  return true;
});

Agora você pode testar sua transmissão. Execute o seguinte comando no terminal do Raspberry Pi:

node index.js

Após cerca de 10 segundos (para que o serviço seja inicializado), acene com a mão na frente do sensor de movimento. Se der certo, você verá uma Motion Detected! mensagem na janela do Terminal. Agora acesse o arquivo no seu Raspberry Pi public/config/config.txt, copie essa URL e cole-a no seu navegador. Acrescente /client ao final da URL. No meu caso, foi https://gregdev.eu.ngrok.io/client. Seu navegador agora exibirá a transmissão publicada do seu Raspberry Pi, que abriu uma instância do navegador Chromium sem interface gráfica e navegou até seu IP local: https://localhost/serve.

Instalando o Vonage Messages

Para usar a nova Messages API do Vonage, que envia mensagens SMS sempre que é detectado movimento, você precisará instalar a versão beta do nosso SDK para Node. Execute o seguinte comando:

npm install @vonage/server-sdk

A Messages API exige que você crie um aplicativo no portal de desenvolvedores da Vonage, além de um private.key que é gerado ao criar o aplicativo. Executar o comando abaixo cria o aplicativo, configura os webhooks (que não são necessários no momento, portanto, deixe-os como estão entre aspas) e, por fim, gera um arquivo de chave chamado 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

Agora que você criou o aplicativo, é preciso definir algumas variáveis de ambiente. Você encontrará suas API key e API secret no Painel do Desenvolvedor da Vonage.

O VONAGE_APPLICATION_PRIVATE_KEY_PATH é o local do arquivo que você gerou no comando anterior. Neste projeto, ele estava armazenado no diretório do projeto; por exemplo: /home/pi/pi-cam/my_messages_app.key

O VONAGE_BRAND_NAME não é usado neste projeto, mas é necessário ter um configurado para a Messages API; mantive a configuração simples HomeCam

Por fim, o TO_NUMBER é o destinatário que recebe a notificação por SMS.

VONAGE_API_KEY= VONAGE_API_SECRET= VONAGE_APPLICATION_PRIVATE_KEY_PATH= VONAGE_BRAND_NAME=HomeCam TO_NUMBER=

Na parte superior do seu index.js , importe o pacote Vonage:

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

Para criar o objeto Vonage, que é usado para fazer as solicitações à API, na definição do objeto OpenTok, adicione o seguinte:

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,
});

No interior da função e no final da sua connectNgrok() função, adicione uma funcionalidade que atualize seu aplicativo Vonage com webhooks para lidar com mensagens recebidas e o status da mensagem usando a URL correta (a URL do 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);
  }
});

Envio de uma mensagem SMS

O método de notificação escolhido para este tutorial é o SMS, enviado por meio da Messages API. A biblioteca da Vonage já foi instalada neste projeto, portanto, não é necessário configurá-la. No index.js arquivo, adicione uma nova função chamada sendSMS(), que recebe a URL e o número no qual você espera receber o SMS. Em seguida, usando a Messages API, ela envia uma notificação por SMS informando que a câmera detectou movimento.

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 },
  );
}

Agora chame a sendSMS() função adicionando:

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

Pronto! Agora basta acessar seu Raspberry Pi via SSH e iniciar o servidor dentro do diretório do seu projeto, executando:

node index.js

Seu servidor já está em funcionamento, e seu Raspberry Pi deve detectar movimento; caso isso ocorra, ele fará o seguinte:

  • Inicie uma sessão do OpenTok,

  • Salve o ID da sessão no banco de dados,

  • Envie um SMS para o número de telefone que você definiu com um link para a transmissão,

  • Inicie um fluxo de publicação a partir do Raspberry Pi.

Você acabou de montar seu próprio sistema de vigilância residencial em pouco tempo, ao qual pode acessar de qualquer lugar do mundo!

O código final deste tutorial pode ser encontrado no repositório do GitHub.

Abaixo estão alguns outros tutoriais que elaboramos sobre a implementação da Video API da Vonage em projetos:

Não se esqueça: se tiver alguma dúvida, sugestão ou ideia que gostaria de compartilhar com a comunidade, sinta-se à vontade para participar do nosso espaço de trabalho do Slack da nossa comunidade. Adoraria receber feedback de quem já colocou este tutorial em prática e saber como está o seu projeto.

Compartilhar:

https://a.storyblok.com/f/270183/250x250/b052219541/greg-holmes.png
Greg HolmesEx-funcionários da Vonage

Ex-educador de desenvolvedores na @Vonage. Tenho formação em PHP, mas não me limito a uma única linguagem. Sou um jogador ávido e entusiasta do Raspberry Pi. Costumo praticar escalada em rocha em centros de escalada indoor.