Releases: pwsh/AI-on-the-edge-device
Release list
v17.4.3 — dependency refresh (ESP-IDF 6.0.2, TFLM 1.3.8, mDNS 1.11.3) + send-only-changed publish fix
v17.4.3
🔁 Send-only-changed publishing no longer loses a reading
When an MQTT (or InfluxDB) publish round failed — the log's sporadic
Failed to publish topic '…', skipping all MQTT publishings in this round! — the last-published
marker still advanced, so in "send only changed readings" mode the new value was never re-sent
until the meter moved again (stale value in MQTT / Home Assistant, potentially for hours).
The marker now only advances once the reading topics were actually delivered, so the next round
retries; the "already failed this round" short-circuit reports failure instead of a fake success
(which also lets the Home Assistant discovery batch re-schedule correctly), a failed publish pauses
250 ms before its single retry, and InfluxDB writes treat an HTTP error status (≥ 300) as a failure.
📦 Dependency refresh
- ESP-IDF 6.0.1 → 6.0.2 (bugfix release, no toolchain change)
- esp-tflite-micro v1.3.5 → v1.3.8, esp32-camera v2.1.6 → v2.1.7 (SCCB read fix),
mDNS 1.11.1 → 1.11.3 (fixes an unbounded recursion in name decompression + several fuzzer-found
parsing bugs), stb → current master (stb_image 2.30), espressif/mqtt 1.0.0 → 1.1.0,
joltwallet/littlefs 1.22.1 → 1.22.3 - Web UI: jQuery 3.6.0 → 3.7.1, Plotly (basic) 2.18.2 → 2.35.3, FileSaver.js 2.0.4 → 2.0.5
Includes everything from v17.4.2.
See the Changelog for details.
📦 Artifacts
| File | Use |
|---|---|
*__update__*.zip |
OTA update via the web UI (existing install) |
*__manual-flash__*.zip |
Full flash via USB (new install) |
*__sd-card__*.zip |
SD-card content (web UI, models, config) |
🤖 Generated with Claude Code
v17.4.2 — rate rounded to the sequence precision + 1 decimal
v17.4.2
📏 Rounded rate values
The reported rate is now rounded to one decimal place more than the sequence's precision
(e.g. 0.010 on a 2-decimal meter) instead of a raw 16-digit double
(0.009523809532679263) — everywhere it appears: data log CSV, MQTT, InfluxDB, REST and the
overview. Sequences with zero/negative precision fall back to integer formatting.
Includes everything from v17.4.1.
See the Changelog for details.
📦 Artifacts
| File | Use |
|---|---|
*__update__*.zip |
OTA update via the web UI (existing install) |
*__manual-flash__*.zip |
Full flash via USB (new install) |
*__sd-card__*.zip |
SD-card content (web UI, models, config) |
🤖 Generated with Claude Code
v17.4.1 — change-rate threshold: visible holds, uncheck disables, tooltip restored
v17.4.1
🛠 Change Rate Threshold — three fixes in one
- Held readings are visible: when the jitter band absorbs a reading that actually differed from
the held value, the status now reportsno error - held by change-rate threshold (read X)instead
of a bare "no error" — so a held value is distinguishable from a genuinely unchanged meter (shown
amber in the Data Viewer; MQTT / Home Assistant / webhook / OpenMetrics still treat it as OK). - Unchecking the parameter now actually disables it: the firmware had a hidden built-in default
of2, so removing the parameter (what the config page's checkbox does) silently kept the ±2 band
active. The built-in default is now0(off); the shipped configuration still enables it
explicitly with2, so fresh installs are unchanged. - Its tooltip is back: an "Example" heading in the parameter's documentation — once rendered into
the tooltip — made the config page misclassify the whole row as a section heading: the row lost
its name-hover tooltip and Post-Processing gained a bogus collapsible "Example" section. Section
detection now ignores headings inside tooltip text. The tooltip also now explains the checkbox
semantics (unchecked = disabled,0= off, 1-9 = jitter band).
Includes everything from v17.4.0.
See the Changelog for details.
📦 Artifacts
| File | Use |
|---|---|
*__update__*.zip |
OTA update via the web UI (existing install) |
*__manual-flash__*.zip |
Full flash via USB (new install) |
*__sd-card__*.zip |
SD-card content (web UI, models, config) |
🤖 Generated with Claude Code
v17.4.0 — web password switch, self-describing data CSVs, visible override statuses
v17.4.0
🔒 Web password as a proper on/off setting
The whole web interface + REST API can now be protected with HTTP basic auth, configured from the
Wi-Fi settings page — no more hand-editing wlan.ini on the SD card:
- A "Web password protection" switch (
http_authinwlan.ini) plus web-username / web-password
fields. Changes apply immediately — no reboot; the settings endpoint is itself auth-gated, so
the protection cannot be disabled without the credentials. - Legacy files keep working: without the switch, credentials-present = enabled (as before).
- The Wi-Fi settings form is now pre-filled with the stored settings (passwords are never sent
back), so a partial edit no longer blanks out everything else — previously, saving Wi-Fi settings
silently wiped hand-edited web credentials. All values written towlan.iniare sanitized. - Reminder: basic auth over plain HTTP sends credentials unencrypted — treat it as protection
against casual access on your LAN.
📊 Self-describing data CSVs + visible override statuses
- Column headers everywhere: each new daily data file on the SD card starts with a header line,
the Data Viewer renders a real table (status colour-coded: OK muted, accepted-with-override
amber, rejected red), and the data export adds the header to files from older firmware so every
CSV in the zip has exactly one. The graph page is unaffected. - Override events show in the status instead of a bare "no error":
no error - confidence vote override (previous X replaced after N confirming reads)no error - rate too high - accepted on confident reads (rate R)(+ physical-max variant)
Theno errorprefix is kept, so MQTT / Home Assistant / webhook / OpenMetrics continue to treat
these accepted readings as OK.
See the Changelog for details.
📦 Artifacts
| File | Use |
|---|---|
*__update__*.zip |
OTA update via the web UI (existing install) |
*__manual-flash__*.zip |
Full flash via USB (new install) |
*__sd-card__*.zip |
SD-card content (web UI, models, config) |
🤖 Generated with Claude Code
v17.3.0 — per-sensor camera ranges + full settings on all setup pages
v17.3.0
📷 Camera controls that match your sensor
Every tuning range now adapts to the detected camera, on every page — fixing OV3660/OV5640
setups that were silently pinned to OV2640 limits:
- The live-preview path (camera-setup stream + reference editor) clamped brightness/contrast/
saturation to ±2 on every sensor, the sliders were hardcoded to ±2, and AGC gain / manual
exposure were capped at 30 / 1200 everywhere. A single per-sensor limit table now drives all
clamp sites (firmware and UI). - On the OV3660/OV5640 this unlocks: brightness/contrast ±3, saturation ±4, AE level
±5, manual gain 0..64, manual exposure 0..1968 (driver-limited to frame timing) and
denoise 0..8. An OV2640 keeps its familiar ±2 / 0..30 / 0..1200.
🎛 Full settings parity on the setup pages
The reference editor and the live camera-setup page now expose every camera setting, not
just a subset: image quality, gain ceiling, auto/manual gain, manual exposure, AEC DSP, denoise,
auto white balance + AWB gain, white-balance mode, gamma correction, black/white-pixel correction,
downsize (DCW), camera clock (XCLK), night mode and the colour-bar test pattern.
- The live preview applies all of them per frame; an XCLK change re-initialises the camera only
when the value actually changes. - Settings that are a no-op on the connected sensor are hidden (e.g. denoise on the OV2640,
auto-sharpness on the OV3660/OV5640).
📝 Docs
Tooltips for saturation, sharpness, manual gain, manual exposure and gain ceiling now state the
per-sensor ranges (several were missing the OV3660/OV5640 entirely), and the camera-capabilities
reference page no longer claims the UI pins everything to ±2.
Includes everything from v17.2.2 (ROI auto-tune crash fix).
See the Changelog for details.
📦 Artifacts
| File | Use |
|---|---|
*__update__*.zip |
OTA update via the web UI (existing install) |
*__manual-flash__*.zip |
Full flash via USB (new install) |
*__sd-card__*.zip |
SD-card content (web UI, models, config) |
🤖 Generated with Claude Code
v17.2.2 — fix: ROI Auto-tune crashed the device (PSRAM leak)
v17.2.2
🛠 Fixes a device crash in the on-device ROI Auto-tune
If you are on v17.2.0 or v17.2.1, update — using the digit-ROI Auto-tune could crash and
reboot the device.
Every candidate box the firmware-side auto-tune evaluated leaked its pixel buffer (~2 MB per
run): the in-memory cut handed its buffer to an image object whose destructor treated a zero
memsize as "never allocated" and skipped the free — ownership was transferred, but not the size.
After one or two runs PSRAM was exhausted, the next cut's allocation failed, and the unchecked
copy loop wrote through the NULL pointer → panic in the web-server task. The classic symptom: the
first auto-tune after a boot works, the next one crashes.
Fixed by transferring buffer ownership including its size (so every cut is freed) and adding
the same safety guards the file-writing cut already had (unloaded source, degenerate box, or a
failed allocation now skip the candidate instead of crashing).
Verified on-device: six consecutive auto-tune runs with free PSRAM stable to the byte.
See the Changelog for details.
📦 Artifacts
| File | Use |
|---|---|
*__update__*.zip |
OTA update via the web UI (existing install) |
*__manual-flash__*.zip |
Full flash via USB (new install) |
*__sd-card__*.zip |
SD-card content (web UI, models, config) |
🤖 Generated with Claude Code
v17.1.2 — opt-in rate ceiling (no more false 'exceeds physical max')
AI-on-the-edge-device v17.1.2 — patch release.
Fixes a false "Rate exceeds physical max" rejection: selecting a Meter Type (Utility = water/gas/electricity) without configuring a pipe diameter (or electrical service rating) previously fell back to built-in residential defaults (a 1″ water pipe, m³ scaling) and derived a ~0.9 units/min ceiling — which rejected ordinary readings until the interval stretched enough to drop under it. The physical rate-rejection ceiling is now strictly opt-in: it only applies when a pipe diameter / service-amps (or a user MaxRate) is explicitly set. The residential defaults are kept for prediction only.
Firmware change (postproc); binaries re-stamped to v17.1.2.
Which file do I download?
| Board | OTA update (web UI) | Full USB flash |
|---|---|---|
| ESP32-CAM | …__esp32cam__update__…zip |
…__esp32cam__manual-flash__…zip |
| ESP32-S3 8 MB | …__esp32s3-8mb__update__…zip |
…__esp32s3-8mb__manual-flash__…zip |
| ESP32-S3 16 MB | …__esp32s3-16mb__update__…zip |
…__esp32s3-16mb__manual-flash__…zip |
- update zip — OTA via the web UI. manual-flash zip — full USB flash (
flash.shincluded).…__sd-card__…zip— SD-card web UI + config.
v17.1.1 — auto-tune PSRAM fragmentation fix
AI-on-the-edge-device v17.1.1 — patch release.
Fixes a PSRAM-fragmentation bug in the digit-ROI Auto-tune introduced in v17.1.0: the search now runs a short sequential 1‑D pass (~17 candidate boxes) instead of a 2‑D grid (~74), with a pre-flight memory guard, so it no longer exhausts/fragments PSRAM (Failed to allocate … STBI / reference.jpg corrupted). Firmware is functionally unchanged from v17.1.0 — this is a web-UI fix; the binaries are re-stamped to v17.1.1.
Which file do I download?
| Board | OTA update (web UI) | Full USB flash |
|---|---|---|
| ESP32-CAM | …__esp32cam__update__…zip |
…__esp32cam__manual-flash__…zip |
| ESP32-S3 8 MB | …__esp32s3-8mb__update__…zip |
…__esp32s3-8mb__manual-flash__…zip |
| ESP32-S3 16 MB | …__esp32s3-16mb__update__…zip |
…__esp32s3-16mb__manual-flash__…zip |
- update zip — upload through the web UI (OTA). manual-flash zip — full USB flash (bundles
flash.sh).…__sd-card__…zip— SD-card web UI + config.
See the Changelog for full v17.1.0 + v17.1.1 details.
v17.1.0 — per-LED control · ROI auto-tune · CPU frequency · accuracy
AI-on-the-edge-device v17.1.0 — ESP32-CAM and ESP32-S3 (8 MB / 16 MB).
Which file do I download?
| Board | OTA update (web UI) | Full USB flash |
|---|---|---|
| ESP32-CAM | …__esp32cam__update__…zip |
…__esp32cam__manual-flash__…zip |
| ESP32-S3 8 MB | …__esp32s3-8mb__update__…zip |
…__esp32s3-8mb__manual-flash__…zip |
| ESP32-S3 16 MB | …__esp32s3-16mb__update__…zip |
…__esp32s3-16mb__manual-flash__…zip |
- update zip — upload through the web UI (OTA), no cables.
- manual-flash zip — full flash over USB with esptool (bundles a ready-to-run
flash.sh). …__sd-card__…zip— web UI + config payload for a manual ESP32-CAM SD-card install.
[17.1.0] - 2026-06-26
General
- External LED brightness + 5V current budgeting: the external WS281x (NeoPixel) strip now has an
explicit output % control (LEDBrightness, 0–100). The firmware estimates the strip's peak 5V draw
(≈ 60 mA per LED at full white, scaled by colour and brightness) and automatically dims it to stay
under the board's safe budget (500 mA) so a long/bright strip can't brown out the board. A new
5V power-injection toggle (LEDPowerInjection, off by default) lets you raise that cap to your own
injected supply's rating (LEDMaxCurrent, mA) when the strip is powered separately. The config page
shows a live "estimated peak current vs budget" readout that warns (red) when the strip will be dimmed. - The internal flash LED's existing 0–100 % intensity is now labelled "internal flash LED" to
distinguish it from the new external-strip control. Defaults preserve previous behaviour
(LEDBrightness=100, injection off). - Perceptual (gamma) brightness: the external-LED brightness % now follows a gamma curve so the
number tracks perceived brightness (20 % looks dim, not ~45 % as raw PWM did). Applied to
the flash, stage colours and the onboard RGB. Brightness can be changed live (/ledbrightness). - External LED on any GPIO via a new
LEDPindata-pin field, with a board-specific default
(ESP32-S3 → 21, ESP32-CAM → 12) and anExternalLEDmaster enable. The legacy
IOxx = external-flash-ws281xmechanism is retired from the UI (auto-migrated toLEDPinon load). - Camera/SD-pin guard: the firmware refuses to drive an LED/flash on a camera or SD pin (driving one
broke image capture and crash-looped the device), logging a clear error instead. - ESP32-S3 onboard RGB (GPIO48): a board-aware
OnboardLEDenable/disable toggle; the GPIO4
flash-LED block is hidden on the S3 (GPIO4 is a camera pin there). The GPIO config section is now
labelled "LED Configuration" with each LED enabled on its own. - Per-LED on/off control of the external strip: a new toggle grid (rendered as a line, grid or
circle,LEDMask/LEDLayout/LEDLayoutCols) lets you turn individual WS281x pixels on or off, on
both the config page and the camera-setup page. Each toggle drives the hardware in realtime via a
new/ledstateendpoint (no reboot), so capture-flash, status colours and the always-on mode all
honour the mask. The 5V power budget now counts only ENABLED LEDs (not the strip length), and the
config page's "estimated peak current" readout updates live as you toggle. - Always-on external LED (
LEDAlwaysOn, off by default): drives the strip continuously at
LEDColor, overriding the per-stage status colours, keeping it lit through capture, and — because the
scene is already lit — skipping the pre-capture flash/settle delay. - Alignment-grid overlay on the camera-setup / live-stream page: an optional SVG grid (off / 3×3 /
4×4 / 8×6, plus a centre cross) over the live image to help line up the camera and ROIs. - External LED strip no longer goes dark after a
doInit(e.g. when saving ROI edits): the
persistent WS281x driver is now torn down and rebuilt on the reconfigured GPIO, instead of holding a
dead RMT binding that silently stopped lighting the strip. - Configurable CPU frequency (
CPUFrequency, 80 / 160 / 240 MHz): trade speed for power on any
board. A boot bug is fixed so the configured value now actually applies — the ESP32-S3 boots at
240 MHz, so a configured 160 was previously ignored there. On the ESP32-S3 a new runtime
DynamicFrequencyScalingoption (off by default) down-clocks to 80 MHz when idle and back up under
load; it is force-disabled on the ESP32-CAM, where the camera clock is APB-tied and scaling would
corrupt captures.
Recognition & accuracy
- Confidence-gated FastRead cache: a digit read is only cached for reuse when its confidence clears
the history floor, so a single low-confidence misread can no longer be cached and replayed for the
rest of the FastRead interval — it is simply re-inferred next round. - Temporal voting for low-confidence in-range digits: when a digit reads 0–9 but below the
confidence floor and the recent history has a stable majority, the reported value is corrected to that
majority; a genuinely rolling digit has mixed history, so a real change is never masked. DigitConfidenceThreshold(new,[Digits], default 0 = off, wired into the config page): a
class-based-digit confidence floor — a read below it is marked unknown ("N") and resolved from
confident history + carry physics rather than committing a shaky value (the class-model analogue of
CNNGoodThreshold).- High-sensitivity FastRead by default: the change-detection default (
FastReadThreshold) is now
5 (High) instead of 8 — a wrong "changed" decision only costs one extra inference, so erring
sensitive avoids skipping a real digit change. - Faster recognition: a purpose-built bilinear ROI→model-input downscale replaces the generic stb
resizer on the hot path, and the tflite model is now loaded lazily — a fully-cached FastRead round
does zero model load and skips the per-round model SD read. - Faster alignment: the template-match search drops the per-pixel
pow()for an integerd*dand
adds branch-and-bound (abandon a candidate once it can't beat the current best). The match result is
identical; on the ESP32-CAM this cut the alignment step (≈85 % of every round) and brought a ~22 s
round down to roughly 5 s. - Rate-limit: an unset pipe diameter no longer caps the rate at 0 flow: with no pipe diameter /
service amps configured, the physical-maximum ceiling is treated as unknown instead of being
computed from a zero, so valid readings are no longer rejected as "exceeds physical max". - Pipe-diameter override is now opt-in: selecting a Meter Type still suggests a Maximum Rate but no
longer force-enables (and saves blank) the raw pipe-diameter / service-amps override — an untouched
override stays commented out so the firmware uses its own default. - Confident reads can override a rate-limit rejection: when the last 3 reads of a sequence all meet
DigitConfidenceThreshold, a large jump is accepted (and logged) instead of being clamped back, for
both the physical-max ceiling and the userMaxRateValuecheck. Inert when the threshold is off, and
it never fires for analog-only sequences or in the first rounds after boot.
Connectivity & security
- Captive portal in setup/AP mode: a small DNS responder + DHCP DNS option make phones/laptops pop
the "Sign in to network" page straight onto the Wi-Fi setup form when connected to theAI-on-the-Edge
access point. STA operation is unaffected.
Web interface
- Consistent config controls: all enable/disable dropdowns read
enabled/disabled(no raw
true/false), and tooltips/labels match the controls and the currentLEDPin. - Fixed Meter Type: the Thermometer °F and K options were both mapped to Celsius
(temperature_c) — they now correctly selecttemperature_f/temperature_k. - Safer Save: a setting whose stored value is no longer a valid option is no longer nagged on every
load; instead Save warns once and comments it out (keeping the old value) so it can't break the
config. A stale-cache "unknown parameter" is a quiet console note, not a red alert. - Download a folder as a ZIP: the web file server can now download a whole directory (including
its sub-folders) as a single ZIP — a "Download folder as ZIP" button in the listing header, and a
per-folder download icon on each sub-folder row (GET /fileserver/<dir>/?zip=1). The archive is built
on-device with the existing miniz writer and streamed as<foldername>.zip;wlan.iniis always
excluded. - Reworked ROI editor: the digit and analog editors swap the name dropdown for a row of clickable
numbered chips and auto-name ROIs (<sequence><position>), so you no longer manage ROI names.
You can now pick the digit/analog model right on the editor screen (a configured model missing
from the SD card is preserved as a(missing)option), edit the Decimal Shift inline with a live
multiplier preview, and the layout-lock preferences (lock aspect ratio / synchronize / keep
equidistance / spacing) are remembered across visits. - Apply ROI / sequence edits without a reboot: saving the digit or analog editor now re-inits the
processing flow via/doinit(~1–2 s, round-safe under the flow lock) instead of requiring a full
reboot, falling back to a reboot prompt if the live re-init fails. - Auto-tune a digit ROI: a new Auto-tune ROI button pauses processing, captures and aligns a
single fresh image, drops the layout-lock constraints, and searches nearby positions then sizes
(reusing the per-ROI examine test against that one static capture) for the box the CNN reads with the
highest confidence — ignoring blankN/no-digit reads (which the model can report at ~100 %)
so the box can't drift off the digit. It applies the winning...
v17.0.0
Production release of the 17.0.0 line — the ESP-IDF 6.0 rebuild, ESP32-S3 support, and the full v17 feature set, now considered stable.
Highlights
- Platform: rebuilt on ESP-IDF 6.0.1; new ESP32-S3 (8 MB / 16 MB) support alongside ESP32-CAM and WROVER. S3 boards run with or without an SD card (web UI, models and config can live in flash).
- Recognition & accuracy: scheduling (fixed interval or daily time slots), FastRead incremental analysis (5–10 s updates), meter-type + physics-bounded predictive reading, confidence display + confidence-vote stuck-high recovery, and leak detection (water/gas) exposed to MQTT/HA/InfluxDB/REST.
- Modern web UI: dark mode, responsive layout, on-demand Examine ROI + Pull camera image tools, config backup/restore.
- Data Publishing: a per-platform matrix to choose exactly which parameters reach MQTT / InfluxDB / Home Assistant, plus an "only send changed readings" mode to cut traffic.
- Reliability: camera PWDN auto-recovery, OTA rollback + reliable full zip-OTA, SD-card crash dumps, Wi-Fi resilience (AP fallback, atomic
wlan.ini, 3×-reset factory reset). - Connectivity: verified MQTTS/HTTPS out of the box via a built-in CA bundle.
- Post-boot stabilisation delay reduced 10 s → 3 s for a faster first reading.
See the full Changelog.
Install — pick your board
| Board | In-app OTA update | Fresh USB flash |
|---|---|---|
| ESP32-CAM (4 MB, SD) | …__esp32cam__update__…zip |
…__esp32cam__manual-flash__…zip |
| ESP32-S3 8 MB | …__esp32s3-8mb__update__…zip |
…__esp32s3-8mb__manual-flash__…zip |
| ESP32-S3 16 MB | …__esp32s3-16mb__update__…zip |
…__esp32s3-16mb__manual-flash__…zip |
update zips deploy over an existing install from the device's OTA page (firmware + web UI + models). manual-flash zips contain bootloader + partition table + app for a clean USB flash (flash.sh included). The sd-card zip is the web UI + config for a manual ESP32-CAM SD-card setup.