Project files updated to Version 10 –05/05/2026.
Video of finished Sump-Monitor project; currently in demo mode. Ultrasonic mode is enabled in variableInput.h; Change Ultrasonic mode to true; prior to compiling.
Github, MIT Licensed project files: Project Files.
Project is only supported by one ESP32 board; the ESP32-S3 with the ESP-1-N16R8 due to memory constraints! Project is a Collaboration with Claude and Copilot. All sharing responsibilities for project objectives.
Claude’s summary of project:
v9.6.1 source code:
Project Summary — Sump Monitor v9.6.1 (04/27/2026)
Hardware Platform
The project targets a single board only: the ESP32-S3 with ESP-1-N16R8 module, chosen specifically for its memory capacity. The comment header notes “Memory size required!!!” — the LittleFS filesystem, async web server, SinricPro, FTP server, and multiple HTML page templates all demand the extra flash and RAM that smaller ESP32 variants can’t provide.
Core Function
An internet-connected sump pit monitor that measures water depth via an ultrasonic transducer, serves a live web dashboard, and fires SMS, email, and Alexa, custom Claxon sound file and Alexa, voice alerts when water levels become dangerous.
Sensing & Condition Logic
The readSensor() function captures distance-to-water-surface (currently using a random() simulator pending real sensor installation). updateCondition() maps that distance to one of four states, with thresholds defined in variableInput.h:
| Condition | Distance | Flags |
|---|---|---|
PUMPOFF |
≥ 38.0″ | Pit dry, pump idle |
ALLCLEAR |
3.0″ – 37.9″ | Normal operation |
HIGHWATER |
0.5″ – 2.9″ | alertFlag = true |
FLOODING |
≤ 0.5″ | alertFlag + floodFlag |
Alert System
Three escalating alert paths via EMailSender:
sendAlert()— high water warning, SMS + emailsendFlood()— flooding emergency, SMS + two email addressessendAllClear()— water returned to normal
A daily heartbeat email fires at 23:57 to confirm the device is alive. All alert events are logged to /ALERT.TXT in LittleFS.
Scheduling Architecture
- Every loop pass –Keeps current; watchdog fed, WiFiManager, ElegantOTA, SinricPro, and FTP all serviced
- Every 5 minutes —
updateFiveMinuteCycle()runs resetReason(),updateCondition(), pump runtime tracking, distance logging, and Alexa/Grafana reporting. A snapshot of state is taken here so the web UI always renders consistent data without contention from/sumproute refreshes (key fix in v9.0.1) - 23:57 daily —
newDay()resets counters, sends heartbeat, writes pump totals to/MASTER.TXT, and on Saturdays purges old log files
Web Dashboard
An AsyncWebServer on port 80 serves four HTML pages (compiled into PROGMEM via index1.h–index4.h) with template processor functions:
/and/sump— live status page with color-coded alert banner (green/yellow/red/grey)/SdBrowse— LittleFS file browser/graph— Grafana-linked graph page/Show— file viewer
Auto-refresh is set to 300 seconds to sync with the 5-minute condition cycle.
Integrations
- SinricPro / Alexa — three virtual contact sensors (HIGHWATER, FLOODING, ALLCLEAR) trigger Alexa announcements
- Grafana / InfluxDB — POSTs JSON events to a local Raspberry Pi (
lucidpi.local:8001) for time-series graphing - ElegantOTA — over-the-air firmware updates via browser
- FTP Server — LittleFS file access over FTP for log retrieval
- WiFiManager — captive portal for initial WiFi setup; static IP configured in
variableInput.h
Reliability Features
- Software watchdog — ISR fires every second; if
watchdogCounterexceeds 45, the device logs the event and restarts - WiFi auto-recovery — disconnects/reconnects logged to
/SYSTEM/WIFI.TXT; after 10 consecutive no-SSID failures, credentials are wiped and the device restarts into the setup portal - Pump runtime tracking —
pumpRunningflag gates start/stop to single-fire; each run and daily totals logged to/PUMP.TXTand/MASTER.TXTin minutes:seconds format
Notable v9.6.1 Fix
The headline fix splits the old monolithic ultrasonic() into readSensor() and updateCondition() (snapshot-based, 5-minute cycle). The /sump route now renders from a frozen snapshot rather than calling condition logic on each page refresh — eliminating the sync/flicker issue where the status display could change between page loads mid-cycle.
You said: ReadSensor only called from inside the 5 minute mark to make UI read-only.
You’re absolutely right on all three points — let me correct my summary:
readSensor() Call Location It’s called exclusively inside updateFiveMinuteCycle(), not every loop pass. This is intentional — keeping processor1() strictly read-only from the snapshot, so the UI never reflects a mid-cycle sensor read. The web page only ever shows the last 5-minute committed state.
Public Internet Access The project uses Tailscale with Funnel running on the Raspberry Pi — not direct port forwarding or a VPN tunnel to the ESP32 itself. The Pi acts as the public-facing gateway, which also happens to be the same Pi hosting the InfluxDB + Grafana stack via Docker Compose. Neat consolidation of infrastructure.
Pump Logic Correction The pump state is simpler than I described:
- Pump ON = any condition where
distanceToTarget < 38.0"— that coversALLCLEAR,HIGHWATER, andFLOODING - Pump OFF =
distanceToTarget ≥ 38.0"→PUMPOFFcondition — pit is dry
So PUMPOFF is the only off state; everything below 38.0″ means the pump is running, regardless of which alert tier it falls into. The pumpRunning flag and start/stop logic in updatePumpRuntime() correctly gate on that single boundary.
Regards,
William