Projeto: Sistema de irrigação automático integrado ao Telegram com Arduino!

Hoje vou apresentar um projeto que desenvolvi junto a alguns amigos. Um sistema capaz de controlar a irrigação de uma plantação de forma autônoma, integrada a um bot no Telegram.

Tutoriais relacionados:

  • NodeMCU: Introdução (em breve)
  • Como conectar um NodeMCU a Internet (em breve)
  • Como integrar o Arduino ao Telegram (em breve)
  • Arduino OTA: Atualize o código sem fio! (em breve)

Introdução:

Alguns amigos estavam terminando o curso técnico em eletrotécnica e precisavam desenvolver um projeto de conclusão, porém estavam com dificuldades com a programação, aí que entrei na história.

A ideia era desenvolver um projeto que fosse capaz de controlar um sistema de irrigação de uma plantação, porém não queríamos algo que só ligasse e desligasse uma válvula.

Por isso pensamos que seria interessante que os dados de umidade coletados fossem entregues por um chatbot.

A ideia principal desse post não é ser um tutorial que explicará passo-a-passo, mas sim apresentar o projeto para que você se inspire, copie e o melhore. Em breve postarei os tutoriais detalhando cada função.

A primeira coisa que fizemos foi definir as funcionalidades que o projeto deveria ter para sabermos o que deveríamos fazer:

  • Ele deveria ser capaz de identificar a umidade do solo;
  • Controlar um relé para que fosse possível acionar uma bomba/válvula;
  • O protótipo deveria ter algumas luzes de indicação para sabermos quando a umidade estivesse crítica e se o sistema de irrigação está ligado;
  • Exibir a umidade atual da plantação no Telegram, e permitir que o usuário ligasse a irrigação de forma manual;
  • O protótipo deveria ser seguro, por isso além da luz de indicação de quando o sistema de irrigação estivesse ligado, deveria possuir um botão de emergência que desligasse o relé;
  • Função sem necessidade, mas que tomei como desafio pessoal: O sistema deveria permitir a atualização do código sem fio.

Programação

Por gosto pessoal (e um pouco de mania), costumo separar o código em partes, assim fica mais fácil de encontrar um possível bug ou fazer uma melhoria.

O código foi dividido em 6 partes:

  • Main;
  • Bot;
  • WiFi;
  • OTA;
  • Pinagem;
  • Configurações básicas.

Configurações básicas e pinagem:

Na minha opinião, a vantagem de separar os parâmetros e configurações básicas é que se torna bem mais simples à modificação de qualquer um deles, sem a necessidade de ter que procura-los no meio do código.

//Configurações de WiFi (Rede que a placa vai se conectar (tem que ter acesso à Internet))
const char* ssid = "nome da rede";         //Rede WiFi
const char* password = "senha";  //Senha do WiFi

//Configurações do BOT do Telegram
const char* botToken = "token";  //Token do BOT

#define ligarsistem 50     //em %umidade
#define desligarsistem 70  //em %umidade
#define muitoalto 90       //em %umidade
#define muitobaixo 30      //em %umidade

#define maxleitura 1023
#define minleitura 0

Aqui definimos coisas como a rede na qual a placa vai se conectar e o token do bot que será utilizado. Além disso também estão aqui alguns parâmetros fundamentais para o funcionamento do sistema, como em qual porcentagem a umidade será considerada crítica.

#define sensor A0
#define rele 16             //D0
#define ledligado 5         //D1
#define leddesligado 4      //D2
#define botaoemergencia 14  //D5
#define ledalta 13          //D7
#define ledbaixa 12         //D6

A pinagem deste código é bem simples, alguns pinos declarados acabaram não sendo utilizados na versão final do código, mas acabaram ficando declarados de herança.

Uma observação sobre o NodeMCU:

Os números escritos na placa indicando os pinos não correspondem aos pinos GPIO da placa, por exemplo, o pino D0 indicado na placa corresponde ao pino 16 na programação.

(Nessa parte não tem muita coisa interessante na programação)

Agora começamos a parte funcional, onde configuramos as funcionalidades do código.

