Sistema de Alarme Residencial Baseado no Protocolo SMTP

Sistema de Alarme Residencial Baseado no Protocolo SMTP

O objetivo deste artigo é apresentar uma solução utilizando o Nodemcu (IoT) com o intuito de simular um sistema de alarme residencial, baseado no protocolo SMTP, onde, ao captar uma mudança de luz no ambiente, é disparado um e-mail para o proprietário.

A respeito do dispositivo utilizado, o Nodemcu é uma placa de desenvolvimento que combina o chip ESP8266 com uma conexão USB-serial em uma tensão de 3.3V.

Imagem das ligações do Nodemcu

O projeto consiste em:

• Implementação da leitura do sensor de luz;

• Implementação do envio de e-mail;

• Implementação de WebServer integrado ao Nodemcu para definir os valores do e-mail e do valor de intensidade de luz (valor base para envio de e-mail para o e-mail cadastrado).

Imagem do ESP8266 utilizado

É possível observar, na figura anterior como foi criado o protótipo do Nodemcu com o sensor de luz.

Imagem de um webclient conectando no webserver para configurar os parâmetros.

Escolha da Linguagem

Para a implementação deste projeto, foi utilizado a linguagem C, utilizando a IDE do Arduino. Apesar das diversas opções de linguagens para IoT, como C, Python e Lua, a equipe do trabalho optou pela linguagem C, por conta do domínio com a mesma no uso com Arduino. Além disso, apesar da linguagem não ser de tão fácil aprendizado quanto as outras, no entanto, a mesma é simples e apresenta os erros de forma clara, não tendo demais abstrações.

Explicação do Código Fonte e Libs Utilizadas

Para a implementação do envio de e-mail, foi utilizado o SMTP2GO o qual permite criar um servidor de e-mail de forma gratuita e efetuar o redirecionamento de e-mails.

Para a implementação do programa no NodeMCU, utilizando a linguagem, foi necessário a utilização das libs: SPI, ESP8266WiFi e FS, sendo que o código ficou da seguinte forma:

#include <SPI.h>
#include <ESP8266WiFi.h>
#include "FS.h"

SPI: O SPI é uma interface de conexão serial.

• FS: O FS controla o FileSystem, ou seja, para fazer as operações de escrita e leitura no NodeMCU.

• ESP8266WiFi: É o módulo do ESP8266 para qualquer coisa que envolva conexão WiFi. É utilizado para rodar o Web Server.

String readString = "";

WiFiClient client;
WiFiClient clientEmail;
WiFiServer server(8080);
const char* ssid     = "Informe o seu SSID aqui";
const char* password = "Informa a senha da sua rede aqui";

const char* host = "Insira o seu host aqui";

String sEmailFS;
int iValorLimiteFS;

unsigned long ulEsperaEnvioEmail = 0;

void setup()
{
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  server.begin();

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  SPIFFS.begin();

  createFile();

  String sRead = readFile();

  Serial.println(sRead.length());

  int iIndex = sRead.indexOf("|");
  if (iIndex > 0)
  {
    iValorLimiteFS = sRead.substring(0, iIndex).toInt();
    sEmailFS       = sRead.substring(iIndex + 1);
    //sEmailFS = sEmailFS.substring(0, sEmailFS.length() - 1);
  }
}

O trecho de código anterior, efetua o setup do programa, bem como, inicializa as variáveis.

Para o correto funcionamento, antes de implementar a função de loop deve-se implementar as funções de escrita a leitura do arquivo.

void createFile()
{
  File wFile;

  //Cria o arquivo se ele não existir
  if (!SPIFFS.exists("/config.txt"))
  {
    Serial.println("Criando o arquivo...");
    wFile = SPIFFS.open("/config.txt", "w+");
    if (!wFile)
      Serial.println("Erro ao criar arquivo!");
    else
      Serial.println("Arquivo criado com sucesso!");
  }
  
  wFile.close();
}

