Repository navigation
v3.12.0 — 2026-09-01
Everything since v3.8, summarised. The headline is Restore on Boot: keep a calibration backup on the gateway and have it replayed to every module on every power cycle, verified. Along the way it exposed — and this release fixes — the reason browser firmware uploads never completed on this board, a module registry that forgot quiet modules, and a read-back quirk that had been quietly corrupting backups.
Upgrading: flash
SplitFlapGateway-3.12.0.binfrom the dashboard's Open Firmware Updater — the browser upload works again as of this release (see Fixed) — or withpio run -e esp32s3_ota -t upload. If you use Restore on Boot, create a fresh backup after upgrading and upload that: backups made by earlier firmware can carry a truncated flap set for modules with big maps (3.12 skips those entries' flap sets rather than writing them, so an old file is safe, just less complete).
Added
- Restore on Boot (Settings tab) — upload a backup file (the one Backup Calibration produces), tick Restore calibration on every boot, set the post-boot delay (default 10 s), Save. On every boot the gateway writes each module by serial — offset, steps, flap set, then every map entry as its own short frame, paced — re-provisions it if the backup's ID differs, reads every module back and compares field by field (one repair round for anything that differs or does not answer), then homes the wall. A 15-module wall restores and verifies in about 33 s. Run Restore Now does it without a reboot; Cancel Restore stops after the current step.
- The bus is locked for the whole run. Every REST endpoint that would touch the RS-485 bus answers
503 {"error":"restore in progress"}, MQTT commands are dropped, and the gateway's own background traffic pauses. Read-only endpoints, settings, status, the maintenance switch and OTA keep working; the dashboard shows a red border and a progress banner. It always finishes — every wait is bounded and a silent module is counted, not waited for. - REST:
GET /api/restore,PUT/DELETE /api/restore/backup(validated before it is accepted, atomic, max 256 KB),POST /api/restore/run,POST /api/restore/cancel;restoreOnBoot/restoreDelayon/api/configandPOST /api/config/settings; arestoreobject onGET /api/status. - The module registry is permanent. A module is never removed for being quiet. One not heard from in 24 hours is re-queried (a few per minute) so its record stays current; only Identify All, De-provision or a corrupt record removes an entry. The module list and
GET /api/capabilitiesare complete from the first second after boot. pio run -e esp32s3_ota -t upload— a preconfigured espota environment inplatformio.ini.
Changed
- A restore never sends the flap map inside one frame. A module writes each entry to EEPROM as it parses it and cannot keep up with a long frame at line speed: on v31 firmware a 434-byte frame left 20 of 42 entries and the module was deaf for ~6 s. The boot restore,
POST /api/flap/restorebysnand the Settings tab's Restore from File all now send themXW(offset, steps, flap set — which clears the map), wait for that erase, then one pacedm<id>w<i>:<p>per entry — the calibration wizard's own frames, scheduled on the RS-485 task so the web server never blocks.restorebysnreturns at once with{entries, perEntry, id, settleMs}; waitsettleMsbefore re-provisioning or reading the module back. - An inconsistent flap-set tail is ignored. A v31
Areply that loses its tail in transit can parse as64:plus a few characters. That used to land in the registry, in capabilities and in backups — and restoring such a backup wrote the truncated set into the module. The parser now treats a tail whose character count does not match its flap count as "not reported", and no restore path writes or verifies one. Access-Control-Allow-Methodsnow also listsDELETE.
Fixed
- Browser OTA no longer dies near the end of a full-size image. The upload callback set the CORS header on every received chunk, and
WebServer::sendHeader()keeps each call as a heap-allocated node until the response goes out — ~1000 chunks ate ~80 KB of a ~100 KB heap, the connection reset at 1.2–1.3 MB with min-free heap under 1 KB, and nothing was flashed. (The browser's instant "100 %" is the file leaving the browser, not arriving.) The header is now set once, on the response; a 1.48 MB image uploads in ~10 s with 80 KB of heap to spare. The/otapage now waits for the gateway to come back and names the version it booted, and says plainly when nothing was flashed.
Board: Waveshare ESP32-S3-RS485-CAN (16 MB flash).
SplitFlapGateway-3.12.0.bin— the application image. Upload it from the dashboard (Settings ▸ Open Firmware Updater), push it withespota, or flash it over USB at0x10000together withboot_app0.binat0xE000(keeps your settings).boot_app0.bin— the boot selector, for a USB flash of the app image only; it makes the bootloader start the slot you just wrote.SplitFlapGateway-3.12.0.factory.bin— the complete image (bootloader + partition table + boot selector + app) for a blank board, flashed over USB at0x0. Erases WiFi/MQTT settings.
If the web update fails, the README's recovery without developer tools section walks through the browser-based USB flasher and the one-script Wi-Fi push.