Introduction: Detecção De Enchente/terremoto Com Raspberry Pi E AWS IoT Greengrass

Projeto criado e escrito por: Rafael Henrique da Silva Correia, Anderson Bernardo de Almeida e Johnny Eric Amancio

Este projeto tem como foco usar alguns componentes que nos foram cedidos pela UFScar Sorocaba e com estes componentes criar um detector de enchente e terremoto. Vale ressaltar que existem alguns componentes no mercado muito mais eficazes para fazer tal detecção, porém nosso foco era extrair ao máximo dos componentes que nos foram cedidos.

Para este projeto usamos os componentes (hardware):

  • 1x Raspberry Pi Modelo B (rev. 1)
  • 1x Sensor de Tilt para medir os tremores
  • 1x Sensor de luminosidade LDR
  • 1x Sensor de temperatura NTC (100 kΩ - kilo ohm)
  • 1x Sensor de nível de líquidos
  • 2x Resistores de 10 kΩ
  • 1x Resistor de 330 Ω
  • 2x Resistores de 1 kΩ
  • 2x Capacitores eletrolíticos de 1 μF (micro Farad)
  • Vários jumpers macho-fêmea e macho-macho
  • 1x Breadboard tamanho full-size

Step 1: Montagem

Em nosso projeto é necessária a leitura de dados de sensores analógicos e de sensores digitais.

A placa Raspberry PI conta apenas com entradas digitais. Para o uso de sensores analógicos utilizamos um circuito alternativo "resistor-capacitor" (Circuito RC). Este circuito não apresenta uma grande precisão, mas foi a forma que encontramos para efetuar a leitura analógica uma vez que não dispomos de um conversor Analógico/Digital (ADC).

Basicamente ele opera carregando o capacitor até que a tensão alcançada corresponda ao nível lógico 1. Quando isso ocorrer, o capacitor é descarregado (via algoritmo implementado) e uma nova carga inicia. O algoritmo calcula o tempo necessário para a carga (que depende do valor da resistência do sensor de luminosidade - LDR - ou de temperatura - NTC), fornecendo assim uma aproximação dos valores analógicos.

Os sensores digitais utilizados foram o de Tilt, que é acionado quando ocorre alguma vibração, e o sensor de nível de líquido que consiste em uma chave magnética (Reed Switch) selada em uma cápsula impermeável e um ímã acoplado a uma boia que faz o acionamento da chave quando a água atinge o sensor.

Para a montagem destes circuitos foram usados diversos tutoriais da comunidade. Alguns se referem a outras placas, sendo adaptadas à placa Raspberry:

  1. Tutorial: como utilizar o Sensor Tilt com Arduino: Fazer a ligação do sensor de TILT ao Arduino;
  2. Reading Analogue Sensors With One GPIO Pin: Ler valores analógicos no Raspberry Pi usando um capacitor;
  3. TERMISTOR NTC COM ARDUINO: Fazer a ligação do sensor de NTC ao Arduino;
  4. Ligando um Sensor de Nível de Líquidos ao Arduino: Fazer a ligação do sensor de nível de líquidos ao Arduino;

  5. Raspberry Pi Light Sensor: A Simple LDR Tutorial: Fazer a ligação do sensor LDR de líquidos ao Raspberry;

Step 2: Leitura Dos Dados Analógicos

Uma biblioteca muito similar a dos exemplos do step 1 foi criada para fazermos nossas leituras analógicas, esta pode ser vista abaixo:

# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time


GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)

def volts_to_centigrade(voltage, vcc):
    voltage = voltage * vcc / 1024.0
    temperature = (voltage - 0.5) * 100
    return round(temperature, 2)

class RaspberryAnalogSensor(object):

    def __init__(self, pin):
        self.pin = pin

    def read(self):
        reading = 0
        GPIO.setup(self.pin, GPIO.OUT)
        GPIO.output(self.pin, GPIO.LOW)
        time.sleep(0.1)
        GPIO.setup(self.pin, GPIO.IN)

        while(GPIO.input(self.pin) == GPIO.LOW):
            reading += 1
        return reading

if __name__ == '__main__':
    while True:
        light_sensor = RaspberryAnalogSensor(pin=17)
        print("light_sensor: {}".format(light_sensor.read()))
        temperature_sensor = RaspberryAnalogSensor(pin=18)
        print("temperature_sensor: {}".format(temperature_sensor.read()))
        time.sleep(1)

Transformamos os exemplos demonstrados em uma classe que pode ser usada em vários scripts para facilitar a reutilização de código. Ao executar o script acima da forma que ele está começaremos a ler os dados da porta 17 e 18 do Raspberry que são nossos sensores LDR e NTC respectivamente.
No método "read" da Classe RaspberryAnalogSensor é realizado o procedimento para a leitura dos dados do Circuito Resistor-Capacitor: O capacitor é descarregado (GPIO.LOW) e em seguida a porta é colocada em modo de entrada (GPIO.IN) permitindo o início da carga do capacitor. O loop (while) repete enquanto a carga do capacitor não for suficiente para mudar o estado lógico da porta, incrementando um contador. Um fator de imprecisão deste método é que ele é dependente da velocidade de processamento da placa.

