Designing Smart Water Meter PCB

I want to create a PCB BOARD OF A SMART WATER METER aquameter , and this is the .ino sketch :/*
  • ================================================================
  • AquaMetre Village — Sketch ESP32 DevKit V1
  • Micro-compteur d'eau autonome — alimentation LiPo + turbine
  • SANS électrovanne — mesure pure débit + conductivité
  • ================================================================
  • VERSION : 5.4
  • MODIFICATIONS v5.4 vs v5.3 :
  • ─────────────────────────────────────────────────────────────
  • [1] ECRAN : remplacement Adafruit_SSD1306 → SH1106 1.3"
  •  Bibliothèque utilisée : U8g2lib (U8G2_SH1106_128X64_NONAME_F_HW_I2C)
    
  •  → Pilote natif SH1106, rendu pixel-perfect sur écran 1.3"
    
  •  → API U8g2 : u8g2.clearBuffer() / sendBuffer() / setFont() etc.
    
  • [2] PAGE 0 — Affichage consommation repensé :
  •  • GRAND FORMAT  → Volume en Litres   (police 28px, ex : 125.4 L)
    
  •  • PETIT FORMAT  → Index en m³        (police 10px, ex : 0.1254 m3)
    
  •  → Plus lisible d'un coup d'œil pour l'utilisateur final
    
  • [CONSERVÉ] Tous les correctifs et la logique v5.3 :
    • FIX bug comptage à vide (pulses == 0 → return immédiat)
    • Seuils TDS H=180/L=100, lecture 100ms
    • Confirmation 2 lectures EAU / 3 lectures AIR
    • Sauvegarde EEPROM 5min / 10L
  • BIBLIOTHÈQUES REQUISES (Gestionnaire de bibliothèques Arduino) :
  • ─────────────────────────────────────────────────────────────
  • • U8g2 par oliver (chercher "U8g2")
  • • RTClib par Adafruit
  • • ArduinoJson
  • ARCHITECTURE MATÉRIELLE :
  • ─────────────────────────────────────────────────────────────
  • CHAÎNE ÉNERGIE :
  • Turbine hydro 5V (DN15/DN20 inline)
  • └→ TP4056 → LiPo 3.7V/2000–3000mAh → MT3608 boost → 5V ESP32
  • CAPTEURS :
  • ┌─ Débitmètre YF-S201 (1–30 L/min) → GPIO4
  • ├─ Sonde conductivité inox 2 électrodes → GPIO34 (ADC)
  • │ VCC sonde → GPIO5 (transistor NPN anti-électrolyse)
  • ├─ OLED SH1106 128×64 1.3" → I2C GPIO21/22
  • ├─ RTC DS3231 → I2C GPIO21/22
  • └─ Pont diviseur batterie → GPIO35 (ADC)
  • BROCHAGE ESP32 DevKit V1 :
  • GPIO 4 → YF-S201 signal débitmètre
  • GPIO 5 → Transistor NPN VCC sonde TDS
  • GPIO 21 → SDA I2C
  • GPIO 22 → SCL I2C
  • GPIO 26 → LED VERTE (EAU détectée)
  • GPIO 27 → LED ROUGE (AIR détecté / alerte)
  • GPIO 34 → ADC sonde TDS
  • GPIO 35 → ADC batterie LiPo
  • GPIO 0 → Bouton reset période
  • ================================================================ */
