Ticker

6/recent/ticker-posts

Criando o seu próprio sistema de irrigação inteligente

Com a pandemia de Covid-19 e o isolamento, muitas pessoas (inclusive eu) acabaram adquirindo o hábito de cultivar plantas dentro de casa ou do apartamento. Acabando o isolamento e com o retorno das atividades presenciais, como fazer para que as plantas continuem sendo irrigadas? No caso de quem mora sozinho, uma possível resposta para essa pergunta é: automatizar a irrigação. Apesar de não ser uma ideia nova, particularmente falando, foi a primeira vez que realmente tive a necessidade de fazer algo de forma automatizada, e uma excelente oportunidade para aprender algumas coisas básicas sobre eletrônica, micro-controladores e atuadores.

Nesta publicação, vou explicar o sistema de irrigação que desenvolvi, descrevendo suas partes, o circuito e o código utilizado.

Sistema de irrigação indoor.



O sistema de irrigação 

Para o meu contexto, onde as plantas estão numa varanda que recebe luz direta do sol pelo período da tarde, precisei desenvolver um sistema alimentado por bateria e que realize a irrigação: duas vezes por dia, por acionamento direto ou via Internet. Ele se comporta da seguinte maneira: a água é bombeada de um reservatório para uma mangueira que, na sua ponta, realiza a irrigação por aspersão (opção escolhida devido a falta de materiais que eu dispunha). E, para economia de energia, o sistema só "acorda" nos momentos de realizar a operação de irrigação. Como limitações (seja por materiais ou tempo), o sistema não identifica o nível de água do reservatório e nem verifica a umidade do solo (possibilidades e funções sensoriais que muitos projetos semelhantes realizam).

Materiais e Desenvolvimento

Para construir o sistema, utilizei os seguintes itens:
  1. Protoboard 400 furos
  2. Placa ESP32
  3. Servomotor SG90
  4. Bomba RS385
  5. Relê de 1 canal
  6. Battery shield
  7. Painel solar
  8. Mangueira de aquário
  9. Pau de selfie
  10. Caixa de papelão

Montando o circuito

O esquema de montagem desses itens pode ser isto na figura abaixo:

Esquema representando os itens do circuito baseado no ESP32.
Esquema representando os itens do circuito baseado no ESP32.

Para montagem do circuito, tenha como referência que posicionei o ESP32 na trilha A1 da protoboard. A partir disso, comecemos pelo módulo relê, que será responsável por acionar a bomba d'água. A conexão desse módulo foi feita da seguinte forma: a porta signal do módulo foi conectada à trilha J9 da protoboard (associada à porta GPIO27 do ESP32), enquanto para as portas de power e ground foram conectadas nas trilhas B29 e B30 - veja que distribuí as respectivas saídas do ESP32 nas trilhas J2 e J6 para as trilhas A29 e A30, tendo em vista que essas trilhas poderão abrigar mais de um componente (i.e., relê e servomotor); por fim, considerando que o módulo relê funcionará no modo normalmente aberto (ou seja, a operação normal dele é não permitir que a corrente flua), as saídas COM e NO foram conectadas, respectivamente, na porta 5V do battery shield e em uma das conexões de entrada de força da bomba RS385 - que, por sua vez, conecta-se ao ground do battery shield. Desta maneira, quando o algoritmo realizar a ativação do GPIO27, o módulo relê fechará o circuito e a corrente fluirá para a ativação da bomba d'água. Não custa dizer que um dos conectores da bomba d'água deve estar ligado a um reservatório de água ou sua bomba pode queimar.

O segundo componente do circuito é o servomotor, que foi utilizado para realizar a irrigação de uma área angular utilizando apenas uma mangueira que fica acoplada ao servo (veja na foto abaixo). A porta de pulse do servo está diretamente ligada ao ESP32 no GPIO13 (trilha J5), enquanto as portas vcc gnd estão conectadas nas trilhas C29 e C30, respectivamente.

Mangueira acoplada ao servomotor.


Escrevendo o código

O software responsável por gerenciar o sistema irá realizar duas atividades básicas em paralelo: acionar a bomba d'água e acionar o servomotor. Além disso, essas duas atividades básicas devem tanto ser realizadas automaticamente duas vezes por dia, quanto por meio do acionamento direto na placa ou via interface Web. Para escrever o código e carregá-lo no ESP32, utilizei a ferramenta Arduino IDE. Duas observações aqui: i) não esqueça de instalar a placa por meio do Gerenciador de Placas da IDE; e ii) caso você esteja utilizando MacOS, talvez seja preciso instalar um driver específico para que sua porta USB reconheça o ESP32 - encontrei o meu aqui.