Vale ressaltar que a função volts_to_centigrade não está sendo usada no momento pois não conseguimos determinar o cálculo para a conversão do valor aferido (utilizando o potenciômetro) para graus centígrados.

Step 3: Leitura Dos Dados Digitais

Para realizar a leitura dos sensores TILT e nível de líquido (que são digitais) estamos usando a biblioteca libsoc-zero versão 0.0.3. Acabamos por optar por esta biblioteca por já ter realizado experimentos anteriores com a leitura de sensores digitais e ter funcionado muito bem. O único trabalho que temos é compilar as bibliotecas libsoc e 96BoardsGPIO previamente. As instruções de compilação podem ser encontradas no README do repositório.

Um script simples para a leitura de dados digitais pode ser visto abaixo:

# -*- coding: utf-8 -*-
from libsoc_zero.GPIO import Tilt, Button

def tilt_event(): print("Tilt activated!")

def water_level_event(): print("Water level activated!")

if __name__ == '__main__':

tilt = Tilt('GPIO_4') tilt.when_pressed(tilt_event)

water_level = Button('GPIO_25') water_level.when_pressed(water_level_event)

Criamos 2 callbacks (water_level_event e tilt_event) onde:

  • tilt_event é acionado quando acontece um tremor próximo ao sensor de tilt;
  • water_level_event é acionado quando o nível de água sobe.

Step 4: Enviando Dados Ao Serviço AWS IoT Greengrass

Nesta etapa é realizada a transmissão dos dados coletados ao serviço da AWS IoT Greengrass. Enviaremos os dados para a nuvem para posteriormente analisar estes dados dentro do Elasticsearch que será usado no serviço AWS Elasticsearch.

Precisaremos fazer essa análise dos dados para detectar anomalias na temperatura e na iluminação, pois hipoteticamente falando quando a temperatura aumenta de forma rápida e a iluminação diminui temos certa chance de chover nos próximos minutos.

Para enviar dados ao Greengrass usaremos a biblioteca paho-mqtt versão 1.3.0. Antes precisaremos configurar o serviço da Amazon para receber nossas mensagens.

Step 5: Criando Things

Após criar sua conta na AWS (se desejar você poderá usar o Free Tier que é o nível gratuito da AWS por 1 ano), vá até o painel do Greengrass.

Tudo no AWS Greengrass começa com a criação de Things. Uma Thing representam uma “coisa” no mundo real. Em nosso projeto teremos 5 Things denominadas:

  • twitter_interactions: Para receber dados do Twitter relativos a enchentes e terremotos;
  • tilt_sensor: Para receber dados do sensor de TILT;
  • water_level_sensor: Para receber dados do sensor de nível de água;
  • light_sensor: Para receber dados do sensor LDR;
  • temperature_sensor: Para receber dados do sensor NTC.

É importante citar a dificuldade que tivemos inicialmente ao gerar gráficos a partir do Greengrass pois o mesmo não oferece serviço de gráfico de dados recebidos dentro dele e sim a partir de integrações com outros serviços da AWS. Desta forma optamos pelo Elasticsearch por já ter integração com o Kibana que é uma ótima ferramenta para visualização de dados e geração de gráficos.

Para criar as Things é bem simples, você poderá ver as imagens acima para entender melhor como isso funciona, caso prefira a AWS também fornece uma documentação muito completa para isso neste link.

Se você chegou até o final da criação neste momento você deverá ter um arquivo zip com um conteúdo bem similar ao da última imagem.

Para criar as 5 Things mencionadas usamos o mesmo procedimento.

Step 6: Usando O Elasticsearch

Como mencionado anteriormente, para este exercício decidimos usar o Elasticsearch por ele estar integrado com o Kibana que é uma ferramenta poderosíssima para a geração de gráficos e análise de dados.

Para criar um domínio de Elasticsearch é bem simples, e para não pagar nada, criamos um servidor incluído no Free Tier. Vá até o serviço AWS Elasticsearch no console da AWS e clique em "Create a new domain" e para não pagar nada (Free Tier) use as configurações abaixo:

Elasticsearch domain name: iot (nome do domínio, pode ser outro, mas escolhemos iot)
Elasticsearch version: 5.3 Instance count: 1 Instance type: t2.small.elasticsearch Storage type: EBS EBS volume type: General Purpose (SSD) EBS volume size: 10 GB Automated snapshot start hour: 00:00 UTC Access Policy: { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "es:*", "Resource": "arn:aws:es:us-east-1:XXXXX:domain/iot/*", "Condition": { "IpAddress": { "aws:SourceIp": "[seu ip externo aqui]" } } } ] }

