Como está o clima?

Voltando para casa de madrugada seria ótimo saber como está o clima – com a patroa, ou talvez você queira saber como está a atmosfera no trabalho.
E se tais informações pudessem ser obtidas de graça, na faixa, no vasco ? Você está bem próximo disso, basta seguir os passos :

Sensor -> ESP32 -> Servidor SSL -> Broker (MQTTS – mosquito) -> Dashboard (node-red) -> Você

Para tanto você precisará:

  • BME680 Sensor(es) – Um sensor de temperatura, umidade, pressão e gás (VOC).
  • ESP32 SoC – Um sistema-em-um-chip com microcontrolador integrado, Wi-Fi e Bluetooth.
  • Servidor gratuito na nuvem – ou um computador antigo, ou raspberry PI ou …

Uma visão geral

Sensor BME-680

O BME680 é um sensor de gás que integra sensores de gás, pressão, umidade e temperatura. Ele vem de fábrica com 2 endereços 0x76 e 0x77, permitindo pendurar dois em cada saída I2C. Com o custo de R$70 (em 2023) vale a pena experimentar o BMP280 por R$10, mas que só oferece pressão e temperatura.

ESP-32

O ESP32 é uma série de microcontroladores de baixo custo e de baixo consumo de energia. É chamado de um sistema-em-um-chip com microcontrolador integrado, Wi-Fi e Bluetooth.
O ESP32-S3-WROOM-1 custa (em 2023) ca. de R$80,00 , mas existem modelos mais baratos. Qualquer SoC com WI-Fi deve funcionar , até mesmo um Arduino com uma expansão (shield) Wi-Fi

O servidor

A maioria das pessoas usa o raspberry PI como solução de automação, mas é muito caro. Desde 2019 qualquer pessoa (estudante, curioso ou empresário) pode criar e manter sua infraestrutura computacional na nuvem pública da Oracle sem pagar nenhum centavo por isto.
TUDO ABSOLUTAMENTE DE GRAÇA! NO VASCO! FREE ou ALWAYS FREE!”
Você terá que fornecer:

  1. Um endereço de e-mail válido.
  2. Um número de celular válido.
  3. Um número de cartão de crédito válido.

Como cartão de crédito usei um virtual com um limite baixo, mas nunca me cobraram nada. O login exige instalação de um aplicativo para autenticação em 2 etapas.

Programando

Carregando o ESP32

Existem dezenas de tutoriais de como ligar um sensor no arduino. Possivelmente você terá que dividir a tarefa em etapas: primeiro fazer o esp32 piscar, depois reconhecer o sensor e enviar para a serial do PC, depois se conectar a uma rede Wi-Fi e só então enviar mensagens MQTT. Para testar o código utilize um broker (MQTT) para testes, algo como o Mosquitto.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-mqtt-publish-bme680-arduino/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/
#include <WiFi.h>
extern "C" {
  #include "freertos/FreeRTOS.h"
  #include "freertos/timers.h"
}
#include <AsyncMqttClient.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"
// Raspberry Pi Mosquitto MQTT Broker
#define MQTT_HOST IPAddress(192, 168, 1, XXX)
// For a cloud MQTT broker, type the domain name
//#define MQTT_HOST "example.com"
#define MQTT_PORT 1883
// Temperature MQTT Topics
#define MQTT_PUB_TEMP "esp/bme680/temperature"
#define MQTT_PUB_HUM  "esp/bme680/humidity"
#define MQTT_PUB_PRES "esp/bme680/pressure"
#define MQTT_PUB_GAS  "esp/bme680/gas"
/*#define BME_SCK 14
#define BME_MISO 12
#define BME_MOSI 13
#define BME_CS 15*/
Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
// Variables to hold sensor readings
float temperature;
float humidity;
float pressure;
float gasResistance;
AsyncMqttClient mqttClient;
TimerHandle_t mqttReconnectTimer;
TimerHandle_t wifiReconnectTimer;
unsigned long previousMillis = 0;   // Stores last time temperature was published
const long interval = 10000;        // Interval at which to publish sensor readings
void getBME680Readings(){
  // Tell BME680 to begin measurement.
  unsigned long endTime = bme.beginReading();
  if (endTime == 0) {
    Serial.println(F("Failed to begin reading :("));
    return;
  }
  if (!bme.endReading()) {
    Serial.println(F("Failed to complete reading :("));
    return;
  }
  temperature = bme.temperature;
  pressure = bme.pressure / 100.0;
  humidity = bme.humidity;
  gasResistance = bme.gas_resistance / 1000.0;
}
void connectToWifi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}
void connectToMqtt() {
  Serial.println("Connecting to MQTT...");
  mqttClient.connect();
}
void WiFiEvent(WiFiEvent_t event) {
  Serial.printf("[WiFi-event] event: %d\n", event);
  switch(event) {
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.println("WiFi connected");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());
      connectToMqtt();
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("WiFi lost connection");
      xTimerStop(mqttReconnectTimer, 0); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
      xTimerStart(wifiReconnectTimer, 0);
      break;
  }
}
void onMqttConnect(bool sessionPresent) {
  Serial.println("Connected to MQTT.");
  Serial.print("Session present: ");
  Serial.println(sessionPresent);
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  Serial.println("Disconnected from MQTT.");
  if (WiFi.isConnected()) {
    xTimerStart(mqttReconnectTimer, 0);
  }
}
/*void onMqttSubscribe(uint16_t packetId, uint8_t qos) {
  Serial.println("Subscribe acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
  Serial.print("  qos: ");
  Serial.println(qos);
}
void onMqttUnsubscribe(uint16_t packetId) {
  Serial.println("Unsubscribe acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}*/