Para os que não quiserem passar pela explicação do código, podem ir diretamente para ele no repositório que disponibilizei no GitHub.

Escrevendo o código: constantes e variáveis

Inicie o seu código importando as bibliotecas necessárias para executar as funções do sistema:

#include <WiFi.h>
#include <ESP32Servo.h>

Apesar do nome autoexplicativo, elas servirão para conectar o ESP32 ao wifi da sua residência e para poder interagir com o servomotor.

Após isso, utilize constantes para definir o comportamento padrão do sistema:

#define RELAY_PIN 27 // Pin do relay conectado ao ESP32.
#define SERVO_PIN 13 // Pin do servomotor conectado ao ESP32.
#define MAX_IRRIGATION_TIMES 3 // Quantidade de vezes que o servomotor irá
                                        // direcionar a mangueira de um ângulo a outro.
#define uS_TO_S_FACTOR 1000000 // Conversão do fator de microsegundos para segundos.
#define TIME_TO_SLEEP 43200 // Tempo que o ESP32 irá dormir em segundos -
                                        // Valor atual equivale a 12 horas.
#define THRESHOLD 40 // Sensibilidade do toque no T8 para ativar ESP32
                                        // por um botão ou toque no jumper.

Caso queira, basta ajustar esses valores para que eles se adequem às suas necessidades.

A partir daqui, vou mostrar as variáveis que utilizei.

Adicionei uma variável de controle para saber quantas inicializações ocorreram:

RTC_DATA_ATTR int bootCount = 0;

O acionamento do sistema de forma manual é feito utilizando a seguinte variável que receberá o pin associado a tal acionamento:

touch_pad_t touchPin;

Para a conexão wifi, defini as variáveis abaixo para armazenar o usuário e a senha da rede, além de uma variável de controle para saber se o sistema está conectado ou não à rede e quantas tentativas de conexão foram feitas:

const char* ssid = "<INSIRA_NOME_DA_REDE_AQUI>";
const char* password = "<INSIRA_SENHA_DA_REDE_AQUI>";

uint32_t notConnectedCounter = 0;

Com relação ao servomotor e o controle da quantidade de irrigações, defini as seguintes variáveis:

Servo myServo; // Cria o objeto para controlar o servo.
float servoPosition = 0; // Variável para armazenar a posição do servo.
uint32_t irrigationTimesCounter = 0; // Variável para verificar quantas vezes a
                                      // irrigação ocorreu.

No caso da interface web, as seguintes variáveis foram utilizadas tanto para disponibilizar o servidor web que ficará escutando por requisições HTTP feitas por clientes, quanto para controlar o tempo dessas requisições:
WiFiServer server(80); // Objeto do servidor Web, sendo executado na porta 80.
String httpHeader; // Variável para armazenar o cabeálho da requisição HTTP.
unsigned long currentTime = millis(); // Tempo atual da requisição.
// Tempo anterior da requisição e duração da requisição.
unsigned long previousTime = 0;
unsigned long elapsedTime = 0;
// Define o timeout em milisegundos (exemplo 2000ms = 2s).
const long timeoutTime = 2000;

Por fim, defini uma variável de controle para saber se o pino associado ao relê está ativo ou não:

// Variável auxiliar para controlar a saída da porta 27.
bool outputPin27StateOn = false;

Escrevendo o código: a função setup

Antes de descrever como implementei essa função, ressalto que esse foi o meu primeiro projeto do tipo, e recomendo que esta e outras funções sejam modularizadas casa de ferreiro, espeto de pau. Um trabalho futuro para isso é definir, por exemplo, um servo.h e um relay.h.

Na função setup(), dado o funcionamento deep sleep, iremos adicionar todo o procedimento que geralmente faríamos na função loop(). Começamos definido a velocidade de transmissão das portas seriais, incrementando o contador de inicializações e definindo o modo de funcionamento do ESP32:

Serial.begin(115200);
delay(1000);

++bootCount;

esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); // Define em quanto tempo
                                                                  // o ESP32 irá acordar.
touchAttachInterrupt(T8, callback, THRESHOLD); // Define que o ESP32 irá ser ativado
                                                  // ao tocar em T8.
esp_sleep_enable_touchpad_wakeup();

Veja que é na função esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR) que definimos por quanto tempo o ESP32 irá hibernar. Perceba, ainda, a utilização da função esp_sleep_enable_touchpad_wakeup() para definir que será possível ativar o ESP32 manualmente (ao ocorrer interação com a porta T8, como definido na função touchAttachInterrupt(T8, callback, THRESHOLD)).