É importantíssimo configurar o Access Policy da maneira correta, senão não será possível acessar o Kibana. A configuração acima foi montada com base nos links abaixo:

Explicando como funciona: No statement do Access Policy é liberado o acesso ao meu ip externo para que eu possa acessar o recurso.

Nenhuma configuração foi realizada no IAM porém é possível criar roles/grupos/usuários por lá para terem acesso ao recurso.

Após finalizada a configuração do domínio o processo de criação demora cerca de 10 minutos para ser concluído. Depois de terminado o “processo de subida” poderemos ver uma tela similar a que está no topo deste step.

Step 7: Acessando O Kibana Na AWS

O Elasticsearch trabalha com indexes e o Kibana consulta esses indexes via API do Elasticsearch para mostrar os dados na tela, então inicialmente não teremos indexes criados e muito menos itens dentro desses indexes.

Então vamos criar nosso index IoT utilizando o endereço que nos é passado no campo Endpoint do nosso domínio Elasticsearch. Todas as operações no Elasticsearch são feitas por API então precisaremos mandar uma requisição HTTP com o verbo PUT para criar o nosso index, como mostrado abaixo:

curl -X PUT 'https://search-iot-xpto.us-east-1.es.amazonaws.com/iot?pretty' -H 'content-type: application/json' -d \
'{ "mappings": { "temperature": { "properties": { "state": { "properties": { "reported": { "properties": { "temperature": { "type": "long" }, "timestamp": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" } } } } } } } } }'

Este index criado aceitará payloads no formato:

{
"state": { "reported": { "temperature": 10, "timestamp": "2017-06-27 00:47:00" } } }

É importante citar que estamos criando o index inicialmente para termos pelo menos um padrão de dados para receber, pois os próximos dados que forem enviados a este index serão estruturados de forma similar sem a necessidade de atualizar este index manualmente.

Este será o formato utilizado por nosso script de publisher que enviará dados para o tópico MQTT criado no AWS Greengrass sendo que cada Thing terá seu tópico para receber dados. Após criado este index selecione o mesmo como index padrão no Kibana.

Step 8: Criando Rules Para As Things

As rules nos permitem enviar dados ou tomar ações baseados nos dados que chegam para uma Thing. No nosso caso para cada Thing vamos criar uma Rule para enviar estes dados para o Elasticsearch para posterior análise.

Para criar uma rule siga as imagens anexadas a este step.

Step 9: Envio De Dados Para O Tópico Da Thing Criada

Para enviar os dados como já foi mencionado anteriormente usaremos a biblioteca paho-mqtt versão 1.3.0. Um script muito simples pode ser visto em:

Este script simplista foi nosso ponto de partida. Porém construímos uma versão muito mais robusta e que estava mais alinhada com o nosso projeto, Esta solução baseia-se em usar o publish single, que abre uma conexão e envia o dado ao tópico MQTT, fechando a conexão em seguida segundo a própria documentação do paho.

Só encontramos um pequeno problema que era logar as conexões realizadas pelo single de modo a estudar como isso funcionava. Para isso desenvolvemos uma biblioteca para sobrescrever algumas funções base do paho-mqtt e começar a usar os logs de que precisávamos. A solução final pode ser vista abaixo:

# -*- coding: utf-8 -*-
import json import ssl import socket import logging import time from datetime import datetime

import paho.mqtt.client as paho from paho.mqtt.publish import _on_publish, _on_connect

def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60, tls=None, protocol=paho.MQTTv311, transport="tcp", logger=None, debug=False): """ This is an modification from original function of paho mqtt python. Reference: https://github.com/eclipse/paho.mqtt.python/blob/master/src/paho/mqtt/publish.py#L60 """

if not isinstance(msgs, list): raise ValueError('msgs must be a list')

client = paho.Client(client_id=client_id, userdata=msgs, protocol=protocol, transport=transport)

if debug and logger: client.enable_logger(logger)

client.on_publish = _on_publish client.on_connect = _on_connect

if tls is not None: if isinstance(tls, dict): client.tls_set(**tls) else: # Assume input is SSLContext object client.tls_set_context(tls)

client.connect(hostname, port, keepalive) client.loop_forever()

def single(topic, payload=None, qos=0, retain=False, hostname="localhost", port=1883, client_id="", keepalive=60, tls=None, protocol=paho.MQTTv311, transport="tcp", logger=None, debug=False): """ This is an modification from original function of paho mqtt python. Reference: https://github.com/eclipse/paho.mqtt.python/blob/master/src/paho/mqtt/publish.py#L156 """ msg = {'topic': topic, 'payload': payload, 'qos': qos, 'retain': retain}

