// PROJETO FINAL:
// INDUSTRIAL HEALTH & SAFETY IOT (IHS-IOT):
// SISTEMA DE MONITORAMENTO DE ESTRESSE TÉRMICO E
// DETECÇÃO DE GASES EM AMBIENTES DE PRODUÇÃO INDUSTRIAL
//
// Turma 1 - Embarcados - FIAP - CPQD
// ALUNOS: Diego Jaques; Gian Ferrari; Rodolfo Poloni;
//
// ============== Descrição Eventos ==============================================
// Envia (Ubidots):
// Analógicas: temperatura, umidade, gas (ppm)
// Digitais: tempAltaAlarm, umidAltaAlarm, gasAlarm
// Analógicas (limiares): AlarmTempAlta, AlarmUmidAlta, AlarmGas
//
// Recebe (Ubidots /lv):
// AlarmTempAlta, AlarmUmidAlta, AlarmGas
//
// Saídas:
// RELE_EXAUSTOR = tempAltaAlarm || gasAlarm
// RELE_SECADOR = umidAltaAlarm
// LED_VERDE = (!tempAltaAlarm && !umidAltaAlarm && !gasAlarm) || (tempAltaAlarm || umidAltaAlarm)
// LED_VERMELHO = gasAlarm || tempAltaAlarm || umidAltaAlarm
// BUZZER = tempAltaAlarm || umidAltaAlarm || gasAlarm
#include <WiFi.h> // Gerenciamento da conectividade Wi-Fi do ESP32
#include <PubSubClient.h> // Implementação do protocolo MQTT
#include <DHT.h> // Inteface com o DHT22
#include <math.h> // Funções matemáticas da linguagem C
#include <HTTPClient.h> // Comunicação via protocolo HTTP/HTTPS
// =============================================================================
// AJUSTES / CONFIGURAÇÕES
// =============================================================================
//======= WiFi Wokwi ============================================================
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASSWORD = "";
// ====== Ubidots ===============================================================
const char* MQTT_BROKER = "industrial.api.ubidots.com";
const int MQTT_PORT = 1883;
const char* MQTT_CLIENTID = " "; // manter vazio
const char* UBIDOTS_TOKEN = "BBUS-WKxSbubJ9nV6Z6UvtYhEKKDVdlAp43";
// Publicação (device)
const char* MQTT_PUB_TOPIC = "/v1.6/devices/esp32proj";
// Tópicos para receber limiares (/lv)
const char* MQTT_SUB_ALARM_TEMP = "/v1.6/devices/esp32proj/alarmtempalta/lv";
const char* MQTT_SUB_ALARM_UMID = "/v1.6/devices/esp32proj/alarmumidalta/lv";
const char* MQTT_SUB_ALARM_GAS = "/v1.6/devices/esp32proj/alarmgas/lv";
// ====== ThingSpeak (HTTP REST) ===============================================
// Observação importante:
// ThingSpeak geralmente limita a taxa de update (ex.: 15s).
static const char* TS_ENDPOINT = "http://api.thingspeak.com/update";
String TS_WRITE_API_KEY = "Y4J1JD0F9TM5CL3O";
// ========= DHT22 ==============================================================
#define DHT_PIN 32 // pino entrada sensor DHT22
#define DHTTYPE DHT22
DHT dht(DHT_PIN, DHTTYPE);
// ======== Pinos I/O ==========================================================
#define GAS_PIN 33 // pino entrada sensor gás MQ2
#define RELE_EXAUSTOR 17 // pino saída relé exaustor
#define RELE_SECADOR 16 // pino saída relé secador de ar
#define LED_VERDE 21 // pino saída acionar LED verde
#define LED_VERMELHO 19 // pino saída acionar LED vermelho
#define BUZZER_PIN 18 // pino saída acionar buzzer
// ======== Limiar defaults Alarmes ============================================
// (podem ser alterados pelo Ubidots via tópicos /lv)
float AlarmTempAlta = 50.0; // °C
float AlarmUmidAlta = 80.0; // %
float AlarmGas = 400.0; // PPM
// ======== Histerese (SOMENTE para baixo) ====================================
float DeltaTemp = 2.0; // °C
float DeltaUmid = 3.0; // %
float DeltaGas = 50.0; // PPM
// ======== Persistência ========================================================
// Precisa ficar 3s acima do setpoint para armar
// e 3s abaixo do (setpoint - delta) para normalizar
const unsigned long PERSIST_MS = 3000;
// ======== Filtro EMA (somente TEMP e UMID) ===================================
// alpha: 0 < alpha <= 1
// alpha menor = mais suave; alpha maior = mais rápido
float alphaTemp = 0.80;
float alphaUmid = 0.80;
// ======== Períodos internos de leitura (para não “forçar” sensor) =============
// DHT22: recomendado não ler muito rápido;
const unsigned long DHT_READ_PERIOD_MS = 500;
// MQ2: pode ler mais rápido;
const unsigned long GAS_READ_PERIOD_MS = 200;
// Timers de envio (UBI e TS)
const unsigned long UBIDOTS_PERIOD_MS = 2000;
const unsigned long THINGSPEAK_PERIOD_MS = 30000;
// =============================================================================
// Variáveis / Estado
// =============================================================================
// Leituras base
float temperatura = NAN;
float umidade = NAN;
int gasRaw = 0;
float gasPPM = 0.0;
// Leituras filtradas (EMA) - somente temp/umid
float tempEMA = NAN;
float umidEMA = NAN;
// Alarmes digitais (estado final)
bool tempAltaAlarm = false;
bool umidAltaAlarm = false;
bool gasAlarm = false;
// Timers para persistência (um ON e um OFF por sensor)
unsigned long tTempOn=0, tTempOff=0;
unsigned long tUmidOn=0, tUmidOff=0;
unsigned long tGasOn=0, tGasOff=0;
// Timers para leitura e envio
unsigned long lastDHTReadMs = 0;
unsigned long lastGasReadMs = 0;
unsigned long lastUbiMs = 0;
unsigned long lastTSMs = 0;
// Buffer para JSON do Ubidots
char msg[420];
WiFiClient espClient;
PubSubClient mqtt(espClient);
// =============================================================================
// CONEXÕES
// =============================================================================
// ========= Conexão WiFi =======================================
void connectWiFi() {
Serial.print("\n\n[WiFi] Conectando...");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(400);
Serial.print(".");
}
Serial.print("\n[WiFi] Conectado!!!");
}
// ========= Conexão MQTT ======================================
void setupMQTT() {
mqtt.setServer(MQTT_BROKER, MQTT_PORT);
mqtt.setCallback(mqttCallback);
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("\n\n[MQTT] Conectando...");
if (mqtt.connect(MQTT_CLIENTID, UBIDOTS_TOKEN, "")) {
Serial.print("\n[MQTT] Conectado!!!");
mqtt.subscribe(MQTT_SUB_ALARM_TEMP);
mqtt.subscribe(MQTT_SUB_ALARM_UMID);
mqtt.subscribe(MQTT_SUB_ALARM_GAS);
Serial.print("\n[MQTT Subscribed] AlarmTempAlta / AlarmUmidAlta / AlarmGas");
} else {
Serial.print("\n[MQTT] Falhou rc=");
Serial.print(mqtt.state());
Serial.print(" -> tentando novamente em 2s");
delay(2000);
}
}
}
// =============================================================================
// MQTT callback: recebe limiares do Ubidots
// =============================================================================
// Converte payload para float
static float payloadToFloat(const byte* payload, unsigned int length) {
char buf[32];
unsigned int n = (length < sizeof(buf) - 1) ? length : (sizeof(buf) - 1);
memcpy(buf, payload, n);
buf[n] = '\0';
// Blindagem simples para remover \r\n se vier no payload
for (unsigned int i = 0; i < n; i++) {
if (buf[i] == '\r' || buf[i] == '\n') buf[i] = '\0';
}
return atof(buf);
}
// Retorno no Ubidots
void mqttCallback(char* topic, byte* payload, unsigned int length) {
float val = payloadToFloat(payload, length);
if (strcmp(topic, MQTT_SUB_ALARM_TEMP) == 0) {
AlarmTempAlta = val;
Serial.print("\n[MQTT Sub] AlarmTempAlta [°C]: ");
Serial.print(AlarmTempAlta, 1);
}
else if (strcmp(topic, MQTT_SUB_ALARM_UMID) == 0) {
AlarmUmidAlta = val;
Serial.print("\n[MQTT Sub] AlarmUmidAlta [%]: ");
Serial.print(AlarmUmidAlta, 1);
}
else if (strcmp(topic, MQTT_SUB_ALARM_GAS) == 0) {
AlarmGas = val;
Serial.print("\n[MQTT Sub] AlarmGas [PPM]: ");
Serial.print(AlarmGas, 1);
}
}
// =============================================================================
// Funções utilitárias / base
// =============================================================================
// Calibração sensor MQ2 no Wokwi (GásRAW -> GásPPM)
static const int rawPts[] = {
868, 1234, 1656, 2345, 3146, 3337, 3497, 3672, 3751,
3821, 3916, 3954, 3990, 4009, 4024, 4036, 4039, 4041
};
static const float ppmPts[] = {
0.1, 0.3, 1.0, 5.0, 50.0, 100.0, 200.0, 525.0, 912.0,
1660.0, 5012.0, 9120.0, 19055.0, 31623.0, 50119.0,
79433.0, 91201.0, 100000.0
};
static const int NPTS = sizeof(rawPts) / sizeof(rawPts[0]);
// Interpolação em log10(ppm) por trechos (mais adequada para curva exponencial)
float rawToPPM_LogInterp(int r) {
if (r <= rawPts[0]) return ppmPts[0];
if (r >= rawPts[NPTS - 1]) return ppmPts[NPTS - 1];
for (int i = 0; i < NPTS - 1; i++) {
if (r >= rawPts[i] && r <= rawPts[i + 1]) {
float x0 = rawPts[i], x1 = rawPts[i + 1];
float y0 = log10f(ppmPts[i]);
float y1 = log10f(ppmPts[i + 1]);
float frac = (r - x0) / (x1 - x0);
return powf(10.0f, y0 + frac * (y1 - y0));
}
}
return ppmPts[NPTS - 1];
}
// Filtro Média curta para reduzir jitter (somente Gás)
int readGasRawAvg5() {
long sum = 0;
for (int i = 0; i < 5; i++) {
sum += analogRead(GAS_PIN);
delay(2);
}
return (int)(sum / 5);
}
// Filtro EMA (somente temp/umid)
// Se o anterior ainda é NAN, inicializa com a primeira amostra.
float applyEMA(float current, float prev, float alpha) {
if (isnan(prev)) return current;
return (1.0f - alpha) * prev + alpha * current;
}
// =============================================================================
// REGRAS / PROCESSAMENTO (EDGE)
// =============================================================================
// ========== Leitura dos sensores ==================================
// Obs.: se DHT falhar (NAN), mantém último valor válido.
void readSensors() {
unsigned long now = millis();
// Leitura DHT22
if (now - lastDHTReadMs >= DHT_READ_PERIOD_MS) {
lastDHTReadMs = now;
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) temperatura = t;
if (!isnan(h)) umidade = h;
// EMA somente em temperatura e umidade
if (!isnan(temperatura)) tempEMA = applyEMA(temperatura, tempEMA, alphaTemp);
if (!isnan(umidade)) umidEMA = applyEMA(umidade, umidEMA, alphaUmid);
}
// Leitura MQ2
if (now - lastGasReadMs >= GAS_READ_PERIOD_MS) {
lastGasReadMs = now;
gasRaw = readGasRawAvg5();
gasPPM = rawToPPM_LogInterp(gasRaw);
}
}
// ========= Persistência e Histerese Alarmes ================================
// - persistência de 3s para armar
// - persistência de 3s para normalizar
// - histerese SOMENTE para baixo (setpoint - delta)
void computeAlarms() {
unsigned long now = millis();
// Persistênica Temperatura (usa tempEMA)
if (!tempAltaAlarm) {
// ARMAR: precisa ficar >= setpoint por 3s
if (!isnan(tempEMA) && tempEMA >= AlarmTempAlta) {
if (tTempOn == 0) tTempOn = now;
if (now - tTempOn >= PERSIST_MS) {
tempAltaAlarm = true;
tTempOn = 0;
}
} else {
tTempOn = 0;
}
tTempOff = 0;
} else {
// NORMALIZAR: precisa ficar <= (setpoint - delta) por 3s
float offTemp = AlarmTempAlta - DeltaTemp;
if (!isnan(tempEMA) && tempEMA <= offTemp) {
if (tTempOff == 0) tTempOff = now;
if (now - tTempOff >= PERSIST_MS) {
tempAltaAlarm = false;
tTempOff = 0;
}
} else {
tTempOff = 0;
}
tTempOn = 0;
}
// Persistência Umidade (usa umidEMA)
if (!umidAltaAlarm) {
if (!isnan(umidEMA) && umidEMA >= AlarmUmidAlta) {
if (tUmidOn == 0) tUmidOn = now;
if (now - tUmidOn >= PERSIST_MS) {
umidAltaAlarm = true;
tUmidOn = 0;
}
} else {
tUmidOn = 0;
}
tUmidOff = 0;
} else {
// NORMALIZAR: precisa ficar <= (setpoint - delta) por 3s
float offUmid = AlarmUmidAlta - DeltaUmid;
if (!isnan(umidEMA) && umidEMA <= offUmid) {
if (tUmidOff == 0) tUmidOff = now;
if (now - tUmidOff >= PERSIST_MS) {
umidAltaAlarm = false;
tUmidOff = 0;
}
} else {
tUmidOff = 0;
}
tUmidOn = 0;
}
// Persistência GÁS (usa gasPPM & média movel)
if (!gasAlarm) {
if (gasPPM >= AlarmGas) {
if (tGasOn == 0) tGasOn = now;
if (now - tGasOn >= PERSIST_MS) {
gasAlarm = true;
tGasOn = 0;
}
} else {
tGasOn = 0;
}
tGasOff = 0;
} else {
// NORMALIZAR: precisa ficar <= (setpoint - delta) por 3s
float offGas = AlarmGas - DeltaGas;
if (gasPPM <= offGas) {
if (tGasOff == 0) tGasOff = now;
if (now - tGasOff >= PERSIST_MS) {
gasAlarm = false;
tGasOff = 0;
}
} else {
tGasOff = 0;
}
tGasOn = 0;
}
}
// ======= Acionamento Saídas ==========================================
void updateOutputs() {
// Relés
bool exaustorOn = tempAltaAlarm || gasAlarm;
bool secadorOn = umidAltaAlarm;
digitalWrite(RELE_EXAUSTOR, exaustorOn ? HIGH : LOW);
digitalWrite(RELE_SECADOR, secadorOn ? HIGH : LOW);
// Aciona LEDs
bool ledVerdeOn =
(!tempAltaAlarm && !umidAltaAlarm && !gasAlarm)
|| ((!gasAlarm && tempAltaAlarm) || (!gasAlarm && umidAltaAlarm));
bool ledVermelhoOn =
(gasAlarm || tempAltaAlarm || umidAltaAlarm);
digitalWrite(LED_VERDE, ledVerdeOn ? HIGH : LOW);
digitalWrite(LED_VERMELHO, ledVermelhoOn ? HIGH : LOW);
// Aciona Buzzer
bool buzzerOn = (tempAltaAlarm || umidAltaAlarm || gasAlarm);
digitalWrite(BUZZER_PIN, buzzerOn ? HIGH : LOW);
}
// =============================================================================
// Publicações (Ubidots e ThingSpeak)
// =============================================================================
// ====== Monta o JSON e publica no Ubidots (MQTT) ===================
void publishUbidots() {
// Enviamos TEMP/UMID filtrados (EMA) e GÁS sem EMA
float tSend = isnan(tempEMA) ? -999.0 : tempEMA;
float hSend = isnan(umidEMA) ? -999.0 : umidEMA;
snprintf(msg, sizeof(msg),
"{"
"\"temperatura\": %.1f, "
"\"umidade\": %.1f, "
"\"gas\": %.1f, "
"\"AlarmTempAlta\": %.1f, "
"\"AlarmUmidAlta\": %.1f, "
"\"AlarmGas\": %.1f, "
"\"tempAltaAlarm\": %d, "
"\"umidAltaAlarm\": %d, "
"\"gasAlarm\": %d"
"}",
tSend,
hSend,
gasPPM,
AlarmTempAlta,
AlarmUmidAlta,
AlarmGas,
tempAltaAlarm ? 1 : 0,
umidAltaAlarm ? 1 : 0,
gasAlarm ? 1 : 0
);
mqtt.publish(MQTT_PUB_TOPIC, msg);
Serial.print("\n\n[MQTT Pub]: ");
Serial.print(msg);
}
// ========= Envia para ThingSpeak ===========================
void publishThingSpeak() {
if (WiFi.status() != WL_CONNECTED) {
Serial.print("\n\n[ThingSpeak] WiFi Desconectado!");
return;
}
float tSend = isnan(tempEMA) ? -999.0 : tempEMA;
float hSend = isnan(umidEMA) ? -999.0 : umidEMA;
HTTPClient http;
String url = String(TS_ENDPOINT) + "?api_key=" + TS_WRITE_API_KEY +
"&field1=" + String(tSend, 1) +
"&field2=" + String(hSend, 1) +
"&field3=" + String(gasPPM, 1) +
"&field4=" + String(tempAltaAlarm ? 1 : 0) +
"&field5=" + String(umidAltaAlarm ? 1 : 0) +
"&field6=" + String(gasAlarm ? 1 : 0);
http.begin(url);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
String response = http.getString(); // normalmente retorna o entry_id
Serial.print("\n\n[ThingSpeak] Código de resposta: ");
Serial.print(httpResponseCode);
Serial.print("\n[ThingSpeak] Resposta: ");
Serial.println(response);
} else {
Serial.print("\n[ThingSpeak] Erro na requisição: ");
Serial.println(httpResponseCode);
}
http.end();
}
// ======================================================================
// SETUP
// ======================================================================
void setup() {
Serial.begin(115200); // inicializa monitor serial
dht.begin(); // inicializa DHT
analogReadResolution(12); // resolução ADC (0–4095)
analogSetPinAttenuation(GAS_PIN, ADC_11db); // faixa até ~3.3V no ESP32
// Configuração de pinos
pinMode(GAS_PIN, INPUT); // sensor MQ-2
pinMode(RELE_EXAUSTOR, OUTPUT); // relé exaustor
pinMode(RELE_SECADOR, OUTPUT); // relé secador
pinMode(LED_VERDE, OUTPUT); // LED verde
pinMode(LED_VERMELHO, OUTPUT); // LED vermelho
pinMode(BUZZER_PIN, OUTPUT); // buzzer
// Inicia com todas as saídas desligadas (estado seguro)
digitalWrite(RELE_EXAUSTOR, LOW);
digitalWrite(RELE_SECADOR, LOW);
digitalWrite(LED_VERDE, LOW);
digitalWrite(LED_VERMELHO, LOW);
digitalWrite(BUZZER_PIN, LOW);
// Conectividade
connectWiFi(); // conecta ao WiFi
setupMQTT(); // configura broker e callback
connectMQTT(); // conecta no Ubidots
// Inicializa timers (evita envio imediato duplicado)
unsigned long now = millis();
lastDHTReadMs = now;
lastGasReadMs = now;
lastUbiMs = now;
lastTSMs = now;
}
// ===================================================================
// LOOP
// ===================================================================
void loop() {
// Mantém MQTT ativo (necessário para receber /lv e manter conexão)
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
unsigned long now = millis();
// Leitura, processamento e acionamento local
readSensors();
computeAlarms();
updateOutputs();
// Ubidots: envia rápido (telemetria e alarmes)
if (now - lastUbiMs >= UBIDOTS_PERIOD_MS) {
lastUbiMs = now;
publishUbidots();
}
// ThingSpeak: enviar mais lento para respeitar rate limit
if (now - lastTSMs >= THINGSPEAK_PERIOD_MS) {
lastTSMs = now;
publishThingSpeak();
}
}
Exaustor
Secardor Ar
Verde: Normal
Vermelho: Alarme Gás
Amarelo: Alarme Temp/Umidade