WiFi:

#include <ESP8266WiFi.h>

void setupWiFi() {
  // Define o modo de operação apenas como STA (cliente)
  WiFi.mode(WIFI_STA);

  // Conectando-se à rede Wi-Fi
  Serial.print("Conectando à rede Wi-Fi");
  WiFi.begin(ssid, password);

  // Aguarda a conexão com a rede Wi-Fi
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // Printa o IP
  Serial.println();
  Serial.print("Conectado! IP: ");
  Serial.println(WiFi.localIP());

  // Remove a verificação SSL (caso esteja utilizando o cliente seguro)
  client.setInsecure();
}

Como vimos no nosso tutorial (ainda não postado, paciência que estamos começando) de NodeMCU,a configuração de conexão ao WiFi é muito simples.

OTA:

O over the air (sob o ar), ou OTA para os íntimos é uma maneira de atualizarmos o código da placa sem uma conexão física, usando a rede local.

#include <ArduinoOTA.h>

void setupOTA() {
  //Configuração do OTA
  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else {
      //U_SPIFFS
      type = "filesystem";
    }

    Serial.println("Iniciando atualização: " + type);
  });

  ArduinoOTA.onEnd([]() {
    Serial.println("\nAtualização Concuída!");
  });

  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progresso: %u%%\r", (progress * 100) / total);
  });

  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Erro[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      Serial.println("Falha na autenticação");
    } else if (error == OTA_BEGIN_ERROR) {
      Serial.println("Falha ao iniciar");
    } else if (error == OTA_CONNECT_ERROR) {
      Serial.println("Erro de conexão");
    } else if (error == OTA_RECEIVE_ERROR) {
      Serial.println("Erro ao receber");
    } else if (error == OTA_END_ERROR) {
      Serial.println("Erro ao finalizar");
    }
  });

  ArduinoOTA.begin();
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());
}

void loopOTA() {
  ArduinoOTA.handle();
}

Sem duvidas o OTA foi o que mais deu trabalho (e problemas) nesse projeto.

Como a placa ficaria dentro de uma caixa alimentada por uma bateria, não seria pratico ter que desmontar todo o protótipo para ter acesso ao circuito e atualizar o código. Por isso surgiu a ideia de fazer isso sem fio, de uma forma.

A ideia original era que pudéssemos carregar o código em algum lugar como um repositório e quando a placa ficasse on-line procurasse a atualização e a instalasse, independente do local e da rede que ela estivesse atualizada.

Tentamos usar o GitHub para isso, mas acabou não dando certo, a placa ficava presa em um loop tentando instalar o arquivo binário.

Como tínhamos um tempo limitado e essa função não era realmente necessária decidimos abandona-la, mas para não cortamos por completos optamos por um modo mais simples que permite a atualização apenas em redes locais.

Em breve vou fazer um tutorial dedicado ao OTA e fazer essa ideia de atualização remota funcionar.

Bot:

#include <WiFiClientSecure.h>      //WiFi Telegram
#include <UniversalTelegramBot.h>  //BOT Telegram

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

unsigned long lastTimeBotRan = 0;
const long botInterval = 1000;  //Intervalo para verificações