multiple([msg], hostname, port, client_id, keepalive, tls, protocol, transport, logger, debug)

class AWSPublisher(object):

def __init__(self, topic, root_cert_file, certificate_file, private_key_file, client_id, address, log_file='aws.log', port=8883, keepalive=60): self.log_file = log_file self.logger = self.get_logger() self.topic = topic self.root_cert_file = root_cert_file self.certificate_file = certificate_file self.private_key_file = private_key_file self.client_id = client_id self.address = address self.port = port self.keepalive = keepalive

def get_logger(self): logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG)

file_handler = logging.FileHandler(self.log_file) file_handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) logger.addHandler(file_handler)

return logger

def send(self, data, debug=False): """ Publish message on topic AWS IoT Greengrass Paramether: data(dict): Some dict with data Returns: None """ timestamp = datetime.utcnow().strftime("%Y-%m-%d %T") data.update({ 'timestamp': timestamp }) payload = { "state": { "reported": data, } }

try: single( self.topic, payload=json.dumps(payload), qos=0, retain=False, hostname=self.address, port=self.port, client_id=self.client_id, keepalive=self.keepalive, tls={ 'ca_certs': self.root_cert_file, 'certfile': self.certificate_file, 'keyfile': self.private_key_file, 'tls_version': ssl.PROTOCOL_TLSv1_2, 'ciphers': None, }, logger=self.logger, debug=debug)

except socket.gaierror as e: # For DNS resolution errors self.logger.exception('AWSPublisher exception')

except ssl.SSLError as e: # For connection errors self.logger.exception('AWSPublisher exception')

if __name__ == '__main__': import os base_dir = os.path.dirname(os.path.abspath(__file__)) keys_path = os.path.join(base_dir, 'light_sensor') root_cert_file = os.path.join(keys_path, 'root-CA.crt') certificate_file = os.path.join(keys_path, 'light_sensor.cert.pem') # need add valid certificate private_key_file = os.path.join(keys_path, 'light_sensor.private.key') # need add valid private key hostname = socket.gethostname() publisher = AWSPublisher(log_file='publisher.log', topic='$aws/things/light_sensor/shadow/update', # need create thing on aws named light_sensor root_cert_file=root_cert_file, certificate_file=certificate_file, private_key_file=private_key_file, client_id=hostname, address='hostname-xpto.iot.us-east-1.amazonaws.com') # change to your broker name on aws import random while True: start = time.time() time.sleep(1) sample_value = random.random() * 10 publisher.send({'light': sample_value}, debug=True) end = time.time() - start print("End time: {}".format(end))

Esta biblioteca foi criada em forma de classe para facilitar seu reuso assim como fizemos com as leituras digitais e analógicas. Nosso código final com o uso das bibliotecas criadas pode ser visto em:

Temos dois exemplos, um que lê o sensor de TILT (pino digital) e outro que lê o sensor NTC de temperatura (que simula um pino analógico - Circuito RC), para criar outros envios o código é o mesmo, modificando apenas a pinagem e o tópico que queremos enviar (de acordo com o nome da Thing criada na AWS).

Step 10: Gerando Gráficos No Kibana

Os gráficos do Kibana nos ajudaram a fazer a análise climática para detectar possíveis tempestades baseado na hipótese que quando a temperatura aumenta e a iluminação diminui poderá chover.

É importante citar que todos os gráficos foram gerados com base no cálculo de média, ou seja, a temperatura, a iluminação, os tremores, e o nível de água serão mostrados nos gráficos com base na média calculada sobre os dados obtidos. O Kibana também possibilita outros tipos de exibição, tal como: Contagem, Somatória, Mediana, Desvio padrão, entre outros.

Utilize as imagens em anexo para entender melhor como criar um gráfico no Kibana.

Step 11: Criando Um Tópico SNS Para Receber Emails

Neste step vamos criar um tópico SNS na Amazon para receber emails quando anomalias forem detectadas. Vamos mandar emails em casos em que:

  • A temperatura aumentar e a luz diminuir simultaneamente (que hipoteticamente seria um início de tempestade);
  • O TILT for acionado, o que indicaria um terremoto;
  • Mensagens sendo publicadas no twitter sobre algumas palavras chave para desastres (enchentes, terremotos, entre outras);

  • O sensor de nível de água for acionado, o que indicaria inundação em determinado local.

Para criar um tópico SNS siga as imagens contidas neste step. No exemplo que está no repositório o sensor de TILT já está enviando um SNS, podemos visualizar a função de envio em:

Em nosso projeto todos os sensores DIGITAIS (o sensor de nível de água e o TILT) enviarão alertas SNS.

Step 12: Analisando Dados De Temperatura E Luz Para Identificar Início De Tempestade

