#include <WiFi.h>
#include <PubSubClient.h>
#include <ESP32Servo.h>
#include <DHT.h>
#include <ArduinoJson.h>
// --- Definições de Hardware ---
#define DHTPIN 15
#define DHTTYPE DHT22
#define LDRPIN 34 // ADC1_CH6
#define PIN_FAN 12 // LED Azul (Ventilador)
#define PIN_HUMID 13 // LED Verde (Nebulizador)
#define PIN_ALARM 2 // LED Vermelho (Alarme Crítico)
#define PIN_SERVO 18 // Servomotor (Cortina do Aviário)
// --- Instâncias ---
DHT dht(DHTPIN, DHTTYPE);
Servo servoJanela;
WiFiClient espClient;
PubSubClient client(espClient);
// --- Configurações de Conectividade ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
// Tópicos MQTT
const char* topic_telemetry = "aviario/dados";
const char* topic_control = "aviario/controle";
// --- Variáveis Globais de Estado ---
float temperature = 0.0;
float humidity = 0.0;
float itu = 0.0;
float luxValue = 0.0;
bool modeAuto = true; // true = Automático, false = Manual
// Estados atuais dos atuadores
bool stateFan = false;
bool stateHumid = false;
int angleServo = 0;
bool alarmActive = false;
// Controle de tempo (millis)
unsigned long lastMsgTime = 0;
const long telemetryInterval = 2000; // Envia dados a cada 2 segundos
// Protótipos de Funções
void wifiReconnect();
void mqttReconnect();
void readSensors();
float calculateITU(float temp, float humi);
void automaticControl();
void manualControl(const String& message);
void updateOutputs();
void publishTelemetry();
void callback(char* topic, byte* payload, unsigned int length);
void setup() {
Serial.begin(115200);
Serial.println("Iniciando ClimaControl Aviários...");
// Configuração dos Pinos
pinMode(PIN_FAN, OUTPUT);
pinMode(PIN_HUMID, OUTPUT);
pinMode(PIN_ALARM, OUTPUT);
// Inicialização de Sensores e Atuadores
dht.begin();
servoJanela.attach(PIN_SERVO);
servoJanela.write(0); // Inicia cortinas fechadas
// Inicialização de Conectividade
WiFi.begin(ssid, password);
wifiReconnect();
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}
void loop() {
// Garante a conectividade sem bloquear o loop principal
if (WiFi.status() != WL_CONNECTED) {
wifiReconnect();
}
if (WiFi.status() == WL_CONNECTED && !client.connected()) {
mqttReconnect();
}
if (client.connected()) {
client.loop();
}
// Loop temporizado não bloqueante para leitura e telemetria
unsigned long now = millis();
if (now - lastMsgTime > telemetryInterval) {
lastMsgTime = now;
// 1. Atualiza dados de leitura
readSensors();
// 2. Executa malha de controle automático se ativo
if (modeAuto) {
automaticControl();
}
// 3. Monitoramento crítico de choque térmico (Sempre Ativo)
// Em galpões, temperaturas > 32.5 °C ou ITU >= 80 indicam perigo extremo.
if (temperature > 32.5 || itu >= 80.0) {
alarmActive = true;
Serial.println("!!! ALERTA CRÍTICO: ESTRESSE TÉRMICO EXTREMO !!!");
} else {
alarmActive = false;
}
// 4. Envia estados atualizados aos pinos físicos
updateOutputs();
// 5. Publica os resultados formatados
if (client.connected()) {
publishTelemetry();
}
}
}
// --- Funções Auxiliares ---
// Gerencia reconexão sem travar o processamento local
void wifiReconnect() {
if (WiFi.status() == WL_CONNECTED) {
return;
}
Serial.print("Conectando ao WiFi: ");
Serial.println(ssid);
int retries = 0;
// Tenta conectar; permite até 10 iterações curtas para não prender o loop
while (WiFi.status() != WL_CONNECTED && retries < 10) {
delay(200);
Serial.print(".");
retries++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi Conectado!");
Serial.print("IP obtido: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi ainda não disponível. Tentando novamente no próximo ciclo.");
}
}
// Conecta ao broker MQTT de forma assíncrona/não bloqueante
void mqttReconnect() {
static unsigned long lastMqttRetry = 0;
unsigned long now = millis();
if (client.connected()) {
return;
}
// Limita tentativas a cada 5 segundos
if (now - lastMqttRetry > 5000 || lastMqttRetry == 0) {
lastMqttRetry = now;
Serial.print("Tentando conexão MQTT...");
String clientId = "ESP32AviarioClient-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("Conectado com sucesso!");
client.subscribe(topic_control);
} else {
Serial.print("Falhou (rc=");
Serial.print(client.state());
Serial.println("). Nova tentativa em 5 segundos.");
}
}
}
// Leitura e condicionamento das variáveis ambientais
void readSensors() {
float newT = dht.readTemperature();
float newH = dht.readHumidity();
if (!isnan(newT) && !isnan(newH)) {
temperature = newT;
humidity = newH;
itu = calculateITU(temperature, humidity);
} else {
Serial.println("Falha na leitura do sensor DHT22!");
}
// Conversão do LDR analógico para Lux (Divisor pull-down interno do Wokwi)
int ldrRaw = analogRead(LDRPIN);
float voltage = (ldrRaw / 4095.0) * 3.3;
if (voltage >= 0.02 && voltage < 3.28) {
// R_LDR = R_fixed * Vout / (Vcc - Vout)
float R_LDR = 10000.0 * voltage / (3.3 - voltage);
// Lux = (RL10 * 1000 * 10^gamma / R_LDR) ^ (1 / gamma)
// Para o sensor GL5528 do Wokwi (RL10 = 50 kOhm, gamma = 0.7):
float lux = pow(250593.6 / R_LDR, 1.0 / 0.7);
luxValue = constrain(lux, 0.0, 100000.0);
} else if (voltage < 0.02) {
luxValue = 100000.0; // Luz máxima
} else {
luxValue = 0.0; // Escuridão total
}
}
// Cálculo do Ponto de Orvalho e ITU (Aproximação de Magnus-Tetens)
float calculateITU(float temp, float humi) {
if (humi <= 0.001) humi = 0.001; // Evita log(0)
float alpha = ((17.27 * temp) / (237.7 + temp)) + log(humi / 100.0);
float t_dp = (237.7 * alpha) / (17.27 - alpha);
return temp + 0.36 * t_dp + 41.2;
}
// Regras de Controle Climático Automático
void automaticControl() {
// Controle de Temperatura e Cortinas (ITU)
if (temperature > 28.0 || itu >= 75.0) {
stateFan = true;
angleServo = 90; // Abre janelas totalmente
} else if (temperature < 24.0 && itu < 70.0) {
stateFan = false;
angleServo = 0; // Fecha janelas
} else if (itu >= 70.0 && itu < 75.0) {
// Alerta Técnico: cortinas abertas para fluxo de ar passivo
stateFan = false;
angleServo = 90;
}
// Controle de Umidade (Nebulizador)
if (humidity < 50.0) {
stateHumid = true;
} else if (humidity > 65.0) {
stateHumid = false;
}
}
// Receptor de comandos manuais MQTT
void manualControl(const String& message) {
if (message.startsWith("mode:")) {
String val = message.substring(5);
modeAuto = (val == "auto");
Serial.print("Modo de operação alterado para: ");
Serial.println(modeAuto ? "AUTOMATICO" : "MANUAL");
}
// Executa comandos de atuador apenas se estiver em modo MANUAL
if (!modeAuto) {
if (message.startsWith("fan:")) {
String val = message.substring(4);
stateFan = (val == "on");
Serial.println(stateFan ? "Manual: Ventilador LIGADO" : "Manual: Ventilador DESLIGADO");
}
else if (message.startsWith("humid:")) {
String val = message.substring(6);
stateHumid = (val == "on");
Serial.println(stateHumid ? "Manual: Nebulizador LIGADO" : "Manual: Nebulizador DESLIGADO");
}
else if (message.startsWith("servo:")) {
int targetAngle = message.substring(6).toInt();
angleServo = constrain(targetAngle, 0, 90);
Serial.print("Manual: Servo movido para: ");
Serial.println(angleServo);
}
// Atualiza saídas imediatamente ao receber comando
updateOutputs();
}
}
// Envia os estados lógicos para as portas físicas e servo motor
void updateOutputs() {
digitalWrite(PIN_FAN, stateFan ? HIGH : LOW);
digitalWrite(PIN_HUMID, stateHumid ? HIGH : LOW);
digitalWrite(PIN_ALARM, alarmActive ? HIGH : LOW);
servoJanela.write(angleServo);
}
// Serialização da telemetria com ArduinoJson e envio via MQTT
void publishTelemetry() {
StaticJsonDocument<256> doc;
doc["temperature"] = round(temperature * 10.0) / 10.0;
doc["humidity"] = round(humidity * 10.0) / 10.0;
doc["lux"] = (int)luxValue;
doc["itu"] = round(itu * 10.0) / 10.0;
doc["fan"] = stateFan;
doc["humidifier"] = stateHumid;
doc["servo"] = angleServo;
doc["alarm"] = alarmActive;
doc["mode"] = modeAuto ? "auto" : "manual";
String payload;
serializeJson(doc, payload);
Serial.print("Publicando dados de telemetria: ");
Serial.println(payload);
client.publish(topic_telemetry, payload.c_str());
}
// Callback do PubSubClient para processar novos pacotes MQTT
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Mensagem recebida no tópico [");
Serial.print(topic);
Serial.print("]: ");
String message = "";
for (unsigned int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
manualControl(message);
}