//Void Mensagens BOT
void handleNewMessages(int numNewMessages) {
  for (i = 0; i < numNewMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);  //Obtem ID dos usuarios
    String text = bot.messages[i].text;                //Obtem as mensagens enviadas
    String from_name = bot.messages[i].from_name;      //Obtem o nome do remetente

    if (from_name == "")
      from_name = "Usuário";  //Define Nome padrão caso o usuario não preencha no Telegram

    //Define funcionalidade do /Start
    if (text == "/start") {
      String resposta = "Olá " + from_name + ", bem-vindo(a)!\nComandos:\n/umidade - Informa a umidade atual. \n/liga - Liga o sistema. \n/desliga - Desliga o sistema. \n/estado - Informa o estado atual do sistema.\n/auto - Liga/desliga o modo automático do sistema.";
      bot.sendMessage(chat_id, resposta, "");
    }

    //Define a funcionalidade do /umidade
    else if (text == "/umidade" || text == "Umidade" || text == "umidade") {
      String resposta = from_name + ", a umidade atual é " + String(umidade) + "%.";  //Mensagem que o BOT enviará no comando /umidade
      bot.sendMessage(chat_id, resposta, "");
    }

    //Define a funcionalidade do /liga
    else if (text == "/liga" || text == "Liga" || text == "liga") {
      liga();
      String resposta = from_name + ", a irrigação está **LIGADA**";  //Mensagem que o BOT enviará no comando /liga
      bot.sendMessage(chat_id, resposta, "");
    }

    //Define a funcionalidade do /desliga
    else if (text == "/desliga" || text == "Desliga" || text == "desliga") {
      desliga();
      String resposta = from_name + ", a irrigação está **DESLIGADA**";  //Mensagem que o BOT enviará no comando /desliga
      bot.sendMessage(chat_id, resposta, "");
    }

    //Define a funcionalidade do /estado
    else if (text == "/estado" || text == "Estado" || text == "estado") {

      String resposta = from_name + ", a irrigação está: " + String(estado) + ".";  //Mensagem que o BOT enviará no comando /estado
      bot.sendMessage(chat_id, resposta, "");
    }

    //Define a funcionalidade do /auto
    else if (text == "/auto" || text == "Auto" || text == "auto") {
      autom = !autom;
      String resposta = from_name + ", o modo automatico foi alterado, ele está: " + String(autom) + ".";  //Mensagem que o BOT enviará no comando /estado
      bot.sendMessage(chat_id, resposta, "");
    }

    //The Capybara Society
    else if (text == "/capivara" || text == "Capivara" || text == "capivara") {
      String resposta = "The Capybara Society gives its blessing to this code!";
      bot.sendPhoto(chat_id, "https://imgur.com/clwMZsF", resposta);
    }

    //Comandos não reconhecidos
    else
      bot.sendMessage(chat_id, "Comando não reconhecido!");
  }
}

//Fim Void Mensagens BOT

void loopBOT() {
  //Verifica se tem novas mensagens
  if (millis() - lastTimeBotRan > botInterval) {
    int numNewMessages = bot.getUpdates(bot.last_message_received + 1);

    while (numNewMessages) {
      handleNewMessages(numNewMessages);
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    }

    lastTimeBotRan = millis();
  }
  //Fim Verifica se tem novas mensagens
}
//Fim do loopBOT

A integração entre o bot e o Arduino está detalhado no nosso tutorial (que também não foi publicado), mas de forma simplificada o código coleta 3 dados do Telegram:

  • O ID do usuário: É como se fosse o “CPF” da sua conta no Telegram, ele é fundamental para sabermos quem enviou a mensagem e para quem devemos responde-la.
  • O nome do usuário: Através do ID conseguimos identificar o nome do usuário cadastrado na conta do Telegram, assim podemos deixar as mensagens mais personalizadas e o bot menos artificial (se isso for possível)
  • A mensagem enviada: A informação coletada mais importante, já que é através dela que saberemos qual ação o Arduino deve executar, seja acionar o sistema de irrigação ou informar a umidade atual.

Sempre que possível tento esconder uma capivara como easter-egg (talvez seja meu animago), por isso o comando /capivara envia uma foto de uma capivara. Para simplificar esse envio preferi hospedar a imagem on-line para não sobrecarregar a memoria do NodeMCU.

Esse código poderia ser melhor otimizado, talvez fazendo com que existisse apenas um comando para enviar a resposta e não separadamente como está, mas como disse, não tínhamos um prazo longo, então o importante foi estar funcional e não otimizado (o bom e velho XGH).

//Inclusão
#include "configuracoes_basicas.h"  //Chamas as configurações básicas
#include "Pinagem.h"                //Inclui pinos

//Declarando variaveis
int i;        // Variavel de repetição
int umidade;  //Variavel de umidade
bool estado;
bool autom = HIGH;
//Fim Declarar variaveis