Nossa hipótese inicial era que com o aumento da temperatura repentino e diminuição de luz repentina existia uma alta possibilidade de chuva. Para detectar este padrão criamos um script para analisar os dados enviados ao Elasticsearch, este script pode ser visto aqui:

Explicando basicamente como funciona a sua lógica:

  1. Dada uma quantidade de tempo retroativo (no caso trabalhamos com 24hrs passadas) faz uma média de todas as medições de temperatura e todas as medições de iluminação;
  2. Estas médias são guardadas em uma estrutura de dados (deque) muito similar a uma fila, porém esta fila tem um tamanho limitado que é controlado via parâmetro (no caso utilizamos 9 pois achamos um bom tamanho);
  3. Como esta fila tem um tamanho limitado, supondo que usamos o tamanho 9, quando entrar a décima média a primeira será removida da fila;
  4. Criamos duas funções (is_decaying e is_raising) para nos dizer se a fila de temperatura está aumentando suas médias e se a fila de iluminação está diminuindo suas médias;
  5. Caso a iluminação aumente e a temperatura diminua no mesmo intervalo da medição consideramos que existe possibilidade de tempestade iminente e enviamos uma notificação SNS utilizando a função send_sns_notification.

Os valores mencionados acima em que a fila tem tamanho 9 e o tempo para calcular a média de valores é de 24hs foi baseada em dados analisados através dos gráficos do Kibana e apresentaram uma precisão bem interessante.

Várias outras medidas foram testadas anteriormente porém em tamanhos de fila menor que 9 a sensibilidade é muito alta e poucas variações são notificadas. Com uma quantidade de horas menor que 24 pode-se notar que os resultados são muito inconsistentes, justamente por que estamos calculando a média desse intervalo de valores e um período pequeno também aumenta a sensibilidade dos sensores fazendo com que pequenas alterações sejam notificadas.

Vale ressaltar que para chegar a estas conclusões analisamos dados durante dias, porém não nos baseamos de nenhuma base teórica prévia para chegamos a nossa conclusão.

Talvez com valores de fila maiores que 9 o resultado seja ainda mais preciso, mas precisaríamos de vários dias de testes a mais para conseguir analisar estes dados com mais segurança.

Step 13: Dados Obtidos Com O Kibana Ao Longo Dos Dias De Teste

Neste step adicionamos algumas imagens que são resultado do Kibana que nos auxiliaram para a análise de dados dos sensores.

Na primeira imagem podemos ver os gráficos de temperatura e luminosidade, onde a cor azul é a luminosidade e a cor verde é a temperatura. Algumas informações importantes:

  • Quanto mais claro o ambiente menor é o valor da resistência apresentada pelo LDR; por isso podemos ver que no período de 8hrs até as 17:30 temos uma queda no gráfico;
  • No caso do sensor NTC, quanto maior a temperatura, menor é o valor da resistência exibida. Podemos notar que a temperatura varia de uma forma muito menos intensa do que a luminosidade, mantendo-se constante em grande parte do tempo.
  • Dados interessantes podem ser vistos nos dois últimos gráficos da primeira imagem que são criados com o mecanismo de Heatmap do Kibana. Nestes dados cores mais intensas significam mais valor (menos calor e menos iluminação) e cores menos intensas significam menos valor (mais calor e mais iluminação).

Na segunda imagem temos gráficos de mais fácil interpretação, pois são nossos sensores digitais, cada pico representa um acionamento ou do sensor de nível de água (que detectou enchente) ou do sensor de tilt (que detectou um terremoto).

Step 14: Leitura De Mensagens Do Twitter - Criação De Chaves

Para este projeto foi utilizada a API de streams de mensagens do twitter, mais especificamente a API pública de streams "POST statuses / filter" (documentação dos parâmetros). Mais detalhes sobre os parâmetros serão apresentados mais a frente.

Primeiramente, será necessário obter as chaves de acesso às APIs do Twitter, para tal é necessário ir até a página de gerenciamento de aplicações (apps.twitter) e criar um novo App. Preencha os campos obrigatórios, escolha um nome para o aplicativo e finalize a criação do aplicativo. O aplicativo então deverá aparecer na listagem.

Clique no link do aplicativo recém criado para entrar na seção do aplicativo, nesta página iremos obter as chaves para conseguir efetivamente realizar requisições para as APIs do Twitter.

Navegue até a aba Keys and Access Tokens.

Para esta aplicação o nível de acesso pode ser apenas de leitura, então fique a vontade para navegar pelo link "modify access permissions" e selecionar a opção Read-only.

Iremos precisar das chaves:

  • Consumer Key (API Key)
  • Consumer Secret (API Secret)
  • Access Token
  • Access Token Secret

Obs.: Na primeira vez será necessário clicar no botão na parte inferior da página para gerar os tokens de acesso (Access Token e Access Token Secret).

Step 15: Leitura De Mensagens Do Twitter – Uso Da API Com Node.js