A partir disso, defini o pin do relê e do servomotor e seu funcionamento inicial:

pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
myServo.attach(SERVO_PIN); // Anexa o servomotor ao pin SERVO_PIN

Para a conexão wifi, vamos utilizar o objeto WiFi importado da biblioteca WiFi.h definida anteriormente:

// Conecta ao Wi-Fi
Serial.print("Conectando em: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".\n");
notConnectedCounter++;
if(notConnectedCounter > 50) {
Serial.println("Reiniciando devido ao WiFi estar desconectado...");
ESP.restart();
}
}

No código acima, veja que a variável contadora notConnectedCounter é utilizada para verificar se estão sendo realizadas novas tentativas de conexão e, caso esse número de tentativas ultrapasse de 50, o ESP32 será reinicializado. Caso a interface web não seja um requisito para você, você pode simplesmente ignorar essa função, ou adicionar a cláusula break para que o setup continue ainda que o ESP32 não esteja conectado ao wifi; ficaria assim:

if(notConnectedCounter > 50) {
    break;
}

Caso o ESP32 consiga se conectar, o seguinte bloco condicional será executado:

if(WiFi.status() == WL_CONNECTED) {
Serial.println("");
Serial.println("WiFi conectado");
Serial.println("Endereço IP: ");
Serial.println(WiFi.localIP());
server.begin();
}

Após isso, finalizamos a função setup iniciando o processo de irrigação e colocando o ESP32 para dormir:

turnOnIrrigation();
turnOffIrrigation();
Serial.println("Indo dormir agora");
delay(1000);
Serial.flush();
esp_deep_sleep_start(); // Coloca o ESP32 para dormir.
}

Irei falar das funções turnOnIrrigation()turnOffIrrigation() na próxima sub-seção. Mas, obviamente, conseguimos hibernar o sistema utilizando a função nativa esp_deep_sleep_start() que irá seguir o tempo definido anteriormente até acordar.

Escrevendo o código: realizando a irrigação