void onMqttPublish(uint16_t packetId) {
  Serial.print("Publish acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}
void setup() {
  Serial.begin(115200);
  Serial.println();
  if (!bme.begin()) {
    Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
    while (1);
  }
  
  mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt));
  wifiReconnectTimer = xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToWifi));
  WiFi.onEvent(WiFiEvent);
  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  //mqttClient.onSubscribe(onMqttSubscribe);
  //mqttClient.onUnsubscribe(onMqttUnsubscribe);
  mqttClient.onPublish(onMqttPublish);
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  // If your broker requires authentication (username and password), set them below
  //mqttClient.setCredentials("REPlACE_WITH_YOUR_USER", "REPLACE_WITH_YOUR_PASSWORD");
  connectToWifi();
  
  // Set up oversampling and filter initialization
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms
}
void loop() {
  unsigned long currentMillis = millis();
  // Every X number of seconds (interval = 10 seconds) 
  // it publishes a new MQTT message
  if (currentMillis - previousMillis >= interval) {
    // Save the last time a new reading was published
    previousMillis = currentMillis;
    
    getBME680Readings();
    Serial.println();
    Serial.printf("Temperature = %.2f ºC \n", temperature);
    Serial.printf("Humidity = %.2f % \n", humidity);
    Serial.printf("Pressure = %.2f hPa \n", pressure);
    Serial.printf("Gas Resistance = %.2f KOhm \n", gasResistance);
    
    // Publish an MQTT message on topic esp/bme680/temperature
    uint16_t packetIdPub1 = mqttClient.publish(MQTT_PUB_TEMP, 1, true, String(temperature).c_str());
    Serial.printf("Publishing on topic %s at QoS 1, packetId: %i", MQTT_PUB_TEMP, packetIdPub1);
    Serial.printf("Message: %.2f \n", temperature);
    // Publish an MQTT message on topic esp/bme680/humidity
    uint16_t packetIdPub2 = mqttClient.publish(MQTT_PUB_HUM, 1, true, String(humidity).c_str());
    Serial.printf("Publishing on topic %s at QoS 1, packetId %i: ", MQTT_PUB_HUM, packetIdPub2);
    Serial.printf("Message: %.2f \n", humidity);
    // Publish an MQTT message on topic esp/bme680/pressure
    uint16_t packetIdPub3 = mqttClient.publish(MQTT_PUB_PRES, 1, true, String(pressure).c_str());
    Serial.printf("Publishing on topic %s at QoS 1, packetId %i: ", MQTT_PUB_PRES, packetIdPub3);
    Serial.printf("Message: %.2f \n", pressure);
    // Publish an MQTT message on topic esp/bme680/gas
    uint16_t packetIdPub4 = mqttClient.publish(MQTT_PUB_GAS, 1, true, String(gasResistance).c_str());
    Serial.printf("Publishing on topic %s at QoS 1, packetId %i: ", MQTT_PUB_GAS, packetIdPub4);
    Serial.printf("Message: %.2f \n", gasResistance);
  }
}

O projeto Mosquitto tem um broker público. Esta é a configuração mais fácil para testar, mas não há privacidade, pois todas as mensagens são públicas. Use isso apenas para fins de teste. Para usar o broker mosquitto público, configure a integração MQTT para se conectar ao broker test.mosquitto.org na porta 1883 ou 8883.

Servidor

Agora é hora de você criar um servidor na nuvem, ou você pode também instalar o linux em um computador local, o procedimento depois do servidor estar pronto será o mesmo para ambos (cloud ou local).

Estabelecendo Conexão com uma Instância do Linux em um Sistema Windows Usando PuTTY

No caso de um servidor local será mais fácil usar o teclado, mas caso o servidor seja remoto uma conexão ssh é necessária. A melhor ferramenta que conheço para windows é o PuTTY. Para tanto tenha em mãos :

Endereço IP , nome do Usuário. chave privada SSH e permissões SSH e que a porta22 não esta bloqueada.

No Cloud o endereço IP público está incluso, e a chave privada só pode ser baixada na criação da instância ( se não tem, apague e crie a instância novamente). O firewall do Cloud está fechado inicialmente, nem mesmo ping ele responde.

Considere comprar um domínio .com.br, torna tudo mais elegante. No registro.br custa R$40,00 por ano, existem domínios gratuitos também.

Let´s Encrypt

Antes de instalar o mosquitto e o node-red é necessário ter uma camada de segurança SSL. O Let´s Encrypt é uma autoridade certificadora gratuita que certificou 300 milhões de páginas web (no VASCO também).
Para tanto, um domínio é necessário. Caso ainda não possua, é possível prosseguir e usar os endereços IP – locais e públicos.
Abra um terminal ( no windows com Putty) e siga a instalação:

sudo apt-get update && sudo apt-get install certbot
sudo certbot certonly --standalone 

Isso levará alguns minutos e deve fazer uma série de perguntas, como endereço de e-mail, nome de domínio, etc (nada complicado) durante o processo.
Se tudo correu bem (como deveria), agora você terá os certificados em algum lugar como:
‘/etc/letsencrypt/live/your.domain.com/’.

Como o Let´s Encrypt foi pensado em total segurança, somente aplicativos com elevação de root (Apache, SQL, …) conseguem ler os arquivos, Para o mosquitto e node-red é necessário permitir a leitura. A pior solução disponível na internet é abrir tudo (chmod 777). Veja que alternativa elegante :

// Create group with root and ubuntu as members
sudo addgroup nodecert
sudo adduser ubuntu nodecert
// sudo adduser mosquitto nodecert
sudo adduser root nodecert
// Make the relevant letsencrypt folders owned by said group.
sudo chgrp -R nodecert /etc/letsencrypt/live
sudo chgrp -R nodecert /etc/letsencrypt/archive
// Allow group to open relevant folders
sudo chmod -R 750 /etc/letsencrypt/live
sudo chmod -R 750 /etc/letsencrypt/archive
Instalando o Mosquitto para mensagens MQTT criptografadas no Cloud

As mensagens MQTT são fundamentais na nuvem. A ideia por trás do texto é lidar com aplicativos industriais de IoT que leem sensores e relatam condições de um edifício, uma linha fabril para um servidor central, para então ser integrado à sistemas de automação predial para controlar a ventilação mecânica ou natural para melhorar o conforto dos usuários.

Neste guia uma estância é criada desde o início, veja que o firewall tem que ser aberto para ao menos a porta 1883 -MQTT – “Add an ingress rule for ports 80, 1883, 8883”. Eu uso apt-get , diferente do guia que instala com snap.

sudo apt-get install mosquitto
//enable  user mosquitto to read certificates Let's Encrypt
sudo adduser mosquitto nodecert

Instalado está, mas ainda é necessário alguns ajustes.
Autenticação de usuários também é importante, pois com o servidor aberto para internet qualquer um poderia acessá-lo.

Navegue até o diretório de instalação e crie uma lista de usuários e senhas.

cd /etc/mosquitto/conf.d
sudo nano credencial.txt

e então digite no editor, algo no formato:

usuario1:senha1
user2:pass2
guest:pass

Salve ^O e feche o edito com ^X. Agora temos que criptografar as senhas :

sudo mosquitto_passwd -U credencial.txt

E adicionar na configuração ao broker mosquitto. Eu considero uma má ideia mudar o arquivo original, melhor criar um separado com as mudanças.

sudo nano /etc/mosquitto/conf.d/default.conf

e preencher com as configurações:

listener 1883 0.0.0.0
allow_anonymous false
password_file /etc/mosquitto/conf.d/credencial.txt
listener 8883 0.0.0.0
keyfile  /etc/letsencrypt/live/your.domain.com/privkey.pem
certfile /etc/letsencrypt/live/your.domain.com/cert.pem
cafile   /etc/letsencrypt/live/your.domain.com/fullchain.pem

Cuidado com espaços em branco, corrompem o arquivo. Agora é hora de rodar o broker.

//verify status 
sudo systemctl status mosquitto
sudo systemctl restart mosquitto

Se tudo correu bem, sua instância do Mosquitto agora estará aceitando conexões TLS. Certifique-se de que, ao se conectar, especifique no aplicativo cliente que você está usando TLS e que a porta do servidor é 8883. Para verificar os logs enquanto o Mosquitto está em execução, você pode usar tail para monitorar o log da seguinte forma:

 sudo tail -f /var/log/mosquitto/mosquitto.log
Instalando o node-red

Agora tudo deve estar funcionando, mas não é possivel ver nada. As mensagens podem ser publicadas (publish) mas a assinatura tem de ser manual e não é nada divertido. Para automatizar o processo o nnode-red é necessário.

Node-RED é uma ferramenta de programação para conectar dispositivos de hardware, APIs e serviços on-line de maneiras novas e interessantes. Ele fornece um editor baseado em navegador que facilita a conexão de fluxos usando a ampla gama de nós na paleta que podem ser implantados em seu tempo de execução em um único clique.

A instalação oficial é complicada. Supõe que o npm node já estejam instalados:

// npm should be first installed
//sudo apt-get update
sudo apt-get install npm

Baixe e importe a chave fonte do Node GPG

//node should be first installed
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg

Crie um repositório deb

NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
//Optional: NODE_MAJOR can be changed depending on the version you need.
//NODE_MAJOR=16
//NODE_MAJOR=18
//NODE_MAJOR=20
//NODE_MAJOR=21

Instale node:

sudo apt-get update
sudo apt-get install nodejs -y

Finalmente instale o node-red

sudo npm install -g --unsafe-perm node-red

No ubuntu 22.04 não foi possivel instalar , pois o script não consegue sobrescrever: libnode-dev

//remove whatever she complains
sudo apt-get remove  libnode-dev
//try again
sudo npm install -g --unsafe-perm node-red

Alternativamente , Este guia para Raspberry Pi é bem mais simples e funciona para o ubuntu 22.04.

bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)

Antes de testar é bom garantir que o firewall não bloqueie nada.

sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 1880 -j ACCEPT
sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 1883 -j ACCEPT
sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 8883 -j ACCEPT
sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 443 -j ACCEPT
//Then save the iptable rules even after reboot next time by
sudo netfilter-persistent save
Protegendo o Node-RED

Por padrão, o editor Node-RED não é protegido – qualquer pessoa que possa acessar seu endereço IP pode acessar o editor e implantar alterações.
Isso só é adequado se você estiver executando em uma rede confiável.

  • Este guia descreve como você pode proteger o Node-RED. A segurança é dividida em três partes:
    • Habilitando o acesso HTTPS
    • Protegendo o editor e a
    • API de administração Protegendo os nós HTTP e o painel do Node-RED
Live

Veja uma demonstração em :

https://iot.tudoemclima.com.br:1880