Para lidar com a API de streams do Twitter iremos usar um módulo npm chamado node-tweet-stream, que fornece uma interface simples para realizarmos o tracking de algumas palavras chave.

Requisitos: Para este passo iremos assumir que node e npm estejam instalados na máquina. Também, se estiver executando em Windows de preferência utilize um aplicativo de linha de comando que aceite comandos linux como o Cmder.

Execute os seguintes comandos na linha de comando em seu diretório de trabalho:

  1. mkdir twitter-consumer && cd twitter-consumer
  2. npm init (siga as instruções)
  3. npm install --save node-tweet-stream aws-iot-device-sdk
  4. touch app.js

A seguir está um trecho de código obtido da página do módulo, para executar copie o conteúdo no arquivo app.js. Também será necessário substituir as chaves de acesso pelas chaves obtidas no passo anterior.

var Twitter = require('node-tweet-stream')
  , t = new Twitter({
    consumer_key: '',
    consumer_secret: '',
    token: '',
    token_secret: ''
  });
 
t.on('tweet', function (tweet) {
  console.log('mensagem ', tweet);
})
 
t.on('error', function (err) {
  console.log('erro ', err);
})
 
t.track('sorocaba terremoto');
t.track('sorocaba enchente');

Para executar o código basta rodar node app.js

Parâmetro que podem ser usados:

track: Nesse parâmetro devemos passar as palavras ou conjunto de palavras que queremos obter. Na documentação é explicado que quando a palavra é separada por espaço significa que temos uma condição lógica equivalente ao operador AND, deste modo só serão filtrados tweets que corresponderem a todas palavras da condição. Quando separamos por vírgula as palavras estamos estabelecendo uma condição com o operador OR, e então poderemos obter tweets que correspondam a cada uma das palavras independentemente.

locations: Nesse parâmetro devemos uma lista de pares de longitude/latitude que representa uma "bounding box" de determinada região que desejamos filtrar tweets. Vale ressaltar que esse parâmetro não age em conjunto com o parâmetro track, o que pode causar confusão na hora de manipular as mensagens. Outro ponto é que mesmo estabelecendo uma "bounding box" da cidade de Sorocaba, foram recebidos tweets de outras regiões do país, por isso não iremos utilizar este parâmetro e iremos tratar da localização através da palavra chave com o nome da cidade de Sorocaba.

Exemplo de uso: t.location('-47.5700,-23.5882,-47.3024,-23.350'); // Cidade de Sorocaba

Step 16: Código Node.js De Leitura Do Twitter E Notificação SNS

Código em Node.js que realiza a leitura de tweets com filtro por algumas palavras chaves importantes e faz a publicação em um tópico SNS para disparo de notificações.

Projeto para referência: https://github.com/johnnyeric/Twitter-Alert

Definimos as seguintes combinações de palavras:

[
  'sorocaba enchente',
  'sorocaba terremoto enchente',
  'sorocaba terremoto',
  'sorocaba chuva forte',
  'sorocaba tempestade',
  'sorocaba alagamento',
  'sorocaba chuva alagamento',
  'sorocaba chuva tempestade',
  'sorocaba tremores',
]

Posteriormente definimos as palavras chave e uma expressão regular para extrair quais palavras acionaram o gatilho da API:

const keywords = ['enchente','terremoto','alagamento','tempestade','chuva', 'sorocaba', 'tremores'];
const pattern = new RegExp('(' + keywords.join('|') + ')','ig');

Quando recebemos um tweet devemos checar se ele contém a palavra "enchente", se sim uma notificação deverá ser enviada imediatamente.

// Para publicação de SNS foi usado
const AWS = require('aws-sdk');

// Para lista foi usada
const Deque = require('double-ended-queue');

// Função SNS

const sendSNS = message => {
  let payload = {
    default: message,
    GCM: `{ \"data\": { \"message\": \"${message}\" } } `
  };
  payload = JSON.stringify(payload);

  sns.publish({
    Message: payload,
    MessageStructure: 'json',
    TargetArn: '<topico>'
  }, function(err, data) {
    if (err) {
      console.log(err.stack);
      return;
    }
    console.log('push sent');
    console.log(data);
  });
}

t.on('tweet', (tweet) => {<br>  
  let keywordsMatched = tweet.text && tweet.text.match(pattern) || [];
  if (tweet.text && tweet.text.toLowerCase().indexOf('enchente') !== -1) {
    sendSNS(`message: ${tweet.text} keywords: ${keywordsMatched.length && keywordsMatched.join(',')}`);
    return;
  }

  if (!startTime) {
    startTime = new Date().getTime();
  }

  const now = new Date().getTime();
  const secs = (now - startTime) / 1000 / 60;
  if ( secs < 20) {
    deque.push(tweet.text);
    count += 1;
    console.log('deque ', count, deque.toArray());
  } else {
    startTime = new Date().getTime();
    deque.clear();
    count = 1;
  }

  if (count >= 5) {
    sendSNS(`message: ${tweet.text} keywords: ${keywordsMatched.length && keywordsMatched.join(',')}`);
    count = 0;
    startTime = null;
  }
});

