IoTIoTWiFiMQTTAPICloudESP32

IoT na Prática: Conectando Dispositivos à Internet

Guia prático para implementar projetos IoT, desde conceitos básicos até deployment em produção com WiFi, MQTT, APIs REST e plataformas em nuvem.

Fixtron Circuits
18/01/2025
16 de leitura
Artigo
Compartilhar

IoT na Prática: Conectando Dispositivos à Internet

IoT na Prática: Conectando Dispositivos à InternetIoT na Prática: Conectando Dispositivos à Internet

A Internet das Coisas (IoT) revolucionou a forma como interagimos com dispositivos eletrônicos. Este guia prático vai ensinar você a criar sistemas IoT robustos e escaláveis, desde o hardware até a nuvem.

Introdução ao IoT

O que é IoT?

IoT é um ecossistema de dispositivos físicos conectados à internet, capazes de:

  • Coletar dados de sensores
  • Comunicar-se com outros dispositivos
  • Processar informações localmente ou na nuvem
  • Executar ações baseadas em dados recebidos

Componentes de um Sistema IoT

graph LR
    A[Sensores] --> B[Microcontrolador]
    B --> C[Conectividade]
    C --> D[Broker/Gateway]
    D --> E[Cloud Platform]
    E --> F[Aplicação Web/Mobile]
    F --> B

Arquitetura de Sistemas IoT

Camadas da Arquitetura IoT

| Camada | Função | Tecnologias | |--------|--------|-------------| | Dispositivos | Coleta de dados, atuação | ESP32, Arduino, Sensores | | Conectividade | Transmissão de dados | WiFi, LoRa, Bluetooth, 4G | | Edge Computing | Processamento local | Edge servers, Gateways | | Cloud | Armazenamento, processamento | AWS IoT, Azure IoT, Google Cloud | | Aplicação | Interface do usuário | Web apps, Mobile apps, Dashboards |

Padrões de Comunicação

// Padrão Publisher/Subscriber
class IoTDevice {
private:
  String deviceId;
  String topicPrefix;
  
public:
  IoTDevice(String id) : deviceId(id) {
    topicPrefix = "devices/" + deviceId;
  }
  
  // Publicar dados de sensor
  void publishSensorData(String sensorType, float value) {
    String topic = topicPrefix + "/sensors/" + sensorType;
    String payload = createSensorPayload(sensorType, value);
    mqtt.publish(topic.c_str(), payload.c_str());
  }
  
  // Subscrever a comandos
  void subscribeToCommands() {
    String commandTopic = topicPrefix + "/commands/+";
    mqtt.subscribe(commandTopic.c_str());
  }
  
  // Processar comandos recebidos
  void handleCommand(String command, String parameter) {
    if (command == "led") {
      controlLED(parameter == "on");
    } else if (command == "restart") {
      ESP.restart();
    } else if (command == "config") {
      updateConfiguration(parameter);
    }
  }
};

Conectividade WiFi

Gerenciamento Robusto de WiFi

#include <WiFi.h>
#include <WiFiMulti.h>
#include <WiFiManager.h>

class WiFiHandler {
private:
  WiFiMulti wifiMulti;
  unsigned long lastConnectionAttempt;
  unsigned long reconnectInterval;
  bool isConnected;
  
public:
  WiFiHandler() : lastConnectionAttempt(0), reconnectInterval(30000), isConnected(false) {}
  
  void addNetwork(const char* ssid, const char* password) {
    wifiMulti.addAP(ssid, password);
  }
  
  void begin() {
    WiFi.mode(WIFI_STA);
    WiFi.setAutoReconnect(true);
    
    // Configurar eventos WiFi
    WiFi.onEvent([this](WiFiEvent_t event) {
      handleWiFiEvent(event);
    });
    
    // Tentar conexão inicial
    connect();
  }
  
  void loop() {
    if (!isConnected && (millis() - lastConnectionAttempt > reconnectInterval)) {
      connect();
    }
  }
  