//Void Setup
void setup() {
  setupOTA();   //Chama funções OTA
  setupWiFi();  //Chama funções WiFi

  Serial.begin(9600);
  Serial.println("Inicializando...");

  //Setup funcional
  //Define função dos pinos
  pinMode(sensor, INPUT);
  pinMode(rele, OUTPUT);
  pinMode(ledligado, OUTPUT);
  pinMode(leddesligado, OUTPUT);
  pinMode(ledalta, OUTPUT);
  pinMode(ledbaixa, OUTPUT);
  pinMode(botaoemergencia, INPUT_PULLUP);
  //Fim setup funcional
}
//Fim void Setup

//Void Loop
void loop() {
  loopOTA();  //Chama Funções do OTA
  loopBOT();  //Chama funções do BOT


  //mapeia a umidade de 0 a 100
  umidade = map(analogRead(sensor), minleitura, maxleitura, 100, 0);
  if (umidade < 0)
    umidade = 0;

  if (umidade > 100)
    umidade = 100;

  Serial.println(umidade);

  //Desliga o sistema caso ultrapasse o limite
  if (umidade >= desligarsistem && autom == HIGH)
    desliga();

  //Liga o sistema caso esteja baixa
  if (umidade <= ligarsistem && autom == HIGH)
    liga();

  //Liga o led de atenção caso a umidade esteja muito baixa ou muito alta
  if (umidade >= muitoalto || umidade <= muitobaixo) {
    digitalWrite(ledalta, HIGH);

  } else
    digitalWrite(ledalta, LOW);

//Faz a leitura do botão de emergencia
  if (digitalRead(botaoemergencia) == LOW)
    desliga();
}
//Fim Void Loop
void liga() {
  estado = HIGH;
  digitalWrite(rele, HIGH);
  digitalWrite(ledligado, HIGH);
  digitalWrite(leddesligado, LOW);
}

void desliga() {
  estado = LOW;
  digitalWrite(rele, LOW);
  digitalWrite(ledligado, LOW);
  digitalWrite(leddesligado, HIGH);
}

É aqui que a mágica funciona, no main todos os códigos são integrados e onde está toda a parte lógica do projeto como o mapeamento do valor lido em porcentagem de umidade e as funções de ligar e desligar a irrigação.

Circuito:

Itens usados:

Comprando através desse link, você não paga nada a mais e ajuda a manter esse projeto vivo.

O circuito desse projeto é simples, com apenas a placa, alguns LEDs, o sensor de umidade do solo.

Sempre que possível, uso os botões em modo PULL_UP, dessa forma é necessário colocar nenhum resistor ou ligação mais complexa, ligando apenas entre o GND e o pino da placa.

Novamente, como esse projeto era um protótipo conceitual e não um projeto final e tínhamos um prazo curto para executa-lo não otimizamos o circuito, o que implicou na forma de alimentação dele. Optamos em usar um power bank para alimentar o Arduino, apesar de não ser o ideal, era o que tínhamos disponível no momento.

Como fiquei responsável apenas pelo software não tenho o circuito final detalhado, nem fotos do mesmo ou o projeto finalizado, mas dou a minha palavra que ele funcionou e eles tiveram o projeto aprovado com louvor.

Foram utilizadas algumas bibliotecas externas, que podem ser instaladas pela própria IDE do Arduino, como ensinado nesse post: (que ainda não existe mas vai vir).

Esse é o primeiro post de verdade do nosso blog, ainda estou pegando o jeito, por isso seu feedback é muito importante, você pode nos encontrar no Instagram, Thingverse, GitHub.

Agradeço ao meu grande amigo Raul que confiou em mim para ajudar nesse projeto e permitiu que eu o compartilhasse aqui.

Espero que tenha sido útil e que tenha te inspirado a desenvolver a sua própria versão (ou algo completamente diferente).

Ryan Avila é técnico em Eletromecânica e atualmente cursa Engenharia de Controle e Automação na UFMG. Apaixonado por tecnologia, estuda eletrônica e mecânica por conta própria desde os 8 anos. Aos 13 anos embarcou no mundo da impressão 3D onde achou sua grande paixão.

Publicar comentário