Step 17: Push Notifications - Aplicativo Android

Nessa etapa foi criado um aplicativo Android a partir do assistente de projetos do Android Studio. Uma aplicação com uma activity básica será suficiente.

Após a criação da estrutura inicial do aplicativo, iremos configurar a integração com o Firebase. Para essa configuração, siga as instruções disponíveis no tutorial do firebase. Aqui iremos apontar alguns detalhes que serão importantes para o funcionamento da integração.

Projeto de referência para configurações: https://github.com/johnnyeric/Android-FCM-Setup

Inclusão manual

Seguiremos a seção de inclusão manual do firebase, nessa etapa deve ser baixado o arquivo google-services.json e incluído na pasta app/ conforme mencionado no tutorial.

Inclusão do SDK

Seguir normalmente com a primeira instrução:

Primeiro, adicione regras ao seu arquivo build.gradle no nível raiz para incluir o plug-in google-services:

buildscript {    
    // ...    
    dependencies {        
    	// ...        
    	classpath 'com.google.gms:google-services:3.0.0'
    }
}

Na segunda instrução, tivemos problema em utilizar a versão 10.2.1 presente no tutorial, para obtermos sucesso na configuração iremos utilizar a versão 10.0.1. Aproveitando a configuração do FCM seguindo o próximo tutorial, iremos adicionar também a biblioteca firebase messaging.

Em seguida, no arquivo Gradle do módulo (geralmente app/build.gradle), adicione a linha apply plugin na parte inferior do arquivo para ativar o plug-in do Gradle:

apply plugin: 'com.android.application'android {  
    // ...
}

dependencies {  
    // ...  
    compile 'com.google.firebase:firebase-core:10.0.1' 
    compile 'com.google.firebase:firebase-messaging:10.0.1'
    // Getting a "Could not find" error? Make sure you have  
    // the latest Google Repository in the Android SDK manager
}

// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'

Após a inclusão das bibliotecas o projeto Android Studio deverá ser sincronizado, para então seguirmos com as configurações adicionais. Seguir normalmente com a edição do manifesto do aplicativo (AndroidManifest.xml). Importante notar que o nome do service deve representar uma classe implementada em sua aplicação que herde de uma classe de serviço do Firebase.

Classes implementadas:

InstanceIdService: Objetivo de obter e gerenciar o token do dispositivo.

MessagingService: Objetivo de receber e interpretar as mensagens recebidas via push notifications.

Monitoração de tokens

Na seção "Monitorar a geração de tokens" você poderá obter o token e manter ele para conseguir enviar mensagens diretamente para os dispositivos. Idealmente o desenvolvedor deverá enviar a um servidor para controle de usuários. Por simplicidade, podemos escolher outra forma, fazer o aplicativo sobrescrever a um tópico, e todos usuários terão acesso as notificações daquele tópico.

Incluir o código abaixo no evento onCreate da activity principal.

FirebaseMessaging.getInstance().subscribeToTopic("<topico>"); //substituir <topico> por um nome de tópico

Importante notar que sem a implementação de uma classe de serviço de mensagem o aplicativo não irá receber notificações, apenas enviando um payload específico em que o sistema que irá gerar a notificação. Aqui iremos tratar da notificação criada via aplicação.

Para o recebimento de mensagens deve ser implementada a classe Messaging Service e no onReceiveMessage deve ser incluído um código similar ao que está abaixo:

