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.



  • Generic Inductor
    A generic fixed inductor suitable for rapid circuit topology development. The footprint automatically adapts based on the selected package, supporting standard SMD sizes (e.g., 0402, 0603, 0805) as well as well-known inductor packages such as SDR1806, PA4320, SRN6028, and SRR1260. Standard inductor values: 1.0 nH, 10 nH, 100 nH, 1.0 µH, 10 µH, 100 µH, 1.0 mH 1.2 nH, 12 nH, 120 nH, 1.2 µH, 12 µH, 120 µH, 1.2 mH 1.5 nH, 15 nH, 150 nH, 1.5 µH, 15 µH, 150 µH, 1.5 mH 1.8 nH, 18 nH, 180 nH, 1.8 µH, 18 µH, 180 µH, 1.8 mH 2.2 nH, 22 nH, 220 nH, 2.2 µH, 22 µH, 220 µH, 2.2 mH 2.7 nH, 27 nH, 270 nH, 2.7 µH, 27 µH, 270 µH, 2.7 mH 3.3 nH, 33 nH, 330 nH, 3.3 µH, 33 µH, 330 µH, 3.3 mH 3.9 nH, 39 nH, 390 nH, 3.9 µH, 39 µH, 390 µH, 3.9 mH 4.7 nH, 47 nH, 470 nH, 4.7 µH, 47 µH, 470 µH, 4.7 mH 5.6 nH, 56 nH, 560 nH, 5.6 µH, 56 µH, 560 µH, 5.6 mH 6.8 nH, 68 nH, 680 nH, 6.8 µH, 68 µH, 680 µH, 6.8 mH 8.2 nH, 82 nH, 820 nH, 8.2 µH, 82 µH, 820 µH, 8.2 mH #generics #CommonPartsLibrary
  • Generic Capacitor
    A generic fixed capacitor ideal for rapid circuit topology development. You can choose between polarized and non-polarized types, its symbol and the footprint will automatically adapt based on your selection. Supported options include standard SMD sizes for ceramic capacitors (e.g., 0402, 0603, 0805), SMD sizes for aluminum electrolytic capacitors, and through-hole footprints for polarized capacitors. Save precious design time by seamlessly add more information to this part (value, footprint, etc.) as it becomes available. Standard capacitor values: 1.0pF, 10pF, 100pF, 1000pF, 0.01uF, 0.1uF, 1.0uF, 10uF, 100uF, 1000uF, 10000uF 1.1pF, 11pF, 110pF, 1100pF 1.2pF, 12pF, 120pF, 1200pF 1.3pF, 13pF, 130pF, 1300pF 1.5pF, 15pF, 150pF, 1500pF, 0.015uF, 0.15uF, 1.5uF, 15uF, 150uF, 1500uF 1.6pF, 16pF, 160pF, 1600pF 1.8pF, 18pF, 180pF, 1800pF 2.0pF, 20pF, 200pF, 2000pF 2.2pF, 22pF, 220pF, 2200pF, 0.022uF, 0.22uF, 2.2uF, 22uF, 220uF, 2200uF 2.4pF, 24pF, 240pF, 2400pF 2.7pF, 27pF, 270pF, 2700pF 3.0pF, 30pF, 300pF, 3000pF 3.3pF, 33pF, 330pF, 3300pF, 0.033uF, 0.33uF, 3.3uF, 33uF, 330uF, 3300uF 3.6pF, 36pF, 360pF, 3600pF 3.9pF, 39pF, 390pF, 3900pF 4.3pF, 43pF, 430pF, 4300pF 4.7pF, 47pF, 470pF, 4700pF, 0.047uF, 0.47uF, 4.7uF, 47uF, 470uF, 4700uF 5.1pF, 51pF, 510pF, 5100pF 5.6pF, 56pF, 560pF, 5600pF 6.2pF, 62pF, 620pF, 6200pF 6.8pF, 68pF, 680pF, 6800pF, 0.068uF, 0.68uF, 6.8uF, 68uF, 680uF, 6800uF 7.5pF, 75pF, 750pF, 7500pF 8.2pF, 82pF, 820pF, 8200pF 9.1pF, 91pF, 910pF, 9100pF #generics #CommonPartsLibrary
  • Generic Resistor
    A generic fixed resistor ideal for rapid circuit topology development. Its footprint automatically adapts based on the selected package case code—supporting 0402, 0603, 0805, 1203, and many other standard SMD packages, as well as axial horizontal and vertical configurations. Save precious design time by seamlessly add more information to this part (value, footprint, etc.) as it becomes available. Standard resistor values: 1.0 ohm, 10 ohm, 100 ohm, 1.0k ohm, 10k ohm, 100k ohm, 1.0M ohm 1.1 ohm, 11 ohm, 110 ohm, 1.1k ohm, 11k ohm, 110k ohm, 1.1M ohm 1.2 ohm, 12 ohm, 120 ohm, 1.2k ohm, 12k ohm, 120k ohm, 1.2M ohm 1.3 ohm, 13 ohm, 130 ohm, 1.3k ohm, 13k ohm, 130k ohm, 1.3M ohm 1.5 ohm, 15 ohm, 150 ohm, 1.5k ohm, 15k ohm, 150k ohm, 1.5M ohm 1.6 ohm, 16 ohm, 160 ohm, 1.6k ohm, 16k ohm, 160k ohm, 1.6M ohm 1.8 ohm, 18 ohm, 180 ohm, 1.8K ohm, 18k ohm, 180k ohm, 1.8M ohm 2.0 ohm, 20 ohm, 200 ohm, 2.0k ohm, 20k ohm, 200k ohm, 2.0M ohm 2.2 ohm, 22 ohm, 220 ohm, 2.2k ohm, 22k ohm, 220k ohm, 2.2M ohm 2.4 ohm, 24 ohm, 240 ohm, 2.4k ohm, 24k ohm, 240k ohm, 2.4M ohm 2.7 ohm, 27 ohm, 270 ohm, 2.7k ohm, 27k ohm, 270k ohm, 2.7M ohm 3.0 ohm, 30 ohm, 300 ohm, 3.0K ohm, 30K ohm, 300K ohm, 3.0M ohm 3.3 ohm, 33 ohm, 330 ohm, 3.3k ohm, 33k ohm, 330k ohm, 3.3M ohm 3.6 ohm, 36 ohm, 360 ohm, 3.6k ohm, 36k ohm, 360k ohm, 3.6M ohm 3.9 ohm, 39 ohm, 390 ohm, 3.9k ohm, 39k ohm, 390k ohm, 3.9M ohm 4.3 ohm, 43 ohm, 430 ohm, 4.3k ohm, 43K ohm, 430K ohm, 4.3M ohm 4.7 ohm, 47 ohm, 470 ohm, 4.7k ohm, 47k ohm, 470k ohm, 4.7M ohm 5.1 ohm, 51 ohm, 510 ohm, 5.1k ohm, 51k ohm, 510k ohm, 5.1M ohm 5.6 ohm, 56 ohm, 560 ohm, 5.6k ohm, 56k ohm, 560k ohm, 5.6M ohm 6.2 ohm, 62 ohm, 620 ohm, 6.2k ohm, 62K ohm, 620K ohm, 6.2M ohm 6.8 ohm, 68 ohm, 680 ohm, 6.8k ohm, 68k ohm, 680k ohm, 6.8M ohm 7.5 ohm, 75 ohm, 750 ohm, 7.5k ohm, 75k ohm, 750k ohm, 7.5M ohm 8.2 ohm, 82 ohm, 820 ohm, 8.2k ohm, 82k ohm, 820k ohm, 8.2M ohm 9.1 ohm, 91 ohm, 910 ohm, 9.1k ohm, 91k ohm, 910k ohm, 9.1M ohm #generics #CommonPartsLibrary
  • Ground
    A common return path for electric current. Commonly known as ground.
  • Terminal
    Terminal
    An electrical connector acting as reusable interface to a conductor and creating a point where external circuits can be connected.
  • Net Portal
    Wirelessly connects nets on schematic. Used to organize schematics and separate functional blocks. To wirelessly connect net portals, give them same designator. #portal
  • RMCF0805JT47K0
    General Purpose Thick Film Standard Power and High-Power Chip Resistor 47 kOhms ±5% 0.125W, 1/8W Chip Resistor 0805 (2012 Metric) Automotive AEC-Q200 Thick Film Features: - RMCF – standard power ratings - RMCP – high power ratings - Nickel barrier terminations standard - Power derating from 100% at 70ºC to zero at +155ºC - RoHS compliant, REACH compliant, and halogen free - AEC-Q200 compliant
  • 875105359001
    10 µF 16 V Aluminum - Polymer Capacitors Radial, Can - SMD 30mOhm 2000 Hrs @ 105°C #commonpartslibrary #capacitor #aluminumpolymer #radialcan
  • CTL1206FYW1T
    Yellow 595nm LED Indication - Discrete 1.7V 1206 (3216 Metric)
  • 1070TR
    Battery Holder (Open) Coin, 20.0mm 1 Cell SMD (SMT) Tab bate or batt #forLedBlink

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