  void connect() {
    Serial.println("Tentando conectar ao WiFi...");
    lastConnectionAttempt = millis();
    
    if (wifiMulti.run() == WL_CONNECTED) {
      isConnected = true;
      Serial.println("WiFi conectado!");
      Serial.printf("IP: %s\n", WiFi.localIP().toString().c_str());
      Serial.printf("RSSI: %d dBm\n", WiFi.RSSI());
    } else {
      Serial.println("Falha na conexão WiFi");
      isConnected = false;
    }
  }
  
  void handleWiFiEvent(WiFiEvent_t event) {
    switch(event) {
      case ARDUINO_EVENT_WIFI_STA_CONNECTED:
        Serial.println("WiFi conectado");
        break;
        
      case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
        Serial.println("WiFi desconectado");
        isConnected = false;
        break;
        
      case ARDUINO_EVENT_WIFI_STA_GOT_IP:
        isConnected = true;
        Serial.printf("IP obtido: %s\n", WiFi.localIP().toString().c_str());
        break;
        
      default:
        break;
    }
  }
  
  bool connected() {
    return isConnected && (WiFi.status() == WL_CONNECTED);
  }
  
  String getConnectionInfo() {
    if (!connected()) return "Desconectado";
    
    return String("SSID: ") + WiFi.SSID() + 
           ", IP: " + WiFi.localIP().toString() +
           ", RSSI: " + String(WiFi.RSSI()) + " dBm";
  }
};

// Configuração automática com WiFiManager
void setupAutoConfig() {
  WiFiManager wm;
  
  // Configurar parâmetros customizados
  WiFiManagerParameter mqtt_server("server", "MQTT Server", "broker.hivemq.com", 40);
  WiFiManagerParameter mqtt_port("port", "MQTT Port", "1883", 6);
  WiFiManagerParameter device_name("name", "Device Name", "ESP32_Device", 32);
  
  wm.addParameter(&mqtt_server);
  wm.addParameter(&mqtt_port);
  wm.addParameter(&device_name);
  
  // Tentar conectar ou abrir portal de configuração
  if (!wm.autoConnect("ESP32-Setup", "password123")) {
    Serial.println("Falha na configuração");
    ESP.restart();
  }
  
  // Salvar parâmetros customizados
  String serverValue = mqtt_server.getValue();
  String portValue = mqtt_port.getValue();
  String nameValue = device_name.getValue();
  
  // Salvar na EEPROM ou SPIFFS
  saveConfiguration(serverValue, portValue, nameValue);
}

Protocolos de Comunicação

HTTP/HTTPS para APIs REST

#include <HTTPClient.h>
#include <ArduinoJson.h>

class RestAPIClient {
private:
  String baseURL;
  String apiKey;
  HTTPClient http;
  
public:
  RestAPIClient(String url, String key) : baseURL(url), apiKey(key) {}
  
  bool sendSensorData(String deviceId, float temperature, float humidity) {
    http.begin(baseURL + "/api/devices/" + deviceId + "/data");
    http.addHeader("Content-Type", "application/json");
    http.addHeader("Authorization", "Bearer " + apiKey);
    
    DynamicJsonDocument doc(200);
    doc["timestamp"] = millis();
    doc["temperature"] = temperature;
    doc["humidity"] = humidity;
    doc["device_id"] = deviceId;
    
    String payload;
    serializeJson(doc, payload);
    
    int httpCode = http.POST(payload);
    
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {
      String response = http.getString();
      Serial.println("Data sent successfully: " + response);
      http.end();
      return true;
    } else {
      Serial.printf("HTTP Error: %d\n", httpCode);
      http.end();
      return false;
    }
  }
  
  String getDeviceConfig(String deviceId) {
    http.begin(baseURL + "/api/devices/" + deviceId + "/config");
    http.addHeader("Authorization", "Bearer " + apiKey);
    
    int httpCode = http.GET();
    
    if (httpCode == HTTP_CODE_OK) {
      String response = http.getString();
      http.end();
      return response;
    } else {
      Serial.printf("Config fetch failed: %d\n", httpCode);
      http.end();
      return "{}";
    }
  }
  