Na função setup, vimos que há a chamada para duas funções: turnOnIrrigation() e turnOffIrrigation(). Estas funções são responsáveis por realizar o acionamento do relê (e, por sua vez, da bomba d'água) e do servomotor. Elas são implementadas da seguinte forma:

void turnOnIrrigation() {
Serial.println("GPIO 27 on");
outputPin27StateOn = true;
digitalWrite(RELAY_PIN, HIGH);
while(irrigationTimesCounter < 3){
rotation();
irrigationTimesCounter++;
}
irrigationTimesCounter = 0;
}

void turnOffIrrigation() {
Serial.println("GPIO 27 off");
outputPin27StateOn = false;
digitalWrite(RELAY_PIN, LOW);
}

Veja que na função turnOnIrrigation(), definimos a variável de controle como true e a saída digital do pin associado ao relê com a constante HIGH. Além disso, enquanto o número de irrigações (nesse caso, entenda irrigação como a movimentação do servomotor de um ângulo a outro com a bomba d'água ativa) for menor que 3, a movimentação (função rotation()) e a saída d'água seguirão em funcionamento.

A função turnOffIrrigation(), por sua vez, tem o papel de simplesmente desativar o relê.

Por fim, no controle da irrigação, vamos ver a função rotation():

void rotation() {
// Vai de 45 até 135 graus passo-a-passo de 0,3 em 0,3 grau.
for (servoPosition = 45; servoPosition <= 135; servoPosition += 0.3) {
myServo.write(servoPosition); // diz ao servo para ir para a posição da variável
                                    // servoPosition
delay(15); // aguarda 15ms para o servo atualizar a posição
}
// Rotação inversa.
for (servoPosition = 135; servoPosition >= 45; servoPosition -= 0.3) {
myServo.write(servoPosition);
delay(15);
}
}

Veja que nesta função define-se os ângulos que o servomotor irá percorrer. Para isso, usei dois laços de repetição que fazem o movimento para esquerda e para direita. Dependendo a área que você tenha que irrigar, ajuste os valores máximos e mínimos da variável servoPosition. É possível também aumentar a velocidade com que a rotação ocorre, aumentando o valor de incremento ou decremento (atualmente em 0,3).

Escrevendo o código: interagindo com sistema manualmente e via web

Para acionar a irrigação de forma manual ou remotamente, defini, respectivamente, a função callback e a função enableServer() que é invocada tanto dentro da função padrão loop() quanto dentro de callback.

Para o acionamento manual, a função callback já foi definida no setup, e foi implementada da seguinte forma:

void callback() {
turnOnIrrigation();
turnOffIrrigation();
enableServer();
}

No caso do servidor remoto, definido na função enableServer, começamos disponiblizando o servidor (ativado anteriomente na porta 80 e que terá o endereço IP da rede local - podendo ser mapeado para a rede externa caso você utilize algum proxy, servidor DNS, etc.):

void enableServer() {
// Criar um servidor http usando a biblioteca WiFi.h.
WiFiClient client = server.available();


Neste caso, a variável client será de fato o cliente que tentar realizar requisições ao servidor que disponibilizamos. Ou seja, se nosso sistema obtiver o IP local 192.168.1.10, quando um usuário acessar localmente via navegador, por exemplo: http://192.168.1.10, a variável client irá armazenar dados desse(a) acesso/requição. Assim:

if(client) {
currentTime = millis();
previousTime = currentTime;
Serial.println("Novo cliente.");
String currentLine = ""; // Variável de controle para leitura da requisição HTTP.

// Calcula a duração da requisição.
elapsedTime = currentTime - previousTime;
// Caso o cliente esteja conectado e não tenha ocorrido um timemout.

Caso um cliente faça uma requisição ao servidor iniciado no nosso sistema, começamos a controlar o tempo da requisição para evitar casos de indisponibilidade:

while(client.connected() && elapsedTime <= timeoutTime) {
    currentTime = millis();
elapsedTime = currentTime - previousTime;

Enquanto a requisição for válida e o cliente estiver conectado, vamos começar a ler dados da sua requisição:

if(client.available()) {
    char c = client.read();
Serial.write(c);
httpHeader += c;

A variável httpHeader será usada para armazenarmos esses dados da requisição HTTP feita pelo cliente. E, quando chegamos no final da leitura desses dados, será montada a resposta HTTP.

Define o cabeçalho da resposta:

if(c == '\n') {
// Define o HTTP response.
if(currentLine.length() == 0) {
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();

Verifica se a requisição foi de ativação ou desativação da irrigação:

if(httpHeader.indexOf("GET /27/on") >= 0){
turnOnIrrigation();
} else if(httpHeader.indexOf("GET /27/off") >= 0){
turnOffIrrigation();
}

E mandamos o status atual em forma de uma página HTML:

// Escreve o cabeçalho da página HTML a ser exibida na resposta ao cliente.
client.println("<!DOCTYPE html><html>");
client.println("<head><meta name=\"viewport\" content=\"width=device-width,
                            initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
// CSS to style the on/off buttons
client.println("<style>html { font-family: Helvetica; display: inline-block;
                            margin: 0px auto; text-align: center;}");
client.println(".button { background-color: #4CAF50; border: none; color: white;
                            padding: 16px 40px;");
client.println("text-decoration: none; font-size: 30px; margin: 2px;
                            cursor: pointer;}");
client.println(".button2 {background-color: #555555;}</style></head>");
// Título da página.
client.println("<body><h1>ESP32 Web Server</h1>");
// Se o estado da variável outputPin27StateOn estiver false, exibde o botão ON
if (outputPin27StateOn==false) {
// Exibe o estado atual do pin, e os botões ON/OFF para o RELAY_PIN
client.println("<p>GPIO 27 - Status - OFF </p>");
client.println("<p><a href=\"/27/on\"><button class=\"button\">ON</button></a>
                              </p>");
} else {
client.println("<p>GPIO 27 - Status - ON </p>");
client.println("<p><a href=\"/27/off\"><button class=\"button button2\">OFF
                                                    </button></a></p>");
}
client.println("</body></html>");
// A resposta HTTP finaliza com uma outra linha em branco
client.println();

Se você não entende muito da linguagem HTML, o código acima simplesmente adiciona um botão para acionar ou desativar a irrigação. Esse botão possui um link que permitirá ao usuário realizar a requisição HTTP que discutimos anteriormente. 

Código completo: aqui.

Gambiarra e Conclusão

Como mencionei, este foi o meu primeiro projeto do tipo, e eu não tinha muitos materiais disponíveis. Então precisei dar um jeito para posicionar a mangueira e para abrigar o sistema, protegendo, principalmente contra a água. Para posicionar o servomotor e acoplá-lo à mangueira da bomba d'água, utilizei um pau de selfie que estava sem uso. O abrigo do sistema foi feito em uma caixa de papelão mesmo, que cortei alguns lados para passagem dos jumpers. O resultado final pode ser visto na imagem abaixo (apenas adicionei um painel solar para realizar o carregamento das baterias que alimentam o sistema):


Resultado final.

Postar um comentário

0 Comentários

Ad Code