void writeFile(String msg)
{  
  SPIFFS.remove("/config.txt");
  File rFile = SPIFFS.open("/config.txt", "a+");

  if (!rFile)
    Serial.println("Erro ao abrir arquivo!");
  else
    rFile.println(msg);
    
  rFile.close();
}

String readFile()
{
  String buf;
  //Faz a leitura do arquivo
  File rFile = SPIFFS.open("/config.txt", "r");
  while (rFile.available())
  {
    buf += rFile.readStringUntil('\n');
  }

  buf.trim();

  rFile.close();

  Serial.println(buf);

  return buf;
}

A função de loop, o qual é chamada sempre pelo programa, deve ser implementada da seguinte forma:

void loop()
{
  delay(10);
  int iValorLido = analogRead(0);
  
  if (iValorLido > iValorLimiteFS)
  {
    if (sEmailFS.length() > 0 && (millis() - ulEsperaEnvioEmail) > 10000)
      sendEmail(iValorLido);
  }

  client = server.available();
  if (client)
  {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected())
    {
      if (client.available())
      {
        char c = client.read();
        Serial.write(c);
        readString += c;
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank)
        {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will be closed after completion of the response
          //client.println("Refresh: ");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE html>");
          client.println("<html>");
          client.println("<head>");
          client.println("    <meta charset=\"utf-8\" />");
          client.println("    <title>Trabalho Final</title>");
          client.println("    <link rel='icon' href='data:,'>");
          client.println("</head>");
          client.println("<body>");
          client.println("<div>");
          client.println("<form>");
          client.println("        <label>Valor Limite</label>");
          client.println("        <input type=\"text\" id=\"valor_limite\" name=\"valor_limite\" />");
          client.println("        <br />");
          client.println("        <label>E-mail</label>");
          client.println("        <input type=\"text\" id=\"email\" name=\"email\" />");
          client.println("        <br />");
          client.println("        <button onclick=\"window.location=this.href+'?valor_limite='+document.getElementById('valor_limite').value+'&email='+document.getElementById('email').value\">Enviar</button>");
          client.println("</form>");
          client.println("    </div>");

          if (iValorLimiteFS && sEmailFS.length())
          {
            client.println("    <div>");
            client.println("    <p>E-mail cadastrado:" + sEmailFS + "</p><br />");
            client.println("    <p>Valor limite cadastrado:" + String(iValorLimiteFS) + "</p>");
            client.println("    </div>");
          }
          client.println(" </body>");
          client.println("</html>");

          break;
        }
        
        if (c == '\n')
          currentLineIsBlank = true;
        else if (c != '\r')
          currentLineIsBlank = false;
      }
    }
    
    delay(1);
    client.stop();
    Serial.println(readString);

    String sValorLimite;
    int iIndexValorLimite = readString.indexOf("valor_limite");
    if (iIndexValorLimite >= 0)
    {
      iIndexValorLimite += 13;
      sValorLimite = readString.substring(iIndexValorLimite, readString.indexOf("&"));
    }

    String sEmail;
    int iIndexEmail = readString.indexOf("email");
    if (iIndexEmail >= 0)
    {
      iIndexEmail += 6;
      sEmail = readString.substring(iIndexEmail, readString.indexOf(" HTTP"));
      sEmail.replace("%40", "@");
    }

    if (sValorLimite.length() > 0 && sEmail.length() > 0)
    {
      String sValue = sValorLimite + "|" + sEmail;
      writeFile(sValue);

      sEmailFS = sEmail;
      sEmailFS.trim();
      iValorLimiteFS = sValorLimite.toInt();
    }

    readString = "";
  }
}

Por fim, devemos criar as funções de envio de envio de e-mail, assim como uma função auxiliar para saber se o envio está sendo feito de forma correta.