  bool sendCommand(String deviceId, String command, String parameter) {
    http.begin(baseURL + "/api/devices/" + deviceId + "/commands");
    http.addHeader("Content-Type", "application/json");
    http.addHeader("Authorization", "Bearer " + apiKey);
    
    DynamicJsonDocument doc(150);
    doc["command"] = command;
    doc["parameter"] = parameter;
    doc["timestamp"] = millis();
    
    String payload;
    serializeJson(doc, payload);
    
    int httpCode = http.POST(payload);
    http.end();
    
    return (httpCode == HTTP_CODE_OK);
  }
};

MQTT na Prática

Cliente MQTT Robusto

#include <PubSubClient.h>
#include <ArduinoJson.h>

class MQTTManager {
private:
  WiFiClient wifiClient;
  PubSubClient client;
  String clientId;
  String username;
  String password;
  String server;
  int port;
  
  unsigned long lastReconnectAttempt;
  unsigned long lastHeartbeat;
  
  // Callbacks
  std::function<void(String, String)> messageCallback;
  std::function<void(bool)> connectionCallback;
  
public:
  MQTTManager(String srv, int prt, String user = "", String pass = "") 
    : server(srv), port(prt), username(user), password(pass),
      lastReconnectAttempt(0), lastHeartbeat(0) {
    
    clientId = "ESP32_" + String(ESP.getEfuseMac(), HEX);
    client.setClient(wifiClient);
    client.setServer(server.c_str(), port);
    
    client.setCallback([this](char* topic, byte* payload, unsigned int length) {
      this->onMessage(topic, payload, length);
    });
  }
  
  void setMessageCallback(std::function<void(String, String)> callback) {
    messageCallback = callback;
  }
  
  void setConnectionCallback(std::function<void(bool)> callback) {
    connectionCallback = callback;
  }
  
  void begin() {
    connect();
  }
  
  void loop() {
    if (!client.connected()) {
      if (millis() - lastReconnectAttempt > 5000) {
        lastReconnectAttempt = millis();
        if (connect()) {
          lastReconnectAttempt = 0;
        }
      }
    } else {
      client.loop();
      
      // Heartbeat
      if (millis() - lastHeartbeat > 60000) {
        publishHeartbeat();
        lastHeartbeat = millis();
      }
    }
  }
  
  bool connect() {
    Serial.printf("Conectando ao MQTT broker: %s:%d\n", server.c_str(), port);
    
    bool connected;
    if (username.length() > 0) {
      connected = client.connect(clientId.c_str(), username.c_str(), password.c_str());
    } else {
      connected = client.connect(clientId.c_str());
    }
    
    if (connected) {
      Serial.println("MQTT conectado!");
      
      // Subscrever a tópicos importantes
      subscribe("devices/" + clientId + "/commands/+");
      subscribe("global/broadcast");
      
      // Publicar status online
      publishStatus("online");
      
      if (connectionCallback) {
        connectionCallback(true);
      }
    } else {
      Serial.printf("MQTT falha, rc=%d\n", client.state());
      
      if (connectionCallback) {
        connectionCallback(false);
      }
    }
    
    return connected;
  }
  
  bool publish(String topic, String payload, bool retained = false) {
    if (client.connected()) {
      return client.publish(topic.c_str(), payload.c_str(), retained);
    }
    return false;
  }
  
  bool subscribe(String topic) {
    if (client.connected()) {
      Serial.println("Subscrevendo: " + topic);
      return client.subscribe(topic.c_str());
    }
    return false;
  }
  
  void publishSensorData(String sensorType, float value, String unit = "") {
    DynamicJsonDocument doc(200);
    doc["sensor"] = sensorType;
    doc["value"] = value;
    doc["unit"] = unit;
    doc["timestamp"] = millis();
    doc["device_id"] = clientId;
    
    String payload;
    serializeJson(doc, payload);
    
    String topic = "sensors/" + clientId + "/" + sensorType;
    publish(topic, payload);
  }
  
  void publishStatus(String status) {
    DynamicJsonDocument doc(150);
    doc["status"] = status;
    doc["timestamp"] = millis();
    doc["uptime"] = millis();
    doc["free_heap"] = ESP.getFreeHeap();
    
    String payload;
    serializeJson(doc, payload);
    
    publish("devices/" + clientId + "/status", payload, true);
  }
  
  void publishHeartbeat() {
    DynamicJsonDocument doc(200);
    doc["timestamp"] = millis();
    doc["uptime"] = millis();
    doc["free_heap"] = ESP.getFreeHeap();
    doc["wifi_rssi"] = WiFi.RSSI();
    doc["device_id"] = clientId;
    
    String payload;
    serializeJson(doc, payload);
    
    publish("heartbeat/" + clientId, payload);
  }
  
private:
  void onMessage(char* topic, byte* payload, unsigned int length) {
    String message;
    for (int i = 0; i < length; i++) {
      message += (char)payload[i];
    }
    
    Serial.printf("Mensagem recebida [%s]: %s\n", topic, message.c_str());
    
    if (messageCallback) {
      messageCallback(String(topic), message);
    }
  }
};

// Uso do MQTTManager
void setupMQTT() {
  mqttManager.setMessageCallback([](String topic, String message) {
    // Parse do tópico
    if (topic.startsWith("devices/" + clientId + "/commands/")) {
      String command = topic.substring(topic.lastIndexOf('/') + 1);
      handleCommand(command, message);
    } else if (topic == "global/broadcast") {
      handleBroadcast(message);
    }
  });
  
  mqttManager.setConnectionCallback([](bool connected) {
    if (connected) {
      Serial.println("MQTT conectado - dispositivo online");
    } else {
      Serial.println("MQTT desconectado - operação offline");
    }
  });
  
  mqttManager.begin();
}

APIs REST para IoT

Servidor Web Embarcado

#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>

class IoTWebServer {
private:
  AsyncWebServer server;
  String deviceId;
  
public:
  IoTWebServer(int port = 80) : server(port) {
    deviceId = "ESP32_" + String(ESP.getEfuseMac(), HEX);
  }
  
  void begin() {
    // Servir arquivos estáticos
    server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
    
    // API Endpoints
    setupAPIRoutes();
    
    // CORS headers
    DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
    DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
    DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");
    
    server.begin();
    Serial.println("Servidor web iniciado");
  }
  
private:
  void setupAPIRoutes() {
    // GET /api/status
    server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
      DynamicJsonDocument doc(300);
      doc["device_id"] = deviceId;
      doc["uptime"] = millis();
      doc["free_heap"] = ESP.getFreeHeap();
      doc["wifi_rssi"] = WiFi.RSSI();
      doc["wifi_ssid"] = WiFi.SSID();
      doc["ip_address"] = WiFi.localIP().toString();
      
      String response;
      serializeJson(doc, response);
      
      request->send(200, "application/json", response);
    });
    
    // GET /api/sensors
    server.on("/api/sensors", HTTP_GET, [this](AsyncWebServerRequest *request) {
      DynamicJsonDocument doc(500);
      JsonArray sensors = doc.createNestedArray("sensors");
      
      // Adicionar dados dos sensores
      JsonObject temp = sensors.createNestedObject();
      temp["type"] = "temperature";
      temp["value"] = readTemperature();
      temp["unit"] = "°C";
      temp["timestamp"] = millis();
      
      JsonObject hum = sensors.createNestedObject();
      hum["type"] = "humidity";
      hum["value"] = readHumidity();
      hum["unit"] = "%";
      hum["timestamp"] = millis();
      
      String response;
      serializeJson(doc, response);
      
      request->send(200, "application/json", response);
    });
    
    // POST /api/control
    server.on("/api/control", HTTP_POST, [this](AsyncWebServerRequest *request) {}, 
              NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
      
      String body = String((char*)data).substring(0, len);
      
      DynamicJsonDocument doc(200);
      DeserializationError error = deserializeJson(doc, body);
      
      if (error) {
        request->send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
        return;
      }
      
      String command = doc["command"];
      String parameter = doc["parameter"];
      
      bool success = executeCommand(command, parameter);
      
      DynamicJsonDocument response(150);
      response["success"] = success;
      response["command"] = command;
      response["parameter"] = parameter;
      response["timestamp"] = millis();
      
      String responseStr;
      serializeJson(response, responseStr);
      
      request->send(success ? 200 : 400, "application/json", responseStr);
    });
    
    // GET /api/config
    server.on("/api/config", HTTP_GET, [this](AsyncWebServerRequest *request) {
      String config = loadConfiguration();
      request->send(200, "application/json", config);
    });
    
    // PUT /api/config
    server.on("/api/config", HTTP_PUT, [this](AsyncWebServerRequest *request) {}, 
              NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
      
      String body = String((char*)data).substring(0, len);
      
      if (saveConfiguration(body)) {
        request->send(200, "application/json", "{\"success\":true}");
      } else {
        request->send(500, "application/json", "{\"error\":\"Failed to save config\"}");
      }
    });
    
    // WebSocket para dados em tempo real
    AsyncWebSocket* ws = new AsyncWebSocket("/ws");
    
    ws->onEvent([this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
      if (type == WS_EVT_CONNECT) {
        Serial.printf("WebSocket client #%u connected\n", client->id());
        
        // Enviar dados iniciais
        DynamicJsonDocument doc(200);
        doc["type"] = "welcome";
        doc["device_id"] = deviceId;
        doc["timestamp"] = millis();
        
        String message;
        serializeJson(doc, message);
        client->text(message);
        
      } else if (type == WS_EVT_DISCONNECT) {
        Serial.printf("WebSocket client #%u disconnected\n", client->id());
      }
    });
    
    server.addHandler(ws);
  }
  
  bool executeCommand(String command, String parameter) {
    if (command == "led") {
      digitalWrite(LED_PIN, parameter == "on" ? HIGH : LOW);
      return true;
    } else if (command == "restart") {
      ESP.restart();
      return true;
    } else if (command == "reset_wifi") {
      WiFi.disconnect(true);
      return true;
    }
    
    return false;
  }
  
  float readTemperature() {
    // Implementar leitura do sensor
    return 25.5; // Exemplo
  }
  
  float readHumidity() {
    // Implementar leitura do sensor
    return 60.0; // Exemplo
  }
  
  String loadConfiguration() {
    // Carregar configuração do SPIFFS
    File file = SPIFFS.open("/config.json", "r");
    if (!file) {
      return "{}";
    }
    
    String config = file.readString();
    file.close();
    return config;
  }
  
  bool saveConfiguration(String config) {
    File file = SPIFFS.open("/config.json", "w");
    if (!file) {
      return false;
    }
    
    file.print(config);
    file.close();
    return true;
  }
};

Segurança em IoT

Implementação de Segurança

#include "mbedtls/md.h"
#include "mbedtls/base64.h"

class IoTSecurity {
private:
  String deviceSecret;
  String apiKey;
  
public:
  IoTSecurity(String secret, String key) : deviceSecret(secret), apiKey(key) {}
  
  // Gerar token JWT simples
  String generateJWT(String payload) {
    String header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
    
    String encodedHeader = base64Encode(header);
    String encodedPayload = base64Encode(payload);
    
    String signature = generateHMAC(encodedHeader + "." + encodedPayload);
    String encodedSignature = base64Encode(signature);
    
    return encodedHeader + "." + encodedPayload + "." + encodedSignature;
  }
  
  // Validar assinatura
  bool validateSignature(String data, String signature) {
    String expectedSignature = generateHMAC(data);
    return (signature == expectedSignature);
  }
  
  // Criptografar dados sensíveis
  String encryptData(String data) {
    // Implementação simplificada - use uma biblioteca de criptografia real
    String encrypted = "";
    for (int i = 0; i < data.length(); i++) {
      encrypted += char(data[i] ^ deviceSecret[i % deviceSecret.length()]);
    }
    return base64Encode(encrypted);
  }
  
  String decryptData(String encryptedData) {
    String decoded = base64Decode(encryptedData);
    String decrypted = "";
    for (int i = 0; i < decoded.length(); i++) {
      decrypted += char(decoded[i] ^ deviceSecret[i % deviceSecret.length()]);
    }
    return decrypted;
  }
  
private:
  String generateHMAC(String data) {
    byte hmacResult[32];
    
    mbedtls_md_context_t ctx;
    mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
    
    mbedtls_md_init(&ctx);
    mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
    mbedtls_md_hmac_starts(&ctx, (const unsigned char*)deviceSecret.c_str(), deviceSecret.length());
    mbedtls_md_hmac_update(&ctx, (const unsigned char*)data.c_str(), data.length());
    mbedtls_md_hmac_finish(&ctx, hmacResult);
    mbedtls_md_free(&ctx);
    
    String result = "";
    for (int i = 0; i < 32; i++) {
      result += String(hmacResult[i], HEX);
    }
    
    return result;
  }
  
  String base64Encode(String data) {
    size_t encoded_len;
    mbedtls_base64_encode(NULL, 0, &encoded_len, (const unsigned char*)data.c_str(), data.length());
    
    unsigned char* encoded = new unsigned char[encoded_len];
    mbedtls_base64_encode(encoded, encoded_len, &encoded_len, (const unsigned char*)data.c_str(), data.length());
    
    String result = String((char*)encoded);
    delete[] encoded;
    
    return result;
  }
  
  String base64Decode(String encoded) {
    size_t decoded_len;
    mbedtls_base64_decode(NULL, 0, &decoded_len, (const unsigned char*)encoded.c_str(), encoded.length());
    
    unsigned char* decoded = new unsigned char[decoded_len];
    mbedtls_base64_decode(decoded, decoded_len, &decoded_len, (const unsigned char*)encoded.c_str(), encoded.length());
    
    String result = String((char*)decoded);
    delete[] decoded;
    
    return result;
  }
};

// Gerenciamento seguro de credenciais
class CredentialManager {
private:
  String encryptionKey;
  
public:
  CredentialManager() {
    // Gerar chave baseada no MAC do dispositivo
    encryptionKey = String(ESP.getEfuseMac(), HEX);
  }
  
  bool storeCredential(String key, String value) {
    String encrypted = encrypt(value);
    return writeToNVS(key, encrypted);
  }
  
  String getCredential(String key) {
    String encrypted = readFromNVS(key);
    if (encrypted.length() == 0) return "";
    return decrypt(encrypted);
  }
  
private:
  String encrypt(String data) {
    // Implementação simplificada
    return data; // Em produção, use AES
  }
  
  String decrypt(String data) {
    // Implementação simplificada
    return data; // Em produção, use AES
  }
  
  bool writeToNVS(String key, String value) {
    // Implementar escrita no NVS
    return true;
  }
  
  String readFromNVS(String key) {
    // Implementar leitura do NVS
    return "";
  }
};

Plataformas em Nuvem

Integração com AWS IoT Core

#include <WiFiClientSecure.h>
#include <PubSubClient.h>

class AWSIoTClient {
private:
  WiFiClientSecure wifiClient;
  PubSubClient client;
  String thingName;
  String awsEndpoint;
  
public:
  AWSIoTClient(String endpoint, String thing) : awsEndpoint(endpoint), thingName(thing) {
    client.setClient(wifiClient);
    client.setServer(awsEndpoint.c_str(), 8883);
  }
  
  bool connect() {
    // Configurar certificados TLS
    wifiClient.setCACert(AWS_CERT_CA);
    wifiClient.setCertificate(AWS_CERT_CRT);
    wifiClient.setPrivateKey(AWS_CERT_PRIVATE);
    
    if (client.connect(thingName.c_str())) {
      Serial.println("Conectado ao AWS IoT Core");
      
      // Subscrever ao tópico de comandos
      String commandTopic = "$aws/things/" + thingName + "/shadow/update/delta";
      client.subscribe(commandTopic.c_str());
      
      return true;
    }
    
    return false;
  }
  
  void publishTelemetry(float temperature, float humidity) {
    DynamicJsonDocument doc(300);
    
    // Formato AWS IoT Device Shadow
    JsonObject state = doc.createNestedObject("state");
    JsonObject reported = state.createNestedObject("reported");
    
    reported["temperature"] = temperature;
    reported["humidity"] = humidity;
    reported["timestamp"] = millis();
    
    String payload;
    serializeJson(doc, payload);
    
    String topic = "$aws/things/" + thingName + "/shadow/update";
    client.publish(topic.c_str(), payload.c_str());
  }
  
  void loop() {
    client.loop();
  }
};

Monitoramento e Analytics

Sistema de Logging

class IoTLogger {
private:
  enum LogLevel { DEBUG, INFO, WARNING, ERROR };
  String deviceId;
  
public:
  IoTLogger(String id) : deviceId(id) {}
  
  void debug(String message) {
    log(DEBUG, message);
  }
  
  void info(String message) {
    log(INFO, message);
  }
  
  void warning(String message) {
    log(WARNING, message);
  }
  
  void error(String message) {
    log(ERROR, message);
  }
  
private:
  void log(LogLevel level, String message) {
    DynamicJsonDocument doc(300);
    
    doc["device_id"] = deviceId;
    doc["level"] = levelToString(level);
    doc["message"] = message;
    doc["timestamp"] = millis();
    doc["free_heap"] = ESP.getFreeHeap();
    
    String logEntry;
    serializeJson(doc, logEntry);
    
    // Enviar para múltiplos destinos
    Serial.println(logEntry);
    sendToMQTT(logEntry);
    saveToSD(logEntry);
  }
  
  String levelToString(LogLevel level) {
    switch(level) {
      case DEBUG: return "DEBUG";
      case INFO: return "INFO";
      case WARNING: return "WARNING";
      case ERROR: return "ERROR";
      default: return "UNKNOWN";
    }
  }
  
  void sendToMQTT(String logEntry) {
    if (mqtt.connected()) {
      mqtt.publish("logs/" + deviceId, logEntry.c_str());
    }
  }
  
  void saveToSD(String logEntry) {
    // Implementar salvamento em SD card
  }
};

Projeto Prático Completo

Estação Meteorológica IoT

#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <BMP280.h>
#include <ArduinoJson.h>

class WeatherStation {
private:
  DHT dht;
  BMP280 bmp;
  WiFiHandler wifi;
  MQTTManager mqtt;
  IoTLogger logger;
  
  struct WeatherData {
    float temperature;
    float humidity;
    float pressure;
    float altitude;
    int lightLevel;
    String timestamp;
  };
  
  WeatherData currentData;
  unsigned long lastReading;
  unsigned long readingInterval;
  
public:
  WeatherStation() : 
    dht(DHT_PIN, DHT22),
    mqtt("broker.hivemq.com", 1883),
    logger("WeatherStation_001"),
    lastReading(0),
    readingInterval(60000) // 1 minuto
  {
    // Configurar redes WiFi
    wifi.addNetwork("SuaRede1", "senha1");
    wifi.addNetwork("SuaRede2", "senha2");
  }
  
  void setup() {
    Serial.begin(115200);
    
    // Inicializar sensores
    dht.begin();
    if (!bmp.begin()) {
      logger.error("Falha ao inicializar sensor BMP280");
    }
    
    // Configurar conectividade
    wifi.begin();
    mqtt.begin();
    
    // Configurar callbacks
    mqtt.setMessageCallback([this](String topic, String message) {
      handleMQTTMessage(topic, message);
    });
    
    logger.info("Estação meteorológica iniciada");
  }
  
  void loop() {
    wifi.loop();
    mqtt.loop();
    
    if (millis() - lastReading > readingInterval) {
      readSensors();
      publishData();
      lastReading = millis();
    }
    
    delay(1000);
  }
  
private:
  void readSensors() {
    currentData.temperature = dht.readTemperature();
    currentData.humidity = dht.readHumidity();
    currentData.pressure = bmp.readPressure() / 100.0; // hPa
    currentData.altitude = bmp.readAltitude(1013.25);  // Nível do mar
    currentData.lightLevel = analogRead(LDR_PIN);
    currentData.timestamp = String(millis());
    
    logger.info("Sensores lidos - T:" + String(currentData.temperature) + 
               "°C, H:" + String(currentData.humidity) + "%");
  }
  
  void publishData() {
    if (!mqtt.connected()) {
      logger.warning("MQTT desconectado - dados não enviados");
      return;
    }
    
    DynamicJsonDocument doc(400);
    
    doc["device_id"] = "WeatherStation_001";
    doc["location"] = "Jardim";
    doc["timestamp"] = currentData.timestamp;
    
    JsonObject sensors = doc.createNestedObject("sensors");
    sensors["temperature"] = currentData.temperature;
    sensors["humidity"] = currentData.humidity;
    sensors["pressure"] = currentData.pressure;
    sensors["altitude"] = currentData.altitude;
    sensors["light_level"] = currentData.lightLevel;
    
    // Calcular índices derivados
    float heatIndex = calculateHeatIndex(currentData.temperature, currentData.humidity);
    float dewPoint = calculateDewPoint(currentData.temperature, currentData.humidity);
    
    JsonObject derived = doc.createNestedObject("derived");
    derived["heat_index"] = heatIndex;
    derived["dew_point"] = dewPoint;
    derived["comfort_level"] = getComfortLevel(currentData.temperature, currentData.humidity);
    
    String payload;
    serializeJson(doc, payload);
    
    mqtt.publish("weather/station_001/data", payload);
    logger.info("Dados publicados via MQTT");
  }
  
  void handleMQTTMessage(String topic, String message) {
    logger.info("Comando recebido: " + topic + " = " + message);
    
    if (topic.endsWith("/config/interval")) {
      int newInterval = message.toInt();
      if (newInterval >= 10000) { // Mínimo 10 segundos
        readingInterval = newInterval;
        logger.info("Intervalo atualizado: " + String(newInterval) + "ms");
      }
    } else if (topic.endsWith("/commands/restart")) {
      logger.info("Comando de reinicialização recebido");
      ESP.restart();
    }
  }
  
  float calculateHeatIndex(float temp, float humidity) {
    // Fórmula do índice de calor
    float hi = -42.379 + 2.04901523 * temp + 10.14333127 * humidity;
    hi -= 0.22475541 * temp * humidity;
    hi -= 0.00683783 * temp * temp;
    hi -= 0.05481717 * humidity * humidity;
    hi += 0.00122874 * temp * temp * humidity;
    hi += 0.00085282 * temp * humidity * humidity;
    hi -= 0.00000199 * temp * temp * humidity * humidity;
    
    return hi;
  }
  
  float calculateDewPoint(float temp, float humidity) {
    float a = 17.27;
    float b = 237.7;
    float alpha = ((a * temp) / (b + temp)) + log(humidity / 100.0);
    return (b * alpha) / (a - alpha);
  }
  
  String getComfortLevel(float temp, float humidity) {
    if (temp < 18) return "Frio";
    if (temp > 30) return "Quente";
    if (humidity < 30) return "Seco";
    if (humidity > 70) return "Úmido";
    return "Confortável";
  }
};

WeatherStation station;

void setup() {
  station.setup();
}

void loop() {
  station.loop();
}

Conclusão

Implementar sistemas IoT robustos requer atenção a múltiplos aspectos: conectividade, protocolos, segurança e escalabilidade. As técnicas apresentadas neste guia fornecem uma base sólida para desenvolver soluções IoT profissionais.

Melhores Práticas:

  1. Sempre implemente reconnexão automática para WiFi e MQTT
  2. Use HTTPS/TLS para comunicações críticas
  3. Implemente logging abrangente para debugging
  4. Monitore o consumo de memória e performance
  5. Teste em condições reais de rede instável
  6. Documente APIs e protocolos utilizados

Próximos Passos:

  • Implementar OTA (Over-The-Air) updates
  • Adicionar machine learning edge
  • Integrar com dashboards como Grafana
  • Implementar alertas em tempo real
  • Escalar para múltiplos dispositivos

Lembre-se: IoT é sobre conectar o mundo físico ao digital de forma inteligente e útil!

Ver todos os artigos
Publicado em 18/01/2025