#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
/* ======================== CONFIG WIFI/MQTT ======================== */
#define WIFI_SSID "MAZIEROOIFIBRA"//"Wokwi-GUEST" // ajuste se necessario
#define WIFI_PASS "Camila@12"//""
#define MQTT_HOST "broker.emqx.io"
#define MQTT_PORT 1883
#define MQTT_TOPIC "AQF" // mantenha seu topico
/* ======================== CONSTANTES DE MEDICAO ======================== */
// YF-B5: F[Hz] = 6.6 * Q[L/min]
static const float F_PER_LPM = 6.6f;
static const uint32_t WINDOW_MS = 250; // janela de calculo (4x/seg)
static const uint32_t MQTT_PERIOD_MS = 1000; // publica 1x/seg (caso haja pulsos)
static const uint32_t LCD_PERIOD_MS = 500; // atualiza LCD a cada 500 ms
// Janelas de status no LCD (a exibicao depende do pino 26 estar LOW)
static const uint32_t STATUS_OK_MS = 30000; // manter WiFi/MQTT OK por 30s
static const uint32_t STATUS_DNS_MS = 8000; // manter DNS FAIL por 8s
// Largura fixa do pulso LOW nas saidas (monoestavel NAO-rearmavel)
static const uint32_t LED_HOLD_MS = 1000; // 1 segundo
/* ======================== MAPEAMENTO DE PINOS ======================== */
// I2C padrao ESP32: SDA=21, SCL=22
static const uint8_t PIN_FLOW1 = 14; // Sensor 1 (entrada de pulsos)
static const uint8_t PIN_FLOW2 = 27; // Sensor 2 (entrada de pulsos)
static const uint8_t PIN_LED_FLOW1 = 4; // LED de pulso 1 (ativo-LOW)
static const uint8_t PIN_LED_FLOW2 = 5; // LED de pulso 2 (ativo-LOW)
// Pino que habilita exibicao de status no LCD quando LOW
static const uint8_t PIN_T_WIFI_MQTT = 26;
#ifndef LED_BUILTIN
#define LED_BUILTIN 2 // muitas DevKit usam GPIO2 como LED onboard
#endif
/* ======================== LCD I2C ======================== */
LiquidCrystal_I2C lcd(0x27, 16, 2);
/* ======================== VARS COMPARTILHADAS ======================== */
volatile uint32_t g_pulses1 = 0;
volatile uint32_t g_pulses2 = 0;
volatile uint32_t g_lastPulseMs1 = 0;
volatile uint32_t g_lastPulseMs2 = 0;
// janela ate quando manter a saida em LOW (monoestavel de 1s)
volatile uint32_t g_ledFlowLowUntilMs1 = 0;
volatile uint32_t g_ledFlowLowUntilMs2 = 0;
struct Meas {
uint32_t prevCount = 0;
float freqHz = 0.0f;
float qLpm = 0.0f;
};
Meas ch1, ch2;
/* ======= Estados de conexao / status ======= */
WiFiClient espClient;
PubSubClient mqtt(espClient);
char g_lastSSID[33] = {0}; // SSID atual (max 32 chars + NUL)
uint32_t g_statusShowUntil = 0; // ate quando "status" estaria valido
enum StatusKind { STATUS_NONE=0, STATUS_INFO, STATUS_DNS_FAIL };
volatile StatusKind g_statusKind = STATUS_NONE;
// Controle de sobreposicao no LCD via pino 26 (interrupt)
volatile bool g_forceStatusByPin = false;
/* ======================== WIFI/MQTT HELPERS ======================== */
// imprime duas linhas no LCD, limpando
static void lcdPrintLines(const char* l1, const char* l2) {
lcd.setCursor(0,0); lcd.print(" ");
lcd.setCursor(0,0); lcd.print(l1);
lcd.setCursor(0,1); lcd.print(" ");
lcd.setCursor(0,1); lcd.print(l2);
}
void wifiEnsureConnected() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint32_t t0 = millis();
Serial.print("[WiFi] Conectando a SSID: "); Serial.println(WIFI_SSID);
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) {
delay(250);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
String s = WiFi.SSID();
s.toCharArray(g_lastSSID, sizeof(g_lastSSID));
Serial.println("[WiFi] Conectado");
Serial.print ("[WiFi] SSID: "); Serial.println(g_lastSSID);
Serial.print ("[WiFi] IP: "); Serial.println(WiFi.localIP());
Serial.print ("[WiFi] DNS0: "); Serial.println(WiFi.dnsIP(0));
Serial.print ("[WiFi] DNS1: "); Serial.println(WiFi.dnsIP(1));
} else {
Serial.println("[WiFi] Falha ao conectar");
g_lastSSID[0] = 0;
}
}
// tenta resolver o host; em sucesso, retorna true e preenche ipOut
bool resolveBroker(IPAddress& ipOut) {
Serial.print("[DNS] Resolving "); Serial.println(MQTT_HOST);
bool ok = WiFi.hostByName(MQTT_HOST, ipOut);
if (ok) {
Serial.print("[DNS] OK: "); Serial.println(ipOut);
} else {
Serial.println("[DNS] FAIL (hostByName)");
}
return ok;
}
void mqttEnsureConnected() {
if (mqtt.connected()) return;
// garante Wi-Fi
wifiEnsureConnected();
if (WiFi.status() != WL_CONNECTED) return;
// DNS: resolve broker
IPAddress brokerIP;
if (!resolveBroker(brokerIP)) {
// Sinaliza DNS FAIL (a exibicao no LCD so ocorre se pino 26 estiver LOW)
g_statusKind = STATUS_DNS_FAIL;
g_statusShowUntil = millis() + STATUS_DNS_MS;
mqtt.setServer(MQTT_HOST, MQTT_PORT);
return;
}
// Usa IP resolvido
mqtt.setServer(brokerIP, MQTT_PORT);
// Tenta conectar
String clientId = String("ESP32-YFB5-") + String((uint32_t)ESP.getEfuseMac(), HEX);
Serial.print("[MQTT] Conectando em "); Serial.print(brokerIP);
Serial.print(":"); Serial.print(MQTT_PORT);
Serial.print(" cid="); Serial.println(clientId);
uint32_t t0 = millis();
while (!mqtt.connected() && millis() - t0 < 8000) {
mqtt.connect(clientId.c_str());
delay(500);
Serial.print(".");
}
Serial.println();
if (mqtt.connected()) {
Serial.println("[MQTT] Conectado");
Serial.print ("[MQTT] Topico: "); Serial.println(MQTT_TOPIC);
// Sinaliza OK (a exibicao no LCD so ocorre se pino 26 estiver LOW)
g_statusKind = STATUS_INFO;
g_statusShowUntil = millis() + STATUS_OK_MS;
} else {
Serial.println("[MQTT] Falha ao conectar (sera retentado)");
}
}
/* ======================== ISRs ======================== */
// ISR do pino 26 – atualiza flag para sobreposicao no LCD
void IRAM_ATTR onTWiFiMQTT() {
g_forceStatusByPin = (digitalRead(PIN_T_WIFI_MQTT) == LOW);
}
// Pulsos do sensor 1 -> contagem e monoestavel de 1s no LED (nao rearmavel)
void IRAM_ATTR onPulse1() {
g_pulses1++;
uint32_t now = millis();
g_lastPulseMs1 = now;
// so inicia novo LOW se ja nao houver janela em curso
if (now >= g_ledFlowLowUntilMs1) {
g_ledFlowLowUntilMs1 = now + LED_HOLD_MS; // segura 1s
digitalWrite(PIN_LED_FLOW1, LOW); // mantem LOW ate expirar
}
}
// Pulsos do sensor 2 -> contagem e monoestavel de 1s no LED (nao rearmavel)
void IRAM_ATTR onPulse2() {
g_pulses2++;
uint32_t now = millis();
g_lastPulseMs2 = now;
// so inicia novo LOW se ja nao houver janela em curso
if (now >= g_ledFlowLowUntilMs2) {
g_ledFlowLowUntilMs2 = now + LED_HOLD_MS; // segura 1s
digitalWrite(PIN_LED_FLOW2, LOW); // mantem LOW ate expirar
}
}
/* ======================== TASKS ======================== */
void taskBlink(void* pv) {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) {
digitalWrite(LED_BUILTIN, HIGH);
vTaskDelay(pdMS_TO_TICKS(500));
digitalWrite(LED_BUILTIN, LOW);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
static void computeWindow(Meas& ch, volatile uint32_t& counter, uint32_t windowMs) {
noInterrupts();
uint32_t total = counter;
interrupts();
uint32_t delta = total - ch.prevCount;
ch.prevCount = total;
float windowSec = windowMs / 1000.0f;
ch.freqHz = delta / windowSec;
ch.qLpm = ch.freqHz / F_PER_LPM;
}
void taskMeasure(void* pv) {
(void)pv;
TickType_t last = xTaskGetTickCount();
for (;;) {
computeWindow(ch1, g_pulses1, WINDOW_MS);
computeWindow(ch2, g_pulses2, WINDOW_MS);
uint32_t now = millis();
// solta as saidas quando expirar a janela de 1s
if (now >= g_ledFlowLowUntilMs1) digitalWrite(PIN_LED_FLOW1, HIGH);
if (now >= g_ledFlowLowUntilMs2) digitalWrite(PIN_LED_FLOW2, HIGH);
vTaskDelayUntil(&last, pdMS_TO_TICKS(WINDOW_MS));
}
}
// LCD: so mostra status se o pino 26 estiver LOW; senao, mostra F/Q
void taskLCD(void* pv) {
(void)pv;
uint32_t lastRefresh = 0;
for (;;) {
if (millis() - lastRefresh >= LCD_PERIOD_MS) {
lastRefresh = millis();
bool forceStatus = g_forceStatusByPin; // controlado pela ISR do pino 26
if (forceStatus) {
char l1[17], l2[17];
snprintf(l1, sizeof(l1), "WiFi:%s", g_lastSSID[0] ? g_lastSSID : "-");
if (g_statusKind == STATUS_DNS_FAIL) {
snprintf(l2, sizeof(l2), "DNS: FAIL");
} else {
if (mqtt.connected()) {
snprintf(l2, sizeof(l2), "MQTT: OK");
} else {
snprintf(l2, sizeof(l2), "MQTT: recon...");
}
}
lcdPrintLines(l1, l2);
} else {
char line1[17], line2[17];
snprintf(line1, sizeof(line1), "F1:%4.1f Q:%4.1f", ch1.freqHz, ch1.qLpm);
snprintf(line2, sizeof(line2), "F2:%4.1f Q:%4.1f", ch2.freqHz, ch2.qLpm);
lcdPrintLines(line1, line2);
}
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
void taskMQTT(void* pv) {
(void)pv;
uint32_t lastPub = 0;
for (;;) {
wifiEnsureConnected();
mqttEnsureConnected();
mqtt.loop();
// PUBLICAR APENAS SE HOUVER PULSOS (como estava)
if (mqtt.connected() && millis() - lastPub >= MQTT_PERIOD_MS) {
lastPub = millis();
bool hasActivity = (ch1.freqHz > 0.0f) || (ch2.freqHz > 0.0f);
if (hasActivity) {
char payload[160];
snprintf(payload, sizeof(payload),
"{\"ts\":%lu,\"f1\":%.2f,\"q1\":%.2f,\"f2\":%.2f,\"q2\":%.2f}",
(unsigned long)(millis()/1000),
ch1.freqHz, ch1.qLpm,
ch2.freqHz, ch2.qLpm);
mqtt.publish(MQTT_TOPIC, payload, true);
Serial.print("[PUB] "); Serial.println(payload);
}
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
/* ======================== SETUP / LOOP ======================== */
void setup() {
Serial.begin(115200);
delay(100);
// I2C + LCD
Wire.begin(); // SDA=21, SCL=22
lcd.init();
lcd.backlight();
lcd.clear();
lcdPrintLines("YF-B5 Flow Meter", "Iniciando...");
// Sensores e LEDs
pinMode(PIN_FLOW1, INPUT_PULLUP);
pinMode(PIN_FLOW2, INPUT_PULLUP);
pinMode(PIN_LED_FLOW1, OUTPUT); digitalWrite(PIN_LED_FLOW1, HIGH);
pinMode(PIN_LED_FLOW2, OUTPUT); digitalWrite(PIN_LED_FLOW2, HIGH);
// Pino 26 como entrada pull-up + interrupcao (T_WiFi_MQTT)
pinMode(PIN_T_WIFI_MQTT, INPUT_PULLUP);
g_forceStatusByPin = (digitalRead(PIN_T_WIFI_MQTT) == LOW);
attachInterrupt(digitalPinToInterrupt(PIN_T_WIFI_MQTT), onTWiFiMQTT, CHANGE);
// Interrupcoes nas bordas de descida dos fluxos
attachInterrupt(digitalPinToInterrupt(PIN_FLOW1), onPulse1, FALLING);
attachInterrupt(digitalPinToInterrupt(PIN_FLOW2), onPulse2, FALLING);
// WiFi/MQTT base
WiFi.mode(WIFI_STA);
wifiEnsureConnected();
mqtt.setServer(MQTT_HOST, MQTT_PORT); // sera sobrescrito com IP apos DNS ok, se aplicavel
// Tarefas FreeRTOS
xTaskCreatePinnedToCore(taskBlink, "Blink", 2048, nullptr, 1, nullptr, 1);
xTaskCreatePinnedToCore(taskMeasure, "Measure", 3072, nullptr, 2, nullptr, 1);
xTaskCreatePinnedToCore(taskLCD, "LCD", 3072, nullptr, 1, nullptr, 1);
xTaskCreatePinnedToCore(taskMQTT, "MQTT", 4096, nullptr, 2, nullptr, 0);
}
void loop() {
// nao usado – FreeRTOS cuida do agendamento
}
Eng. Renato Maziero
Fluxostato/Frio
Fluxostato/Quente
LCD
WIFI/MQTT
BOMBA_FRIA
BOMBA QUENTE
Os dois inversores no esquema, são apenas para permitir a simulação, pois os módulos de relés físicos
são acionados em LOW, ao passo que os do Wokwi, em HIGH.
Para permitir contabilizar a vazão para publicação, acione diversas vezes os fluxostatos frio e quente,
assim voce publicará a vasão e a frequencia, bem como apresentará a vazão no LCD.
Para um sistema funcionar bem, voce ao instalar no sistema hidráulico, deve medir a vazão e limitar a maior
até igualar a de menor fluxo. Assim seu sistema estará pressurizado corretamente, independente da tubulação
e das alturas manométricas das caixas d'água, fria e quente. Note que está é a etapa de controle para acionar
as bombas, não apresentada nesta parte do projeto.
No Fluxostato YF-B5, a fórmula é: F(Hz)=6,6×Q(L/min)
Portanto:
Se a água está passando a 1 L/min, o sensor gera ~6,6 pulsos por segundo.
Se a água está a 10 L/min, gera ~66 pulsos por segundo.
Em 1 minuto, 1 litro: 6,6×60≈396 pulsos
.......................10 litros 66 pulsos/seg 3.960 pulsos
Vazão (Q)
A fórmula é: 𝑄=𝐴×𝑣
Convertendo para litros/min:
v = 1 m/s 𝑄=3,14×10−4×1=3,14×10−4m3/𝑠≈18,8 L/min
v = 2 m/s 𝑄≈37,7 L/min
v = 3 m/s 𝑄≈56,5 L/min
Resumindo:
Para um tubo de 22 mm:
Vazão recomendada: ~20 a 40 L/min
Vazão máxima “de projeto” (3 m/s): ~55–60 L/min
Para um tubo de 25 mm:
Vazão recomendada: ~30 a 60 L/min
Vazão máxima “de projeto” (3 m/s): ~90 L/min
Para igualar a vazão 𝑄25=𝑄22, você precisa reduzir a velocidade no tubo de 25 mm: Instale uma válvula de esfera/borboleta/balanceamento
no ramo de 25 mm e estrangule até que as leituras dos seus sensores YF-B5 (Q = F/6,6) fiquem iguais.
Você precisa reduzir a velocidade no tubo de 25 mm:
𝑣25=𝑣22⋅𝐴22/𝐴25=𝑣22⋅(22/25)2≈0,774
Na prática isso se consegue aumentando a perda de carga (resistência) do ramo de 25 mm até que a sua vazão caia e iguale a do ramo de 22 mm.
FA
FB
DA
DB