// ─────────────────────────────────────────────── // BIBLIOTHÈQUES // ─────────────────────────────────────────────── #include #include #include #include // ← SH1106 (remplace Adafruit_SSD1306) #include #include #include
// ─────────────────────────────────────────────── // !! MODIFIER CES VALEURS AVANT DE PROGRAMMER !! // ─────────────────────────────────────────────── const char* WIFI_SSID = "NomDeVotreWiFi"; const char* WIFI_PASSWORD = "MotDePasseWiFi";
const char* AP_SSID = "AquaMetre-M002"; const char* AP_PASSWORD = "aqua1234"; IPAddress AP_IP (192, 168, 4, 1); IPAddress AP_SUBNET (255, 255, 255, 0);
#define COMPTEUR_ID "M-002" #define COMPTEUR_MAISON "Entrer le nom" #define PRIX_PAR_M3 45.0
// ─────────────────────────────────────────────── // MODE CALIBRATION SONDE TDS // ─────────────────────────────────────────────── #define CALIBRATION_MODE false
// ─────────────────────────────────────────────── // CONFIGURATION ÉCRAN SH1106 1.3" (I2C) // // Le SH1106 est différent du SSD1306 : contrôleur colonne décalé // de 2 pixels. U8g2 gère cela nativement avec le bon driver. // // Adresse I2C par défaut : 0x3C (peut être 0x3D selon cavalier) // Brochage : SDA=GPIO21 / SCL=GPIO22 (I2C matériel ESP32) // ─────────────────────────────────────────────── U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2( U8G2_R0, // rotation 0° /* reset= / U8X8_PIN_NONE, / clock= / 22, // SCL / data= */ 21 // SDA );
// ─────────────────────────────────────────────── // CONFIGURATION RTC DS3231 // ─────────────────────────────────────────────── RTC_DS3231 rtc;
// ─────────────────────────────────────────────── // CONFIGURATION EEPROM // ─────────────────────────────────────────────── #define EEPROM_SIZE 512 #define ADDR_INITIALIZED 0 #define ADDR_TOTAL_M3 4 #define ADDR_PERIODE_M3 8 #define ADDR_AIR_COUNT 12
void eepromWriteFloat (int addr, float val) { EEPROM.put(addr, val); EEPROM.commit(); } float eepromReadFloat (int addr) { float v; EEPROM.get(addr, v); return v; } void eepromWriteUint32 (int addr, uint32_t val) { EEPROM.put(addr, val); EEPROM.commit(); } uint32_t eepromReadUint32 (int addr) { uint32_t v; EEPROM.get(addr, v); return v; }
// ─────────────────────────────────────────────── // CONFIGURATION DÉBITMÈTRE YF-S201 // ─────────────────────────────────────────────── #define FLOW_PIN 4 #define FLOW_INTERVAL_MS 1000
volatile uint32_t pulseCount = 0; float flowRate_Lmin = 0.0; float totalVolume_L = 0.0; float totalVolume_m3 = 0.0; float periodeVolume_m3 = 0.0; unsigned long lastFlowCalc = 0;
// ─────────────────────────────────────────────── // CONFIGURATION SONDE TDS INOX 2 ÉLECTRODES // ─────────────────────────────────────────────── #define CONDUCT_PIN 34 #define CONDUCT_VCC_PIN 5 #define CONDUCT_THRESHOLD_HIGH 180 #define CONDUCT_THRESHOLD_LOW 100 #define CONDUCT_CONFIRM_WATER 2 #define CONDUCT_CONFIRM_AIR 3
bool isWaterDetected = false; uint16_t conductValue = 0; uint16_t conductRaw = 0; uint32_t airFilterCount = 0;
uint8_t confirmWaterCount = 0; uint8_t confirmAirCount = 0;
// ─────────────────────────────────────────────── // CONFIGURATION BATTERIE LiPo // ─────────────────────────────────────────────── #define BAT_PIN 35 #define BAT_FULL_V 4.2f #define BAT_EMPTY_V 3.0f
float batteryVoltage = 0.0; uint8_t batteryPercent = 0;
// ─────────────────────────────────────────────── // BROCHES LED ET BOUTON // ─────────────────────────────────────────────── #define LED_GREEN_PIN 26 #define LED_RED_PIN 27 #define BTN_PIN 0
// ─────────────────────────────────────────────── // SERVEUR WEB HTTP // ─────────────────────────────────────────────── WebServer server(80); bool wifiConnected = false;
// ─────────────────────────────────────────────── // VARIABLES AFFICHAGE OLED // ─────────────────────────────────────────────── uint8_t displayPage = 0; unsigned long lastDisplay = 0; unsigned long lastPage = 0;
// ================================================================ // INTERRUPTION — DÉBITMÈTRE YF-S201 // ================================================================ void IRAM_ATTR flowISR() { pulseCount++; }
// ================================================================ // SONDE TDS — LECTURE OPTIMISÉE // ================================================================ uint16_t readConductivity() { digitalWrite(CONDUCT_VCC_PIN, HIGH); delay(5);
uint16_t samples[8]; uint32_t sum = 0; uint16_t vmin = 4095, vmax = 0;
for (int i = 0; i vmax) vmax = samples[i]; delay(2); }
digitalWrite(CONDUCT_VCC_PIN, LOW);
sum -= vmin; sum -= vmax; conductRaw = (uint16_t)(sum / 6); return conductRaw; }
// ================================================================ // SONDE TDS — DÉTECTION EAU AVEC HYSTÉRÉSIS + CONFIRMATION // ================================================================ bool checkWaterHysteresis(uint16_t adc) {
if (!isWaterDetected) { if (adc >= CONDUCT_THRESHOLD_HIGH) { confirmWaterCount++; confirmAirCount = 0; if (confirmWaterCount >= CONDUCT_CONFIRM_WATER) { isWaterDetected = true; confirmWaterCount = 0; Serial.printf("[TDS] EAU CONFIRMEE : ADC=%u >= seuil H=%u (%u lectures)\n", adc, CONDUCT_THRESHOLD_HIGH, CONDUCT_CONFIRM_WATER); } } else { confirmWaterCount = 0; }
} else { if (adc = CONDUCT_CONFIRM_AIR) { isWaterDetected = false; confirmAirCount = 0; Serial.printf("[TDS] AIR CONFIRME : ADC=%u = CONDUCT_THRESHOLD_HIGH) etat = "EAU"; else if (val %s\n", i + 1, val, tension, CONDUCT_THRESHOLD_HIGH, CONDUCT_THRESHOLD_LOW, etat);
char buf[32];
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.drawStr(0, 10, "MODE CALIBRATION");
sprintf(buf, "ADC: %u", val);
u8g2.drawStr(0, 24, buf);
sprintf(buf, "V: %.3f", tension);
u8g2.drawStr(0, 36, buf);
u8g2.drawStr(0, 50, etat);
u8g2.sendBuffer();
delay(1000);
} Serial.println("[CALIB] Fin. Regler CONDUCT_THRESHOLD_HIGH/LOW puis CALIBRATION_MODE=false\n"); }
// ================================================================ // GESTION CONDUCTIVITÉ — lecture toutes les 100ms // ================================================================ void handleConductivity() { static unsigned long lastCheck = 0; unsigned long now = millis(); if (now - lastCheck 0 ET isWaterDetected → comptage // ================================================================ void calculateFlow() { unsigned long now = millis(); if (now - lastFlowCalc 0.0f && pulses > 0) { flowRate_Lmin = ((float)pulses / elapsed) / 7.5f; } else { flowRate_Lmin = 0.0f; }
// GARDE 1 : débitmètre doit avoir tourné (FIX v5.3) if (pulses == 0) return;
// GARDE 2 : sonde TDS doit confirmer la présence d'eau if (flowRate_Lmin > 0.01f) { if (isWaterDetected) { float vol_L = (flowRate_Lmin / 60.0f) * elapsed; totalVolume_L += vol_L; totalVolume_m3 = totalVolume_L / 1000.0f; periodeVolume_m3 += vol_L / 1000.0f; } else { airFilterCount++; Serial.printf("[FILTRE] Debit %.1f L/min REJETE - TDS=%u = 0.0f && t = 0.0f && p = 0.001f) { periodeVolume_m3 = p; Serial.printf("[EEPROM] Periode restauree : %.4f m3\n", periodeVolume_m3); } else { periodeVolume_m3 = 0.0f; Serial.printf("[EEPROM] Periode residuelle ignoree (%.6f m3 9999 L : police réduite pour tenir dtostrf(totalVolume_L, 7, 0, bufL); // ex: " 12345" u8g2.setFont(u8g2_font_logisoso20_tf); u8g2.drawStr(0, 40, bufL); u8g2.setFont(u8g2_font_6x10_tf); u8g2.drawStr(110, 40, "L"); }
// ── Séparateur ─────────────────────────────────────────────── u8g2.drawHLine(0, 46, 128);
// ── Petit affichage : m³ + débit ───────────────────────────── u8g2.setFont(u8g2_font_6x10_tf);
char bufM3[16], bufQ[16]; dtostrf(totalVolume_m3, 7, 4, bufM3); // " 0.1254" dtostrf(flowRate_Lmin, 4, 1, bufQ); // " 3.2"
char line1[32]; sprintf(line1, "%s m3 %s L/m", bufM3, bufQ); u8g2.drawStr(0, 56, line1);
// ── Dernière ligne : mensuel + état ────────────────────────── char bufP[12]; dtostrf(periodeVolume_m3, 6, 3, bufP); char line2[32]; sprintf(line2, "Mois:%sm3 Bat:%u%%", bufP, batteryPercent); u8g2.drawStr(0, 64, line2);
u8g2.sendBuffer(); }
// ================================================================ // PAGE 1 : SONDE TDS // ================================================================ void oledPage1_Capteurs() { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_6x10_tf);
u8g2.drawStr(0, 9, "--- SONDE TDS v5.4 ---"); u8g2.drawHLine(0, 11, 128);
char buf[32]; sprintf(buf, "ADC brut : %u", conductRaw); u8g2.drawStr(0, 22, buf);
sprintf(buf, "H:%u L:%u W:%u/%u", CONDUCT_THRESHOLD_HIGH, CONDUCT_THRESHOLD_LOW, confirmWaterCount, CONDUCT_CONFIRM_WATER); u8g2.drawStr(0, 32, buf);
// Barre de progression ADC uint8_t barLen = (uint8_t)map(conductRaw, 0, 4095, 0, 120); uint8_t seuilH_x = (uint8_t)map(CONDUCT_THRESHOLD_HIGH, 0, 4095, 0, 120); uint8_t seuilL_x = (uint8_t)map(CONDUCT_THRESHOLD_LOW, 0, 4095, 0, 120); u8g2.drawFrame(0, 35, 120, 6); u8g2.drawBox(0, 35, barLen, 6); // Effacer la zone des marqueurs puis redessiner u8g2.setDrawColor(2); // XOR u8g2.drawVLine(seuilH_x, 33, 10); u8g2.drawVLine(seuilL_x, 35, 6); u8g2.setDrawColor(1); // normal
u8g2.drawStr(0, 52, isWaterDetected ? "Etat : EAU DETECTEE" : "Etat : AIR / SEC");
sprintf(buf, "Rejects: %us", airFilterCount); u8g2.drawStr(0, 63, buf);
u8g2.sendBuffer(); }
// ================================================================ // PAGE 2 : BATTERIE // ================================================================ void oledPage2_Batterie() { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_6x10_tf);
u8g2.drawStr(0, 9, "-- LIPO + TURBINE --"); u8g2.drawHLine(0, 11, 128);
// Tension en grand char bufV[10]; dtostrf(batteryVoltage, 4, 2, bufV); u8g2.setFont(u8g2_font_logisoso20_tf); u8g2.drawStr(0, 38, bufV); u8g2.setFont(u8g2_font_6x10_tf); u8g2.drawStr(70, 38, "V");
char bufPct[8]; sprintf(bufPct, "%u%%", batteryPercent); u8g2.drawStr(85, 38, bufPct);
// Barre batterie uint8_t batBar = (uint8_t)map(batteryPercent, 0, 100, 0, 100); u8g2.drawFrame(0, 42, 104, 8); u8g2.drawBox(2, 44, batBar, 4); u8g2.drawFrame(104, 44, 4, 4); // borne positive
if (batteryPercent >= 98) u8g2.drawStr(0, 56, "Batterie: PLEINE"); else if (batteryPercent > 20) u8g2.drawStr(0, 56, "Batterie: OK"); else u8g2.drawStr(0, 56, "Batterie: FAIBLE !");
u8g2.drawStr(0, 64, "MT3608: 3.7V->5V | TP4056");
u8g2.sendBuffer(); }
// ================================================================ // PAGE 3 : RÉSEAU WiFi // ================================================================ void oledPage3_Reseau() { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_6x10_tf);
u8g2.drawStr(0, 9, "--- RESEAU WiFi ---"); u8g2.drawHLine(0, 11, 128);
char buf[32]; sprintf(buf, "ID: %s", COMPTEUR_ID); u8g2.drawStr(0, 22, buf);
sprintf(buf, "AP: %s", AP_SSID); u8g2.drawStr(0, 32, buf);
String apIP = WiFi.softAPIP().toString(); sprintf(buf, "IP AP: %s", apIP.c_str()); u8g2.drawStr(0, 42, buf);
if (wifiConnected) { String staIP = WiFi.localIP().toString(); sprintf(buf, "STA: %s", staIP.c_str()); } else { sprintf(buf, "STA: non connecte"); } u8g2.drawStr(0, 52, buf);
sprintf(buf, "Facture: %d DA", (int)(periodeVolume_m3 * PRIX_PAR_M3)); u8g2.drawStr(0, 64, buf);
u8g2.sendBuffer(); }
// ================================================================ // ROTATION DES PAGES OLED (4s par page) // ================================================================ void updateDisplay() { unsigned long now = millis(); if (now - lastPage >= 4000) { displayPage = (displayPage + 1) % 4; lastPage = now; } if (now - lastDisplay >= 500) { switch (displayPage) { case 0: oledPage0_Consommation(); break; case 1: oledPage1_Capteurs(); break; case 2: oledPage2_Batterie(); break; case 3: oledPage3_Reseau(); break; } lastDisplay = now; } }
// ================================================================ // SERVEUR HTTP — JSON COMPLET // ================================================================ String buildJSON() { DateTime now = rtc.now(); char ts[20]; sprintf(ts, "%04d-%02d-%02dT%02d:%02d:%02d", now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());
StaticJsonDocument doc; doc["id"] = COMPTEUR_ID; doc["maison"] = COMPTEUR_MAISON; doc["timestamp"] = ts; doc["index_total_m3"] = serialized(String(totalVolume_m3, 4)); doc["index_total_L"] = serialized(String(totalVolume_L, 1)); doc["periode_m3"] = serialized(String(periodeVolume_m3, 4)); doc["debit_lmin"] = serialized(String(flowRate_Lmin, 2)); doc["conductivite_raw"] = conductRaw; doc["seuil_tds_haut"] = CONDUCT_THRESHOLD_HIGH; doc["seuil_tds_bas"] = CONDUCT_THRESHOLD_LOW; doc["confirm_water_needed"] = CONDUCT_CONFIRM_WATER; doc["confirm_air_needed"] = CONDUCT_CONFIRM_AIR; doc["confirm_water_current"] = confirmWaterCount; doc["confirm_air_current"] = confirmAirCount; doc["eau_detectee"] = isWaterDetected; doc["air_rejects_secondes"] = airFilterCount; doc["batterie_pct"] = batteryPercent; doc["batterie_v"] = serialized(String(batteryVoltage, 2)); doc["wifi_rssi"] = wifiConnected ? WiFi.RSSI() : 0; doc["facture_da"] = serialized(String(periodeVolume_m3 * PRIX_PAR_M3, 0)); doc["ip_sta"] = wifiConnected ? WiFi.localIP().toString() : "---"; doc["ip_ap"] = WiFi.softAPIP().toString(); doc["systeme"] = "LiPo+turbine hydro+MT3608"; doc["electrovanne"] = false; doc["calib_mode"] = CALIBRATION_MODE; doc["version"] = "5.4";
String json; serializeJson(doc, json); return json; }
// ================================================================ // SERVEUR HTTP — PAGE WEB PRINCIPALE // ================================================================ void handleRoot() { DateTime now = rtc.now(); char dateStr[20]; sprintf(dateStr, "%02d/%02d/%04d %02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute());
String html = ""; html += ""; html += ""; html += ""; html += ""; html += "";
html += "AquaMetre — " + String(COMPTEUR_ID) + " v5.4"; html += "" + String(COMPTEUR_MAISON) + "  ·  " + String(dateStr); html += "  ·  Autonome : LiPo + Turbine hydro";
if (CALIBRATION_MODE) { html += ""; html += "MODE CALIBRATION ACTIF"; html += ""; html += "ADC sonde : " + String(conductRaw) + ""; html += " — Seuil H : " + String(CONDUCT_THRESHOLD_HIGH) + ""; html += " — Seuil L : " + String(CONDUCT_THRESHOLD_LOW) + ""; html += ""; }
// État eau/air String eauClass = isWaterDetected ? "ok" : "warn"; String eauTxt = isWaterDetected ? "EAU DETECTEE" : "AIR — DEBIT REJETE"; html += ""; html += ""; html += ""; html += "LED VERTE"; html += "|"; html += ""; html += "LED ROUGE"; html += "" + eauTxt + ""; html += ""; html += "Confirmation EAU ("; html += String(CONDUCT_CONFIRM_WATER) + " lectures requises) :"; html += ""; for (int i = 0; i L"; html += "↳ " + String(totalVolume_m3, 4) + " m³"; html += "";
// Grille 3 colonnes html += "";
html += ""; html += "Debit actuel"; html += "" + String(flowRate_Lmin, 1) + "L/min"; html += ""; html += String(isWaterDetected ? "COMPTE" : "REJETE") + ""; html += "";
html += ""; html += "Sonde TDS inox"; html += "" + String(conductRaw) + "ADC"; html += "H:" + String(CONDUCT_THRESHOLD_HIGH); html += " / L:" + String(CONDUCT_THRESHOLD_LOW) + " / 4095"; uint8_t tpct = (uint8_t)map(conductRaw, 0, 4095, 0, 100); uint8_t spctH = (uint8_t)map(CONDUCT_THRESHOLD_HIGH, 0, 4095, 0, 100); uint8_t spctL = (uint8_t)map(CONDUCT_THRESHOLD_LOW, 0, 4095, 0, 100); html += ""; html += ""; html += ""; html += "▮ H=vert ▮ L=rouge"; html += "";
String batColor = batteryPercent > 50 ? "bar-fg-green" : batteryPercent > 20 ? "bar-fg-amber" : "bar-fg"; html += ""; html += "Batterie LiPo"; html += "" + String(batteryPercent) + "%"; html += "" + String(batteryVoltage, 2) + " V — MT3608 → 5V"; html += ""; html += "";
html += ""; // fin grid3
html += "";
html += ""; html += "Ce mois (eau validee)"; html += "" + String(periodeVolume_m3, 3) + "m3"; html += "" + String((int)(periodeVolume_m3 * PRIX_PAR_M3)) + " DA"; html += "";
html += ""; html += "Securite anti-air (secondes rejetees)"; html += "" + String(airFilterCount) + "s"; html += "Secondes de debit sans eau confirmee"; html += "";
html += "";
html += ""; html += "Alimentation autonome"; html += ""; html += "Turbine hydro DN15/DN20 5V"; html += "TP4056 chargeur LiPo"; html += "LiPo 3.7V 2000-3000mAh"; html += "MT3608 boost 3.7->5V"; html += ""; html += "Systeme passif — se recharge pendant le passage de l eau"; html += "";
html += ""; html += "Connexion WiFi"; html += "AP : "; html += "" + String(AP_SSID) + " → http://192.168.4.1"; html += "STA : "; if (wifiConnected) { html += "http://" + WiFi.localIP().toString() + ""; } else { html += "Non connecte"; } html += "";
html += ""; html += "JSON brut"; html += "Reset periode"; html += "Status"; html += "";
html += ""; server.send(200, "text/html; charset=UTF-8", html); }
void handleReleve() { server.sendHeader("Access-Control-Allow-Origin", "*"); server.send(200, "application/json", buildJSON()); Serial.println("[HTTP] Releve envoye a : " + server.client().remoteIP().toString()); }
void handleReset() { float anciennePeriode = periodeVolume_m3; periodeVolume_m3 = 0.0f; eepromWriteFloat(ADDR_PERIODE_M3, 0.0f);
StaticJsonDocument doc; doc["status"] = "reset_ok"; doc["ancienne_periode"] = serialized(String(anciennePeriode, 4)); doc["nouvelle_periode"] = "0.0000";
String json; serializeJson(doc, json); server.send(200, "application/json", json); Serial.println("[HTTP] Reset periode effectue."); }
void handleStatus() { StaticJsonDocument doc; doc["id"] = COMPTEUR_ID; doc["online"] = true; doc["bat_pct"] = batteryPercent; doc["bat_v"] = serialized(String(batteryVoltage, 2)); doc["rssi"] = wifiConnected ? WiFi.RSSI() : 0; doc["ip_sta"] = wifiConnected ? WiFi.localIP().toString() : "---"; doc["ip_ap"] = WiFi.softAPIP().toString(); doc["eau_detectee"] = isWaterDetected; doc["conductivite_raw"] = conductRaw; doc["seuil_haut"] = CONDUCT_THRESHOLD_HIGH; doc["seuil_bas"] = CONDUCT_THRESHOLD_LOW; doc["confirm_water_needed"] = CONDUCT_CONFIRM_WATER; doc["confirm_air_needed"] = CONDUCT_CONFIRM_AIR; doc["confirm_water_current"] = confirmWaterCount; doc["confirm_air_current"] = confirmAirCount; doc["debit_lmin"] = serialized(String(flowRate_Lmin, 2)); doc["air_rejects_secondes"] = airFilterCount; doc["electrovanne"] = false; doc["calib_mode"] = CALIBRATION_MODE; doc["version"] = "5.4";
String json; serializeJson(doc, json); server.send(200, "application/json", json); }
void handleNotFound() { server.send(404, "application/json", "{"error":"route inconnue","routes":["/","/releve","/reset","/status"]}"); }
// ================================================================ // CONNEXION WIFI // ================================================================ void connectWiFi() { Serial.println("\n[WiFi] Demarrage mode AP+STA..."); WiFi.mode(WIFI_AP_STA); WiFi.softAPConfig(AP_IP, AP_IP, AP_SUBNET);
bool apStarted = (strlen(AP_PASSWORD) >= 8) ? WiFi.softAP(AP_SSID, AP_PASSWORD) : WiFi.softAP(AP_SSID);
if (apStarted) { Serial.printf("[WiFi] AP demarre : SSID=%s | IP=%s\n", AP_SSID, WiFi.softAPIP().toString().c_str()); } else { Serial.println("[WiFi] ERREUR : impossible de demarrer l AP !"); }
u8g2.clearBuffer(); u8g2.setFont(u8g2_font_6x10_tf); u8g2.drawStr(0, 10, "AP actif !"); u8g2.drawStr(0, 22, AP_SSID); u8g2.drawStr(0, 34, "Connexion routeur..."); u8g2.sendBuffer();
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); uint8_t tries = 0; while (WiFi.status() != WL_CONNECTED && tries = 2000) { periodeVolume_m3 = 0.0f; eepromWriteFloat(ADDR_PERIODE_M3, 0.0f); Serial.println("[BTN] Reset periode par bouton physique."); for (int i = 0; i = 30000) { readBattery(); lastBat = millis(); }
// 7. Sauvegarde EEPROM périodique (toutes les 5 min) static unsigned long lastSave = 0; if (millis() - lastSave >= 300000) { saveToEEPROM(); lastSave = millis(); }
// 8. Sauvegarde EEPROM par volume (tous les 10 litres) static float lastSavedLiters = 0.0f; if (totalVolume_L - lastSavedLiters >= 10.0f) { saveToEEPROM(); lastSavedLiters = totalVolume_L; }
// 9. Reconnexion WiFi STA (toutes les 30s) static unsigned long lastWifiCheck = 0; if (millis() - lastWifiCheck >= 30000) { if (WiFi.status() != WL_CONNECTED) { wifiConnected = false; WiFi.reconnect(); Serial.println("[WiFi] Tentative de reconnexion STA..."); } else { wifiConnected = true; } lastWifiCheck = millis(); }
// 10. Log série (toutes les 5s) static unsigned long lastLog = 0; if (millis() - lastLog >= 5000) { Serial.printf( "[%s] Debit:%.1fL/min | TDS:%u(H:%u/L:%u) | %s | ConfW:%u/%u ConfA:%u/%u | %.1fL / %.4fm3 | Bat:%.2fV(%u%%) | AirRejects:%us\n", COMPTEUR_ID, flowRate_Lmin, conductRaw, CONDUCT_THRESHOLD_HIGH, CONDUCT_THRESHOLD_LOW, isWaterDetected ? "EAU-OK" : "AIR-REJETE", confirmWaterCount, CONDUCT_CONFIRM_WATER, confirmAirCount, CONDUCT_CONFIRM_AIR, totalVolume_L, totalVolume_m3, batteryVoltage, batteryPercent, airFilterCount ); lastLog = millis(); }
delay(10); }
/*
  • ================================================================
  • RÉSUMÉ DES MODIFICATIONS v5.4
  • ================================================================
  • [1] MIGRATION SSD1306 → SH1106 1.3"
  • ─────────────────────────────────────────────────────────────
  • Bibliothèque : U8g2lib (chercher "U8g2" dans le gestionnaire)
  • Driver : U8G2_SH1106_128X64_NONAME_F_HW_I2C
  • Différence clé : le SH1106 a un offset de colonne interne de
  • 2 pixels — U8g2 gère cela nativement, contrairement à
  • Adafruit_SSD1306 qui produirait un décalage visuel.
  • API U8g2 (remplacements) :
  • display.begin() → u8g2.begin()
  • display.clearDisplay() → u8g2.clearBuffer()
  • display.display() → u8g2.sendBuffer()
  • display.setTextSize(1) → u8g2.setFont(u8g2_font_6x10_tf)
  • display.setTextSize(2) → u8g2.setFont(u8g2_font_logisoso20_tf)
  • display.setCursor(x,y) → u8g2.drawStr(x, y+baseline, str)
  • display.print(val) → sprintf(buf,…) + u8g2.drawStr(…)
  • display.drawFastHLine → u8g2.drawHLine
  • display.fillRect → u8g2.drawBox
  • display.drawRect → u8g2.drawFrame
  • display.drawFastVLine → u8g2.drawVLine
  • Note U8g2 : l'ordonnée Y est la BASELINE du texte, pas le
  • coin supérieur. Décaler de +8 à +10px par rapport à SSD1306.
  • [2] PAGE 0 — Affichage Litres grand / m³ petit
  • ─────────────────────────────────────────────────────────────
  • Police grand format : u8g2_font_logisoso28_tf (28px)
  • → Chiffres nets, idéals pour lecture rapide
  • → Si totalVolume_L ≥ 10000 → bascule sur logisoso20_tf (20px)
  • Police petit format : u8g2_font_6x10_tf (10px)
  • → Affiche l'index en m³ sous le grand chiffre
  • → Exemple : " 125.4 L" (grand) + "0.1254 m3" (petit)
  • [CONSERVÉ INTÉGRAL] Toute la logique v5.3 :
    • FIX bug comptage à vide (pulses == 0 → return)
    • Résidus EEPROM ignorés (< 0.001 m3)
    • Seuils TDS H=180/L=100, lecture 100ms, stab 5ms
    • Confirmation 2 lectures EAU / 3 lectures AIR
    • LEDs GPIO26 (verte=eau) / GPIO27 (rouge=air)
    • Sauvegarde EEPROM 5min / 10L
    • Serveur HTTP : /, /releve, /reset, /status
    • JSON enrichi : ajout "index_total_L" en v5.4
  • ================================================================ */
Je ne peux pas modifier le projet directement pour l’instant car l’exécution est bloquée côté crédits. Aucun composant ni schéma n’a encore été créé dans ce projet : il est toujours vide, avec 0 composant et 0 net.
À partir de ton sketch, le PCB AquaMetre devra intégrer au minimum : ESP32 DevKit V1 ou module ESP32, charge LiPo type TP4056, boost 5 V type MT3608, connecteur batterie LiPo, entrée turbine hydro 5 V, débitmètre YF-S201 sur GPIO4, sonde conductivité 2 électrodes avec commande par GPIO5, OLED SH1106 I2C, RTC DS3231 I2C, pont diviseur batterie vers GPIO35, LEDs GPIO26/GPIO27, bouton reset sur GPIO0, et connecteurs adaptés aux capteurs externes.
Ce qu’il reste à faire quand l’exécution sera disponible :
  1. Créer la spécification du projet AquaMetre.
  2. Choisir les composants réels disponibles dans la bibliothèque.
  3. Calculer le budget de puissance LiPo/boost/ESP32/capteurs.
  4. Dessiner le schéma complet avec protections, découplages, pull-ups I2C, diviseurs ADC et connecteurs.
  5. Vérifier le schéma avec les datasheets et corriger les erreurs.
  6. Passer ensuite au placement PCB, puis au routage après validation.
Find core parts
Save requirements note
Calculate power budget
Draft schematic plan
Check datasheet values
No schematic yet

Talk to Flux to get started.

No files yet

Documents

    Documents are markdown text files which describe project goals, capture details, or even simulation outputs.

    Assets

    No assets added.

    Financial Violet Teleporter

    Financial Violet Teleporter thumbnail
    Welcome to your new project. Imagine what you can build here.

    Properties

    Properties describe core aspects of the project.

    Pricing & Availability

    See prices from popular manufacturers for your project.

    Controls