void sendEmail(int iValorLido)
{  
  ulEsperaEnvioEmail = millis();
  
  if (clientEmail.connect(host, 2525))
  {
    String sRCPT = "RCPT To: <";
    sRCPT += sEmailFS;
    sRCPT += ">";
    Serial.println(sRCPT);
    sRCPT.replace("=", "");

    Serial.println("connected");
    if (!received()) return;
    // Make a HTTP request:
    clientEmail.println("EHLO INFORME SEU IP AQUI");
    if (!received()) return;
    clientEmail.println("AUTH LOGIN");
    if (!received()) return;
    clientEmail.println("bGVvbmFyZG9fZmllZGxlckBob3RtYWlsLmNvbQ==");
    if (!received()) return;
    clientEmail.println("MjlkVEdZR20xT3Yz");
    if (!received()) return;
    clientEmail.println("MAIL From: <INFORME O DE AQUI>");
    if (!received()) return;
    //clientEmail.println("RCPT To: <INFORME O PARA AQUI>");
    clientEmail.println(sRCPT);
    if (!received()) return;
    clientEmail.println("DATA");
    if (!received()) return;
    clientEmail.println("To: You <INFORME O PARA AQUI>");
    clientEmail.println("From: Me <INFORME O DE AQUI>");

    clientEmail.println("Subject: Atencao alarme disparado\r\n");

    String sValorLido = "Valor: " + String(iValorLido);
    
    clientEmail.println("Reconhecemos uma variacao no sensor de luminosidade");
    clientEmail.println(sValorLido);

    clientEmail.println(".");
    if (!received()) return;
    clientEmail.println("QUIT");
    if (!received()) return;
    clientEmail.stop();
  }
  else
  {
    // kf you didn't get a connection to the server:
    Serial.println("connection failed");
  }
}

boolean received()
{
  byte respCode;
  int thisByte;
  int loopCount = 0;

  while (clientEmail.available() == 0)
  {
    delay(1);
    loopCount++;

    // if nothing received for 10 seconds, timeout
    if (loopCount > 10000)
    {
      clientEmail.stop();
      Serial.println(F("\r\nTimeout"));
      return false;
    }
  }
  String result = "0";
  while (clientEmail.available() > 0)
  {
    thisByte = clientEmail.read();
    Serial.write(thisByte);
    result += (char) thisByte;
  }

  if (result.indexOf("250") > -1 ||
      result.indexOf("220") > -1 ||
      result.indexOf("354") > -1 ||
      result.indexOf("334") > -1 ||
      result.indexOf("235") > -1 ||
      result.indexOf("221") > -1)
  {
    return true;
  }
  else
  {
    Serial.println("Codigo de resultado invalido. Conexao interrompida.");
    return false;
  }
}

Fluxograma

Fluxograma de funcionamento da aplicação.

Dificuldades Encontradas

• Na hora de enviar o post do web server, o caractere @ é substituído por %40.

• Dificuldades em implementar o File System, por conta do arquivo sempre adicionar o cursor no final e não no inicial do arquivo (para sobrescrita).

• Envio de e-mail – são disparadas múltiplas chamadas a partir do momento em que é atingido o valor. Para contornar este problema, o ideal seria implementar mais um parâmetro para configurar quantos e-mails deseja-se enviar e implementar uma lógica de controle para isso.

• Em alguns momentos, foi necessário utilizar comandos de replace e trim para caracteres indesejados nas variáveis.

Procedimento para permitir o Nodemcu receber programas

Para o correto funcionamento do Nodemcu na linguagem C, utilizando a IDE do Arduino, é necessário apenas instalar os pacotes do Esp8266 pela própria IDE do Arduino.

Referências Utilizadas

• http://pedrominatel.com.br/pt/esp8266/webserver-log-no-esp8266-com-spiffs/

• https://www.arduino.cc/en/Main/Software

Código Fonte

Baixe o código fonte clicando aqui.

Equipe do Projeto:

  • Flávio Losada (flavio.losada@outlook.com)
  • Jader Tomelin (jader.tomelin@gmail.com)
  • Leonardo Fiedler (leonardo_fiedler@hotmail.com)
  • Matheus Pereira (pereiramateduardo@gmail.com)

O senhor caveira é programador web, mobile, Desktop, amante da Tecnologia e filósofo de boteco.

Deixe um comentário :)

%d bloggers like this: