DEV Community

Hugo Souza
Hugo Souza

Posted on

Cultivo Conectado: Do Conceito à Realidade de um Sistema IoT para Monitoramento Agrícola

Uma ilustração de uma horta monitorada por sensores conectados a um computador.

Em um mundo onde a tecnologia se entrelaça cada vez mais às nossas vidas, visando simplificar o nosso cotidiano, Hugo Tiburtino e eu (Hugo Souza) decidimos integrá-la ao ambiente doméstico para a produção de hortaliças. Essa integração possibilita uma produção natural, livre de resíduos dos defensivos agrícolas, promovendo melhorias significativas para a saúde, tanto física quanto mental. Além disso, contribui para a preservação do meio ambiente, uma vez que adotamos práticas sustentáveis.

Com o intuito de validar nossa possível solução, optamos por criar um mecanismo capaz de comprovar a eficácia de adubos orgânicos na produção. Escolhemos desenvolver um sistema de monitoramento utilizando a Internet das Coisas (IoT), que engloba a coleta, processamento e armazenamento de dados para uma análise mais aprofundada.

Visão Geral

Diante do contexto do projeto, nos deparamos com diversas questões essenciais que exigiam decisões ponderadas sobre as ferramentas, tecnologias e serviços a serem empregados no desenvolvimento da solução. Uma abordagem inicial cogitada envolvia a criação de um circuito elétrico, integrando um microcontrolador, junto a sensores de umidade e temperatura, para coletar e armazenar informações localmente, por exemplo, em um cartão de memória.

Contudo, rejeitamos prontamente essa alternativa diante da aspiração por um sistema que viabilizasse a visualização em “tempo real” dos dados, acessível por meio, por exemplo, de uma API REST ou WebSockets. Nesse sentido, a escolha recaiu sobre o ESP32, microcontrolador cujo módulo Wi-Fi integrado proporciona a conexão e comunicação via internet, o que permitiria a transmissão dos dados para posterior processamento e armazenamento. Após isso, surgiram outras questões relevantes, destacando-se as seguintes:

Devemos desenvolver uma API para facilitar a comunicação entre o microcontrolador e o sistema de processamento, viabilizando a inserção no banco de dados?

Qual protocolo devemos adotar para garantir que as mensagens enviadas não se percam durante o percurso?

Como garantir a segurança da transmissão dos dados entre o dispositivo e o sistema de processamento?

Tecnologias Utilizadas

Imagem real, capturada durante o desenvolvimento do sistema, mostra o circuito elétrico junto a duas pequenas plantas

Com as incertezas em relação à integração de ambos os sistemas, agora é o momento de explorar as tecnologias e serviços que escolhi para este projeto. Vou compartilhar não apenas as escolhas em si, mas também a lógica por trás delas, destacando as características mais importantes.

Todas as preocupações sobre a segurança na transmissão de dados e a seleção do protocolo foram dissipadas quando descobrimos o AWS IoT, um serviço da Amazon projetado para conectar dispositivos à nuvem de maneira fácil e segura. Essa solução eficiente permite a conexão de vários dispositivos IoT e o roteamento de trilhões de mensagens sem a necessidade de gerenciar a infraestrutura. A necessidade de criar uma API para simplificar a comunicação entre o microcontrolador e o sistema de processamento foi eliminada, pois o AWS IoT oferece uma maneira descomplicada de conectar e gerenciar dispositivos. Além disso, o serviço proporciona flexibilidade ao escolher entre protocolos como MQTT, HTTPS, MQTT sobre WSS e LoRaWAN. Para reforçar ainda mais a solução, a conexão e os dados são protegidos por autenticação mútua e criptografia de ponta a ponta.

Com a questão do tráfego de dados pela internet resolvida, surgiu a necessidade de capturar, processar e armazenar esses dados. Dado que a infraestrutura central já estava sendo desenvolvida na AWS, a integração com o AWS Lambda, um serviço serverless orientado a eventos, foi uma escolha natural. Essa opção permitiu a execução de código em resposta a eventos específicos, exigindo apenas a configuração do AWS IoT como o gatilho para ativar a função. Essa função é responsável por receber e processar as informações antes de armazená-las no banco de dados. Por fim, escolhi o DynamoDB devido à sua integração nativa com os serviços AWS, seu modelo de dados flexível e baixa latência.

Dessa forma, de maneira resumida, essas foram as tecnologias fundamentais para o desenvolvimento desta solução:

  • ESP32, módulo de sensores de umidade e sensor de temperatura
  • AWS IoT
  • AWS Lambda
  • Amazon DynamoDB

Arquitetura

Diagrama da arquitetura na AWS

A arquitetura desenvolvida para este projeto suporta a conexão de vários dispositivos IoT, embora, para o estudo de caso apresentado, tenha sido utilizado apenas um único ESP32. A robustez da arquitetura foi cuidadosamente planejada, sendo escalável e altamente disponível para acomodar futuras expansões. Os detalhes de implementação serão abordados ao longo deste artigo.

Funcionamento do Fluxo de Dados

  1. Estabelecimento da Conexão via MQTT: O ESP32 inicia o processo ao estabelecer uma conexão segura utilizando o protocolo MQTT com o AWS IoT. Essa comunicação tira proveito do módulo Wi-Fi integrado do ESP32 para a transferência de dados.

  2. AWS IoT: Os dados fluem pela rede e são recebidos pelo AWS IoT. Essa camada desempenha o papel de gerenciar a comunicação com diversos dispositivos IoT, atuando como a ponte entre os dispositivos e os serviços na nuvem da AWS.

  3. Execução da Função Lambda: Cada novo evento registrado no AWS IoT aciona a execução de uma função Lambda. Essa função oferece uma flexibilidade, permitindo validações dos dados recebidos, acionamento de alertas em situações específicas e outros processamentos customizados.

  4. Armazenamento no DynamoDB: Após o processamento, os dados são persistentemente armazenados em uma tabela no DynamoDB. Este serviço altamente escalável e totalmente gerenciado pela AWS proporciona uma solução eficaz para o armazenamento e consulta de dados.

Implementação

Agora irei dar foco na parte técnica, mostrando as principais partes da implementação desde os códigos, funcionamento interno, até as configurações gerais dos serviços da AWS. Esse artigo não possui como foco ser um tutorial, portanto caso queira replicar localmente o experimento, é possível acessar todas as implementações que estão disponíveis no repositório no GitHub.

ESP32

Caso você não conheça, o ESP32 é um microcontrolador de alto desempenho e baixo custo, projetado para aplicações de Internet das Coisas (IoT) e amplamente utilizado devido à sua versatilidade, conectividade integrada e poder computacional suficiente para atender às demandas de projetos IoT.

Um dos pontos chaves que levou a decisão por utiliza-lo neste projeto, foi devido a sua capacidade de conectividade, já que oferece várias opções de conectividade, incluindo Wi-Fi e Bluetooth integrados, essas características permitem que o ESP32 se comunique facilmente com redes locais sem fio e outros dispositivos. Além da conectividade, esse microcontrolador possui suporte a diversos protocolos.

Em relação a programação, possui compatibilidade com a IDE Arduino, permitindo facilmente o desenvolvimento dos códigos necessários para seu funcionamento e o upload do mesmo, dessa forma que foi desenvolvido o programa responsável pela coleta das métricas e envio para os serviços da AWS.

No contexto da programação, o dispositivo possui compatibilidade total com a Arduino IDE, proporcionando uma abordagem simplificada para o desenvolvimento dos códigos essenciais ao seu funcionamento. Através dessa integração, foi possível elaborar o programa encarregado da coleta das métricas, bem como a transmissão para os serviços da AWS, facilitando todo o processo de implementação e atualização do sistema.

Configuração da “Coisa” no AWS IoT

Interface visual do serviço AWS IoT

Na AWS, uma “coisa” ou recurso de item, é uma representação e/ou um registro do dispositivo físico na nuvem, é dessa forma que um dispositivo é configurado para funcionar com o AWS IoT. Sendo assim, ao criar o item, os seguintes certificados serão gerados:

  • Certificado da Autoridade de Certificação (CA — Certificate Authority): utilizado para verificar a autenticidade do servidor AWS durante a conexão segura.

  • Certificado do Cliente (CRT — Certificate): usado para autenticar o dispositivo junto ao servidor.

  • Chave Privada do Cliente (Private Key): necessária para estabelecer uma conexão segura e autenticada.

Todas essas informações são sensíveis, dessa maneira devem ser mantidas de maneira segura e protegida, sendo assim evite compartilha-las. Na implementação do ESP32 essas credenciais estão disponíveis no arquivo aws_secrets.h.

Bibliotecas utilizadas

Em resumo, essas foram as dependências utilizadas a implementação do código.

  • OneWire e DallasTemperature: Utilizados para comunicação com o sensor de temperatura.

  • WiFiClientSecure e PubSubClient: Responsável pela comunicação com o AWS IoT, estabelecendo uma conexão segura e gerenciamento do protocolo MQTT.

  • ArduinoJson: Facilita a manipulação de dados em formato JSON, usado na formatação e envio dos dados.

#include <OneWire.h>
#include <DallasTemperature.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
Enter fullscreen mode Exit fullscreen mode

Estabelecendo conexão via Wi-Fi

Para estabelecer a conexão com os servidores AWS, é necessário portanto, possuir inicialmente acesso à internet, para isso, o ESP32 foi configurado para operar como uma estação (STA), ou seja, como um cliente que se conecta a um ponto de acesso Wi-Fi. O WiFiClientSecure é uma classe que fornece uma implementação de cliente TCP seguro para comunicação através de conexões TLS/SSL, sendo especialmente útil quando é necessário estabelecer conexões seguras, como ao lidar com servidores que exigem uma camada adicional de segurança.

WiFiClientSecure net;

const char* ssid = "<WIFI_NAME>";
const char* password = "<WIFI_PASSWORD>";

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

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

  Serial.println("Connecting to WiFi");

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

  Serial.println(ssid);
}
Enter fullscreen mode Exit fullscreen mode

Realizando a conexão para a comunicação MQTT

Após a bem-sucedida conexão Wi-Fi, inicia-se o processo para estabelecer a conexão que viabilizará a comunicação com o AWS IoT. Para essa tarefa, fazemos uso da instância da classe WiFiClientSecure, a qual desempenha o papel de configurar os certificados da AWS. Esses certificados, necessários para garantir uma comunicação segura, são acessíveis ao código por meio da inclusão do arquivo aws_secrets.h.

No curso desse procedimento de estabelecimento de uma conexão segura com os servidores, utilizamos o cliente MQTT. Configurando o servidor e a porta segura (8883), assegurando que a conexão seja estabelecida no endpoint correto. Em seguida, realiza-se a tentativa de conexão, seguindo-se de casos específicos para lidar com o sucesso ou a falha dele.

Esse conjunto de passos não apenas garante a conectividade com os servidores, mas também proporciona uma camada adicional de segurança na transmissão de dados. Dessa forma, a implementação do cliente MQTT e a configuração apropriada estabelecem uma base para a comunicação segura e confiável entre o dispositivo ESP32 e a infraestrutura AWS.

PubSubClient client(net);

void establishAWSSecureConnection() {
  net.setCACert(AWS_CERT_CA);
  net.setCertificate(AWS_CERT_CRT);
  net.setPrivateKey(AWS_CERT_PRIVATE);

  client.setServer(AWS_IOT_ENDPOINT, 8883);

  Serial.print("Connecting to AWS...");

  while (!client.connect(AWS_THING_NAME)) {
    Serial.print(".");
    delay(100);
  }

  if (!client.connected()) {
    Serial.println("AWS IoT Timeout!");
    return;
  }

  Serial.println("CONNECTED");
}
Enter fullscreen mode Exit fullscreen mode

Precisão Temporal com NTP

A obtenção precisa de timestamps se revelou um desafio durante a implementação do projeto, uma vez que a precisão temporal é fundamental para garantir a qualidade dos dados coletados. O problema central residia na necessidade de obter horários e datas precisos, considerando diferentes formatos e padrões. A implementação padrão no ESP32 enfrenta dificuldades para assegurar uma sincronização precisa, especialmente em ambientes onde a precisão temporal é crucial. A solução adotada para superar essas limitações foi a integração do NTP (Network Time Protocol), essa abordagem permite que o ESP32 sincronize seu relógio interno com servidores de tempo confiáveis, garantindo uma referência temporal precisa.

const char* ntpServer = "pool.ntp.org";

void setup() {
 ...
 configTime(0, 0, ntpServer);
 ...
}

unsigned long getCurrentTimestamp() {
  time_t now;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    return 0;
  }
  time(&now);
  return now;
}
Enter fullscreen mode Exit fullscreen mode

Enviando mensagens para o AWS IoT

Com todo o ambiente preparado para comunicação com a infraestrutura do AWS IoT, é possível o envio dos dados coletados. O formato de mensagem escolhida foi em JSON, essa mensagem é formatada em uma estrutura que representa os dados dos sensores em duas hortas distintas (porém não é restrita apenas a duas, sendo capaz de suportar uma ou mais hortas). Cada jardim é representado por um objeto JSON contendo uma identificação única, o timestamp, a temperatura e a umidade. Antes de realizar a publicação da mensagem, é sempre necessário verificar se o cliente MQTT permanece conectado, caso a conexão tenha sido interrompida ocorre a tentativa de ser restabelecida. Em caso de sucesso, finalmente, a mensagem é publicada no tópico.

void publishSensorDataToAWS() {
  if (epochTime == 0) {
    Serial.println("Timestamp not ready. Skipping publish.");
    return;
  }

  StaticJsonDocument<400> jsonDocument;
  JsonArray dataArray = jsonDocument.createNestedArray("data");

  // Garden 1
  JsonObject garden1 = dataArray.createNestedObject();
  garden1["id"] = "garden_1";
  garden1["timestamp"] = epochTime;
  garden1["temperature"] = celsius;
  garden1["humidity"] = sensorHumidityOnePercentage;

  // Garden 2
  JsonObject garden2 = dataArray.createNestedObject();
  garden2["id"] = "garden_2";
  garden2["timestamp"] = epochTime;
  garden2["temperature"] = celsius;
  garden2["humidity"] = sensorHumidityTwoPercentage;

  String jsonMessage;
  serializeJson(jsonDocument, jsonMessage);

  if (!client.connected()) {
    establishAWSSecureConnection();  // Reconnect if disconnected
  }

  client.publish(AWS_IOT_DATA_TOPIC, jsonMessage.c_str());
  Serial.println("Message published to AWS");
}
Enter fullscreen mode Exit fullscreen mode

A mensagem enviada para o AWS IoT possui o seguinte formato:

{
 "data": [
  {
   "garden_id": "garden_1",
   "timestamp": 1704367109,
   "temperature": 25.8,
   "humidity": 68.2
  },
  {
   "garden_id": "garden_2",
   "timestamp": 1704367109,
   "temperature": 25.8,
   "humidity": 56.8
  }
 ]
}
Enter fullscreen mode Exit fullscreen mode

DynamoDB

Optei pelo DynamoDB como solução de banco de dados para a integração com os serviços AWS, dada sua integração nativa e flexibilidade de modelo de dados. A estruturação dos dados foi planejada de modo que cada linha do banco represente as métricas de uma horta específica. Essa abordagem inclui o identificador da horta (gardenId), data/hora (timestamp) e colunas dedicadas para temperatura e umidade.

Estrutura de dados armazenada no DynamoDB

No estágio atual do projeto, o foco está na escrita no banco de dados, enquanto a implementação da leitura está planejada para futuras iterações. As chaves de partição e classificação são definidas como gardenId e timestamp, respectivamente. É fundamental observar que o timestamp é exclusivo para cada métrica específica de uma determinada horta, garantindo a unicidade dentro do escopo da mesma. No entanto, é possível que diferentes hortas compartilhem o mesmo timestamp em registros distintos. A integração nativa com os serviços AWS facilita a conexão, especialmente quando combinada com funções AWS Lambda.

Função Lambda

A utilização da AWS Lambda no projeto se deu devido à natureza do fluxo de dados provenientes dos dispositivos IoT. Considerando que esses dispositivos enviam informações em intervalos espaçados, não era necessário manter uma infraestrutura dedicada 24 horas por dia. Para otimizar custos e garantir eficiência computacional, escolhi as funções Lambda, que operam sob demanda e são gerenciadas automaticamente pela AWS, proporcionando escalabilidade automática, uma vez que a execução ocorre apenas quando necessário.

Interface visual da AWS Lambda

A função Lambda implementada desempenha um papel importante no processamento dos eventos IoT. O código, escrito em Node.js e utilizando o SDK da AWS, recebe os dados do evento e realiza manipulações necessárias. No atual estágio, o processamento consiste principalmente na conversão do formato de timestamp de Unix para o padrão ISO 8601. Este é um passo inicial, e a função foi projetada para acomodar futuras melhorias.

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async (event) => {
    try {
        const data = event.data;

        await Promise.all(data.map(async (garden) => {
            const command = new PutCommand({
                TableName: 'GardenMetrics',
                Item: {
                    gardenId: garden.id,
                    timestamp: new Date(garden.timestamp * 1000).toISOString(),
                    temperature: garden.temperature,
                    humidity: garden.humidity,
                }
              });

            await docClient.send(command);
        }));

        return {
            statusCode: 200,
            body: JSON.stringify({ message: 'Lambda function executed successfully' })
        };
    } catch (error) {
        console.error(`Error processing event: ${JSON.stringify(event)}`, error);
        return {
            statusCode: 500,
            body: JSON.stringify({ error: 'Internal Server Error' })
        };
    }
};
Enter fullscreen mode Exit fullscreen mode

O trecho de código da função Lambda ilustra a eficácia do ambiente serverless e a integração com o DynamoDB. Ao receber eventos contendo dados de diferentes hortas, a função realiza operações de inserção no DynamoDB, armazenando informações como identificador da horta, timestamp convertido, temperatura e umidade. A estrutura modular da função permite expansões futuras, o plano inclui aprimorar a função para incorporar funcionalidades avançadas, como alertas automatizados. A ideia é utilizar notificações por e-mail ou SMS para informar sobre eventos fora do padrão.

Próximos Passos

Com a intenção de avançar no desenvolvimento do projeto, estou planejando criar um ambiente de horta em casa, adotando o conceito de hortas suspensas para otimizar o espaço disponível. Essa etapa representa um passo para colocar o sistema em “produção” e permitir a coleta contínua de métricas provenientes dos dispositivos IoT. Ao estabelecer esse ambiente real de cultivo, o foco principal é obter dados sobre o comportamento das plantas em condições reais. A coleta contínua dessas métricas servirá como base para análises, visando compreender melhor a dinâmica do ambiente de cultivo.

Ao integrar visualmente as métricas em gráficos informativos, a intenção final é criar um ambiente de cultivo sustentável e eficiente. A coleta sistemática de dados e a análise detalhada contribuirão para a evolução contínua do projeto, permitindo adaptações e melhorias conforme necessário. Essa abordagem prática reforça a aplicabilidade do sistema IoT na prática, unindo tecnologia e agricultura de maneira integrada.

Conclusão

Encerrando este artigo, realizei a implementação de uma infraestrutura que unifica dispositivos IoT, AWS IoT, DynamoDB e AWS Lambda. Essa combinação possibilita o monitoramento e a coleta eficientes de dados para o cultivo de pequenas plantas, reforçando a integração bem-sucedida entre tecnologia e agricultura sustentável.

Caso você esteja interessado em contribuir ou explorar o código-fonte, convido-o a visitar o repositório no GitHub. Sua colaboração é fundamental para a evolução deste projeto, pois podemos expandir as funcionalidades, otimizar o desempenho e promover a aplicação prática da Internet das Coisas. Acesse o repositório aqui e faça parte do crescimento desta iniciativa!

Top comments (0)