Skip to content

Releases: pwsh/AI-on-the-edge-device

v17.4.3 — dependency refresh (ESP-IDF 6.0.2, TFLM 1.3.8, mDNS 1.11.3) + send-only-changed publish fix

Choose a tag to compare

@pwsh pwsh released this 19 Aug 03:05

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

Choose a tag to compare

@pwsh pwsh released this 11 Jul 01:02

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

Choose a tag to compare

@pwsh pwsh released this 07 Jul 04:01

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 reports no 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
    of 2, so removing the parameter (what the config page's checkbox does) silently kept the ±2 band
    active. The built-in default is now 0 (off); the shipped configuration still enables it
    explicitly with 2, 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

Choose a tag to compare

@pwsh pwsh released this 06 Jul 16:21

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_auth in wlan.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 to wlan.ini are 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)
      The no error prefix 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

Choose a tag to compare

@pwsh pwsh released this 03 Jul 02:07

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)

Choose a tag to compare

@pwsh pwsh released this 03 Jul 01:05

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')

Choose a tag to compare

@pwsh pwsh released this 26 Jun 13:20

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.sh included). …__sd-card__…zip — SD-card web UI + config.

v17.1.1 — auto-tune PSRAM fragmentation fix

Choose a tag to compare

@pwsh pwsh released this 26 Jun 07:21

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

Choose a tag to compare

@pwsh pwsh released this 26 Jun 06:49

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 LEDPin data-pin field, with a board-specific default
    (ESP32-S3 → 21, ESP32-CAM → 12) and an ExternalLED master enable. The legacy
    IOxx = external-flash-ws281x mechanism is retired from the UI (auto-migrated to LEDPin on 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 OnboardLED enable/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 /ledstate endpoint (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
    DynamicFrequencyScaling option (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 integer d*d and
    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 user MaxRateValue check. 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 the AI-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 current LEDPin.
  • Fixed Meter Type: the Thermometer °F and K options were both mapped to Celsius
    (temperature_c) — they now correctly select temperature_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.ini is 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 blank N/no-digit reads (which the model can report at ~100 %)
    so the box can't drift off the digit. It applies the winning...
Read more

v17.0.0

Choose a tag to compare

@pwsh pwsh released this 07 Jun 03:37

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.