@Override
public void onMessageReceived(RemoteMessage remoteMessage) { // ... // TODO(developer): Handle FCM messages here. // Not getting messages here? See why this may be: https://goo.gl/39bRNJ Log.d(TAG, "From: " + remoteMessage.getFrom()); // Check if message contains a data payload. if (remoteMessage.getData().size() > 0) { Log.d(TAG, "Message data payload: " + remoteMessage.getData()); sendNotification(remoteMessage.getData().get("message")); } // Check if message contains a notification payload. if (remoteMessage.getNotification() != null) { Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody()); sendNotification(remoteMessage.getNotification().getBody()); } // Also if you intend on generating your own notifications as a result of a received FCM // message, here is where that should be initiated. See sendNotification method below. }

private void sendNotification(String messageBody) { // TODO implementar criação de notificação no android. // Exemplo: Intent intent = new Intent(this, MainActivity.class); intent.putExtra("message", messageBody); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT); Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); android.support.v4.app.NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("FCM Message") .setContentText(messageBody) .setAutoCancel(true) .setSound(defaultSoundUri) .setContentIntent(pendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0, notificationBuilder.build()); } // MyFirebaseMessagingService.java

Step 18: Push Notifications - Configuração Do Firebase Cloud Messaging

Neste passo iremos habilitar notificações push em dispositivos Android, onde as notificações poderão, futuramente, ser enviadas via AWS SNS.

Primeiramente, devemos criar um projeto Firebase na página do Console Firebase.

Após a criação do projeto, navegue até o dashboard o projeto. Em seguida, clique sobre o botão Adicionar app e clique em Android. Este procedimento irá abrir uma janela modal, preencha os dados de seu aplicativo e finalize o registro do App. Você pode escolher um nome de pacote para o aplicativo, porém deverá usar esse mesmo nome na hora de criar o aplicativo. Siga as instruções do Firebase.

Faça o download do arquivo google-services.json e guarde em um local seguro pois ele deverá ser colocado na pasta do aplicativo para que ele identifique as configurações do Firebase. Siga também as instruções para inclusão do SDK do Firebase ao seu aplicativo.

Step 19: Push Notifications - Configuração SNS E FCM/GCM

Nesse passo iremos realizar a integração entre o serviço AWS SNS e o serviço Firebase Cloud Messaging (FCM). Vale lembrar que anteriormente esse serviço era chamado de Google Cloud Messaging (GCM).

O SNS possui apenas integração com o GCM no momento, ele não foi atualizado após essa mudança. Porém, o FCM mantém total compatibilidade com o GCM, dessa forma iremos poder realizar essa integração sem problemas.

No painel SNS do AWS vá na seção Applications, e clique em Create platform application. Em seguida, preencha o Application name (escolha um nome ou coloque o nome do pacote do app) e selecione Google Cloud Messaging no campo Push notification platform.

No campo API key, será necessário preencher a chave de acesso obtida no console do Firebase.

No console do Firebase, abra o aplicativo Android criado, e navegue até a página de configurações. Na página de configurações, selecione a aba Cloud Messaging. Obtenha o token em "Chave do servidor".

Após criada a aplicação, clique no link presente na coluna ARN da aplicação.

Criação de endpoint

Na página da aplicação recém criada, clique no botão Create Platform Endpoint e preencha no campo Device token.

Opções de preenchimento para Device Token:

  1. Token obtido ao iniciar aplicação no dispositivo cliente, esse token deverá ser armazenado para envio de mensagens direcionadas.
  2. Definição de valor de tópico no formato "/topics/". Nesse caso, todos clientes móveis que sobrescreverem no tópico em questão irão receber notificações. Por simplicidade, iremos utilizar um tópico chamado disaster-notification para atingir um número maior de dispositivos móveis sem a necessidade de gerenciar o token de cada dispositivo.

O campo User Data poderá ficar vazio.

Envio de notificação

Para teste de publicação selecione o endpoint, e clique no botão Publish to endpoint.

Selecione o formato json e coloque a seguinte mensagem:

{ "GCM": "{ \"data\": { \"message\": \"teste\" } }" }

Note que para esse payload acima funcionar, o aplicativo android deve conter uma class de serviço de mensagem do Firebase registrada. (Exemplo: Classe implementação de FirebaseMessagingService).

Para testar notificações, podemos usar um payload que o próprio sistema Android cria a notificação.

{ "GCM": "{ \"notification\": { \"text\": \"teste\" } }" }

Step 20: Conclusão

É muito complexa a detecção de tempestades sem ter sensores adequados, trabalhar com o sensor NTC e LDR podem ter resultados positivos de acordo com o ambiente em que os sensores estão instalados. Neste projetos demonstramos a viabilidade do conceito, sendo necessárias novas pesquisas no sentido de relacionar os dados coletados com os eventos ocorridos.

O LDR é bastante sensível à variação de luminosidade, apresentando uma resposta imediata a sombras ou reflexos de luz. O Sensor NTC é sensível à incidência de raios solares que ocorreram em dados momentos do dia. Nosso trabalho gerou diversos falsos positivos devido as características do ambiente em que os sensores estão coletando os dados e novos testes com eles seriam necessários para melhorar a precisão.

Com certeza para a detecção de chuvas uma combinação de outros tipos de sensores como barômetro (pressão atmosférica), anemômetro (velocidade do vento) e sensor de umidade do ar seria o ideal, porém tentamos realizar nosso experimento com os sensores que dispomos e tentar ver se realmente seria possível a medição correta e assertiva. Nossa conclusão foi de que a previsão de chuvas e enchentes seria bastante complexa apenas com os sensores utilizados.

Comments

author
Swansong (author)2017-07-21

Thanks for sharing :)

About This Instructable

193views

3favorites

More by Rafael Henriqued:Detecção De Enchente/terremoto Com Raspberry Pi E AWS IoT Greengrass
Add instructable to: