#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <FastLED.h>
#include <ESP32Servo.h>
/* ================= CONSTANTES ================= */
#define TEMP_MIN 20.0
#define TEMP_ALERTA_ON 30.0
#define TEMP_ALERTA_OFF 28.5
#define HUM_MIN 30.0
#define HUM_MAX 70.0
#define SENSOR_INTERVAL 2000
#define BLINK_INTERVAL 500
#define BEEP_DURATION 100
#define BEEP_PAUSE 100
#define HEARTBEAT_INTERVAL 10000
#define DHTPIN 14
#define DHTTYPE DHT22
#define LED_PIN 26
#define BUZZER_PIN 27
#define NEOPIXEL1_PIN 4
#define NUM_LEDS1 16
#define NEOPIXEL2_PIN 5
#define NUM_LEDS2 8
#define SERVO_PIN 13
#define MAX_SENSOR_FAILS 3
#define FILTRO_ALPHA 0.3
#define TEMP_DELTA_PUBLISH 0.3
#define HUM_DELTA_PUBLISH 1.0
/* ================= ENUM ================= */
enum EstadoSistema { FRIO, NORMAL, ALERTA };
enum ServoModo { LINEAR, DEGRAU };
/* ================= OBJETOS ================= */
DHT dht(DHTPIN, DHTTYPE);
CRGB ledsTemp[NUM_LEDS1]; // NeoPixel 1 - temperatura
CRGB ledsUnidade[NUM_LEDS2]; // NeoPixel 2 - unidade / status
Servo servo;
/* ================= WIFI ================= */
const char* ssid = "Wokwi-GUEST";
const char* password = "";
/* ================= MQTT ================= */
const char* mqttServer = "broker.hivemq.com";
const int mqttPort = 1883;
const char* topicPub = "TempHumData";
const char* topicMode = "led/mode";
const char* topicLed = "led/control";
const char* topicStatus = "esp32/status";
const char* topicHeartbeat = "esp32/heartbeat";
const char* topicServoMode = "servo/mode";
/* ================= VARIÁVEIS ================= */
WiFiClient espClient;
PubSubClient client(espClient);
EstadoSistema estadoAtual = NORMAL;
EstadoSistema ultimoEstado = NORMAL;
ServoModo servoModo = LINEAR;
bool modoAuto = true;
bool ledManualState = false;
bool blinkState = false;
float tempAtual = 0;
float tempAnteriorPublicada = 0;
float humAtual = 0;
float humAnteriorPublicada = 0;
int servoAtual = 0;
int sensorFails = 0;
unsigned long lastSensorMillis = 0;
unsigned long lastBlinkMillis = 0;
unsigned long lastBipMillis = 0;
/* ================= WIFI ================= */
void setupWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.begin(ssid, password);
unsigned long inicio = millis();
while (WiFi.status() != WL_CONNECTED && millis() - inicio < 10000) delay(500);
}
/* ================= MQTT ================= */
void reconnectMQTT() {
while (!client.connected()) {
String id = "esp32_" + String(random(0xffff), HEX);
if (client.connect(id.c_str())) {
client.subscribe(topicMode);
client.subscribe(topicLed);
client.subscribe(topicServoMode);
} else delay(3000);
}
}
void callback(char* topic, byte* payload, unsigned int length) {
String msg;
for (uint8_t i = 0; i < length; i++) msg += (char)payload[i];
msg.trim();
if (String(topic) == topicMode) modoAuto = (msg == "AUTO");
if (!modoAuto && String(topic) == topicLed) {
ledManualState = (msg == "ON");
digitalWrite(LED_PIN, ledManualState);
}
if (String(topic) == topicServoMode) servoModo = (msg == "DEGRAU") ? DEGRAU : LINEAR;
}
/* ================= FUNÇÕES DE SENSOR ================= */
void lerSensor() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) { sensorFails++; return; }
sensorFails = 0;
// Filtro exponencial
tempAtual = (tempAtual == 0) ? t : tempAtual * (1 - FILTRO_ALPHA) + t * FILTRO_ALPHA;
humAtual = (humAtual == 0) ? h : humAtual * (1 - FILTRO_ALPHA) + h * FILTRO_ALPHA;
// Publicar no MQTT apenas se houver mudança significativa
if (abs(tempAtual - tempAnteriorPublicada) >= TEMP_DELTA_PUBLISH ||
abs(humAtual - humAnteriorPublicada) >= HUM_DELTA_PUBLISH) {
String payload = String(tempAtual, 1) + "," + String(humAtual, 1);
client.publish(topicPub, payload.c_str());
tempAnteriorPublicada = tempAtual;
humAnteriorPublicada = humAtual;
}
}
/* ================= FUNÇÕES DE ESTADO ================= */
void atualizarEstado() {
bool alerta = false;
if (tempAtual >= TEMP_ALERTA_ON || humAtual < HUM_MIN || humAtual > HUM_MAX) alerta = true;
if (tempAtual <= TEMP_ALERTA_OFF && humAtual >= HUM_MIN && humAtual <= HUM_MAX) alerta = false;
if (tempAtual <= TEMP_MIN) estadoAtual = FRIO;
else if (alerta) estadoAtual = ALERTA;
else estadoAtual = NORMAL;
if (estadoAtual != ultimoEstado) {
const char* txt = estadoAtual == FRIO ? "FRIO" : estadoAtual == NORMAL ? "NORMAL" : "ALERTA";
client.publish(topicStatus, txt);
ultimoEstado = estadoAtual;
}
}
/* ================= CONTROLES ================= */
void controlarServo() {
int alvo = 0;
if (servoModo == DEGRAU) alvo = (estadoAtual == ALERTA) ? 180 : 0;
else {
if (tempAtual >= TEMP_ALERTA_ON) alvo = 180;
else if (tempAtual <= TEMP_MIN) alvo = 0;
else alvo = map(tempAtual, TEMP_MIN, TEMP_ALERTA_ON, 0, 180);
}
servoAtual += (alvo - servoAtual) / 4;
servo.write(servoAtual);
}
void controlarNeoPixelTemp() {
if (estadoAtual == FRIO) fill_solid(ledsTemp, NUM_LEDS1, CRGB::Blue);
else if (estadoAtual == NORMAL) fill_solid(ledsTemp, NUM_LEDS1, CRGB::Green);
else fill_solid(ledsTemp, NUM_LEDS1, CRGB::Red);
FastLED.show();
}
void tocarBuzzer() {
unsigned long now = millis();
if (estadoAtual == FRIO && now - lastBipMillis >= 1000) {
lastBipMillis = now;
tone(BUZZER_PIN, 500, BEEP_DURATION);
} else if (estadoAtual == NORMAL && now - lastBipMillis >= 1500) {
lastBipMillis = now;
tone(BUZZER_PIN, 1000, BEEP_DURATION);
delay(BEEP_PAUSE);
tone(BUZZER_PIN, 1000, BEEP_DURATION);
} else if (estadoAtual == ALERTA && now - lastBipMillis >= 300) {
lastBipMillis = now;
tone(BUZZER_PIN, 2000, BEEP_DURATION);
}
}
void controlarNeoPixelUnidade() {
if (modoAuto) fill_solid(ledsUnidade, NUM_LEDS2, CRGB::Cyan);
else fill_solid(ledsUnidade, NUM_LEDS2, CRGB::Yellow);
FastLED.show();
}
/* ================= SETUP ================= */
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
noTone(BUZZER_PIN);
dht.begin();
servo.attach(SERVO_PIN);
FastLED.addLeds<WS2812, NEOPIXEL1_PIN, GRB>(ledsTemp, NUM_LEDS1);
FastLED.addLeds<WS2812, NEOPIXEL2_PIN, GRB>(ledsUnidade, NUM_LEDS2);
FastLED.clear(); FastLED.show();
setupWiFi();
client.setServer(mqttServer, mqttPort);
client.setCallback(callback);
}
/* ================= LOOP ================= */
void loop() {
setupWiFi();
if (!client.connected()) reconnectMQTT();
client.loop();
unsigned long now = millis();
// LED físico piscando
if (now - lastBlinkMillis >= BLINK_INTERVAL) {
lastBlinkMillis = now;
blinkState = !blinkState;
digitalWrite(LED_PIN, blinkState ? HIGH : LOW);
}
// Ler sensor
if (now - lastSensorMillis >= SENSOR_INTERVAL) {
lastSensorMillis = now;
lerSensor();
}
// Falha de sensor
if (sensorFails >= MAX_SENSOR_FAILS) {
fill_solid(ledsTemp, NUM_LEDS1, CRGB::Purple);
FastLED.show();
servo.write(0);
digitalWrite(LED_PIN, LOW);
noTone(BUZZER_PIN);
return;
}
if (!modoAuto) return;
atualizarEstado();
controlarServo();
controlarNeoPixelTemp();
tocarBuzzer();
controlarNeoPixelUnidade();
}