// ================== Plataforma ==================
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#define ISR_ATTR ICACHE_RAM_ATTR
#define USING_ESP8266 1
#else
#include <WiFi.h>
#define ISR_ATTR IRAM_ATTR
#define USING_ESP32 1
#endif
#include <PubSubClient.h>
#include <time.h>
// ================== CONFIG WIFI/MQTT ==================
#define WIFI_SSID "Wokwi-GUEST"//para o wokwi
#define WIFI_PASS ""
#define MQTT_HOST "broker.emqx.io"
#define MQTT_PORT 1883
#define MQTT_TOPIC "AQF"
#define MQTT_QOS 1
// ================== MAPEAMENTO DE PINOS ==================
#if defined(USING_ESP32)
// ---- ESP32 DevKit-v4 ----
#define PIN_IN1 14 // FLUXOSTATO FRIO (ATIVO = LOW)
#define PIN_IN2 27 // FLUXOSTATO QUENTE (ATIVO = LOW)
#define PIN_JMP1 16 // J1: +5 s
#define PIN_JMP2 17 // J2: +10 s
#define PIN_JMP3 18 // J3: +20 s
#define PIN_OUT1 25 // SFRIO (ativo LOW)
#define PIN_OUT2 26 // SQUENTE (ativo LOW)
#define LED_WIFI 2 // LED Wi-Fi conectado
#define LED_MQTT 4 // LED MQTT conectado
#define LED_PUB 5 // LED “publicação” (pisca 4 s)
#define LED_PISCA 23
#else
// ---- ESP8266 NodeMCU ----
#define PIN_IN1 D5 // GPIO14; ATIVO=LOW
#define PIN_IN2 D6 // GPIO12; ATIVO=LOW
#define PIN_JMP1 D0 // GPIO16; +5 s
#define PIN_JMP2 D3 // GPIO0; +10 s (*strap*)
#define PIN_JMP3 D4 // GPIO2; +20 s (*strap*)
#define PIN_OUT1 D1 // GPIO5 - SFRIO (ativo LOW)
#define PIN_OUT2 D2 // GPIO4 - SQUENTE (ativo LOW)
#define LED_WIFI D0
#define LED_MQTT D7
#define LED_PUB D8
#endif
// ================== ESTADO GLOBAL ==================
WiFiClient g_net;
PubSubClient g_mqtt(g_net);
volatile bool g_in1Active = false; // estado lógico (LOW=ativo)
volatile bool g_in2Active = false;
volatile bool g_irqIn1 = false; // flags “houve mudança”
volatile bool g_irqIn2 = false;
uint32_t g_lastDeb1 = 0, g_lastDeb2 = 0;
const uint32_t DEBOUNCE_MS = 40;
bool g_outputsOn = false;
uint32_t g_holdUntil = 0;
uint32_t g_pubBlinkUntil = 0;
unsigned long g_countDia = 0;
int g_diaYday = -1;
// ================== HELPERS ==================
// Ajuste: saídas ativas em LOW
inline void setOutput(int pin, bool on) {
digitalWrite(pin, on ? LOW : HIGH);
}
inline bool inputActiveRaw(int pin) {
// pull-up interno: LOW = ativo/aterrado
return digitalRead(pin) == LOW;
}
uint32_t calcDelayMs() {
uint32_t s = 0;
if (digitalRead(PIN_JMP1) == LOW) s += 5;
if (digitalRead(PIN_JMP2) == LOW) s += 10;
if (digitalRead(PIN_JMP3) == LOW) s += 20;
if (s == 0) s = 10; // padrão
return s * 1000UL;
}
void logSerialAcionamento(time_t t) {
struct tm tmLocal;
#if defined(ESP8266)
tmLocal = *localtime(&t);
#else
localtime_r(&t, &tmLocal);
#endif
char buf[16];
strftime(buf, sizeof(buf), "%H:%M:%S", &tmLocal);
Serial.printf("[%s] acionamento #%lu\n", buf, g_countDia);
}
void publishJSON(time_t t) {
struct tm tmLocal;
#if defined(ESP8266)
tmLocal = *localtime(&t);
#else
localtime_r(&t, &tmLocal);
#endif
char buf[16];
strftime(buf, sizeof(buf), "%H:%M:%S", &tmLocal);
String payload;
payload.reserve(64);
payload += "{\"timestamp\":\"";
payload += buf; // exemplo: "14:22:05"
payload += "\",\"count\":";
payload += g_countDia;
payload += "}";
g_mqtt.publish(MQTT_TOPIC, payload.c_str());
g_pubBlinkUntil = millis() + 4000UL;
}
// ================== ISRs (mínimas, IRAM-safe) ==================
// NÃO usar millis()/digitalRead() aqui.
void ISR_ATTR isrIn1() { g_irqIn1 = true; }
void ISR_ATTR isrIn2() { g_irqIn2 = true; }
// ================== CONEXÃO ==================
void wifiConnect() {
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(200);
Serial.print('.');
}
Serial.println("\nWi-Fi conectado");
}
void ensureMqtt() {
while (!g_mqtt.connected()) {
if (g_mqtt.connect("ESP-AQF")) {
Serial.println("MQTT conectado");
} else {
delay(1000);
}
}
}
// ================== Tarefas (ESP32/FreeRTOS) ==================
#if defined(USING_ESP32)
void logicTask(void*){
// Estados anteriores para detectar bordas e debouncing
bool lastRaw1 = inputActiveRaw(PIN_IN1);
bool lastRaw2 = inputActiveRaw(PIN_IN2);
g_in1Active = lastRaw1;
g_in2Active = lastRaw2;
g_lastDeb1 = g_lastDeb2 = millis();
for(;;){
const uint32_t now = millis();
// Leitura “rápida” quando houver IRQ, mas com debounce no contexto normal
bool raw1 = lastRaw1, raw2 = lastRaw2;
if (g_irqIn1 || (now - g_lastDeb1 >= DEBOUNCE_MS)) {
raw1 = inputActiveRaw(PIN_IN1);
if (raw1 != lastRaw1) { g_lastDeb1 = now; lastRaw1 = raw1; }
g_irqIn1 = false;
}
if (g_irqIn2 || (now - g_lastDeb2 >= DEBOUNCE_MS)) {
raw2 = inputActiveRaw(PIN_IN2);
if (raw2 != lastRaw2) { g_lastDeb2 = now; lastRaw2 = raw2; }
g_irqIn2 = false;
}
// Estados válidos (pós-debounce)
g_in1Active = lastRaw1;
g_in2Active = lastRaw2;
const bool any = g_in1Active || g_in2Active;
if (any) {
if (!g_outputsOn) {
g_countDia++;
time_t t; time(&t);
logSerialAcionamento(t);
if (g_mqtt.connected()) publishJSON(t);
Serial.println(g_in1Active && g_in2Active ? "[TRIGGER] IN1+IN2"
: g_in1Active ? "[TRIGGER] IN1" : "[TRIGGER] IN2");
}
g_outputsOn = true;
setOutput(PIN_OUT1, true);
setOutput(PIN_OUT2, true);
g_holdUntil = now + calcDelayMs(); // renova hold enquanto ativo
} else {
if ((int32_t)(now - g_holdUntil) >= 0) {
if (g_outputsOn) Serial.println("[SAIDA] desligando (hold expirado)");
g_outputsOn = false;
setOutput(PIN_OUT1, false);
setOutput(PIN_OUT2, false);
}
}
vTaskDelay(pdMS_TO_TICKS(10)); // 100 Hz
}
}
void netTask(void*){
for(;;){
digitalWrite(LED_WIFI, (WiFi.status()==WL_CONNECTED) ? HIGH : LOW);
digitalWrite(LED_MQTT, g_mqtt.connected() ? HIGH : LOW);
digitalWrite(LED_PUB, (millis() < g_pubBlinkUntil) ? HIGH : LOW);
if (!g_mqtt.connected()) ensureMqtt();
g_mqtt.loop();
vTaskDelay(pdMS_TO_TICKS(50));
}
}
void dailyTask(void*){
for(;;){
time_t now; time(&now);
struct tm tmUtc;
#if defined(ESP8266)
tmUtc = *gmtime(&now);
#else
gmtime_r(&now, &tmUtc);
#endif
if (g_diaYday < 0) g_diaYday = tmUtc.tm_yday;
if (tmUtc.tm_yday != g_diaYday) {
g_diaYday = tmUtc.tm_yday;
g_countDia = 0;
Serial.println("[reset diário] contador zerado");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void blinkTask(void*){
pinMode(LED_PISCA, OUTPUT);
for(;;){
digitalWrite(LED_PISCA, HIGH);
vTaskDelay(pdMS_TO_TICKS(500));
digitalWrite(LED_PISCA, LOW);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
#endif
// ================== setup/loop ==================
void setup() {
Serial.begin(9600);
Serial.println("Iniciando");
// Pinos
pinMode(PIN_IN1, INPUT_PULLUP);
pinMode(PIN_IN2, INPUT_PULLUP);
pinMode(PIN_JMP1, INPUT_PULLUP);
pinMode(PIN_JMP2, INPUT_PULLUP);
pinMode(PIN_JMP3, INPUT_PULLUP);
pinMode(PIN_OUT1, OUTPUT); setOutput(PIN_OUT1, false);
pinMode(PIN_OUT2, OUTPUT); setOutput(PIN_OUT2, false);
pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, LOW);
pinMode(LED_MQTT, OUTPUT); digitalWrite(LED_MQTT, LOW);
pinMode(LED_PUB, OUTPUT); digitalWrite(LED_PUB, LOW);
pinMode(LED_PISCA, OUTPUT); digitalWrite(LED_PISCA, LOW);
// Interrupções: apenas sinalizam “houve mudança”
attachInterrupt(digitalPinToInterrupt(PIN_IN1), isrIn1, CHANGE);
attachInterrupt(digitalPinToInterrupt(PIN_IN2), isrIn2, CHANGE);
// Wi-Fi + MQTT + NTP
wifiConnect();
g_mqtt.setServer(MQTT_HOST, MQTT_PORT);
configTime(-3 * 3600, 0, "pool.ntp.org", "time.nist.gov");// -3 horas em segundos = UTC-3 (São Paulo)
// Estado inicial
g_in1Active = inputActiveRaw(PIN_IN1);
g_in2Active = inputActiveRaw(PIN_IN2);
g_holdUntil = millis();
#if defined(USING_ESP32)
// Tarefas (lógica=core1, redes=core0, diário=core1)
xTaskCreatePinnedToCore(logicTask, "LogicTask", 4096, nullptr, 2, nullptr, 1);
xTaskCreatePinnedToCore(netTask, "NetTask", 4096, nullptr, 1, nullptr, 0);
xTaskCreatePinnedToCore(dailyTask, "DailyTask", 2048, nullptr, 1, nullptr, 1);
xTaskCreatePinnedToCore(blinkTask, "BlinkTask", 1024, nullptr, 1, nullptr, 1); // <<< nova tarefa
#endif
}
void loop() {
#if defined(USING_ESP32)
vTaskDelay(portMAX_DELAY); // FreeRTOS cuida
#else
// ESP8266: laço cooperativo com as mesmas correções de debounce
const uint32_t now = millis();
static bool lastRaw1 = inputActiveRaw(PIN_IN1);
static bool lastRaw2 = inputActiveRaw(PIN_IN2);
static uint32_t lastDeb1 = now, lastDeb2 = now;
// Debounce
bool raw1 = inputActiveRaw(PIN_IN1);
if (raw1 != lastRaw1 && (now - lastDeb1) >= DEBOUNCE_MS) { lastDeb1 = now; lastRaw1 = raw1; }
bool raw2 = inputActiveRaw(PIN_IN2);
if (raw2 != lastRaw2 && (now - lastDeb2) >= DEBOUNCE_MS) { lastDeb2 = now; lastRaw2 = raw2; }
g_in1Active = lastRaw1; g_in2Active = lastRaw2;
const bool any = g_in1Active || g_in2Active;
if (any) {
if (!g_outputsOn) {
g_countDia++;
time_t t; time(&t);
logSerialAcionamento(t);
if (g_mqtt.connected()) publishJSON(t);
}
g_outputsOn = true;
setOutput(PIN_OUT1, true);
setOutput(PIN_OUT2, true);
g_holdUntil = now + calcDelayMs();
} else {
if ((int32_t)(now - g_holdUntil) >= 0) {
g_outputsOn = false;
setOutput(PIN_OUT1, false);
setOutput(PIN_OUT2, false);
}
}
if (!g_mqtt.connected()) ensureMqtt();
g_mqtt.loop();
digitalWrite(LED_WIFI, (WiFi.status()==WL_CONNECTED)?HIGH:LOW);
digitalWrite(LED_MQTT, g_mqtt.connected()?HIGH:LOW);
digitalWrite(LED_PUB, (millis()<g_pubBlinkUntil)?HIGH:LOW);
// Reset diário simples
static unsigned long tLast = 0;
if (millis() - tLast >= 1000) {
tLast = millis();
time_t t; time(&t);
struct tm* pt = gmtime(&t);
if (g_diaYday < 0) g_diaYday = pt->tm_yday;
if (pt->tm_yday != g_diaYday) {
g_diaYday = pt->tm_yday;
g_countDia = 0;
Serial.println("[reset diário] contador zerado");
}
}
delay(10);
#endif
}
Eng. Renato Maziero
Bomba_Fria
Bomba_Quente
5S
10S
20S
O USO DOS INVERSORES SÃO PARA SIMULAR MÓDULOS REAIS QUE
SÃO ACIONADOS COM NÍVEL LOW, ENQUANTO ESSE MODELO DE
RELAY NO WOKWI É ACIONADO EM NÍVEL HIGH.