- New flash layout (partitions.csv): factory = recovery app, ota_0/ota_1 =
clock firmware; esp32_usb uploads to ota_0 so it never overwrites it
- src/recovery: WiFi + OTA + web page only. Opens the password protected
'FlipDot-Setup' AP with a captive portal when WiFi fails, lets you pick
another WiFi (stored in NVS, used by both apps), start the clock or stay
- Recovery is started when the clock firmware can't connect to WiFi,
crashes 3 times, after 3 quick power cycles, from a new Home Assistant
button, or by the bootloader when no clock firmware works
- Shared NVS store and boot switching in src/esp32/system
- Clock firmware keeps working on the old flash layout (falls back to
OTA-only safe mode)
- README: recovery app, one-time USB installation; tests: 65 total
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rollback: override verifyRollbackLater() so a new OTA image stays
pending until it ran 60 s with WiFi; any earlier restart rolls back
(the Arduino core marked every image valid at boot before)
- Task watchdog (30 s) on loop(), paused during OTA uploads
- Crash counter in RTC memory: OTA-only window after a crash, 10 min
safe mode (WiFi + OTA only) after 3 crashes in a row
- Restart when WiFi stays down for 10 min
- Home Assistant sensor 'FlipDot Display Firmware' with version/state
- logic/boot_policy with tests (60 tests total)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Structure (src/esp32/):
- logic/: DisplayController, schedule, power timer, LED pattern; pure C++
- display/: FlipdotFrames and bitmaps
- nano_link/: NanoLink (relay, serial, READY, bootloader) and flash bridge
- net/: WiFi/OTA/panic window, Home Assistant
- config.h with all settings; libs.h (globals in a header) removed
Bug fixes:
- Home Assistant switches now report their state back and start as ON
- loop() no longer blocks: WiFi.waitForConnectResult() (up to 60 s
without WiFi) and getLocalTime() (5 s before NTP sync) were called
every iteration
- Power switch off now powers off after 1 s even when a 30 s countdown
is already running
- millis() overflow safe power-off timer; no frame catch-up bursts
- No garbage time string before NTP sync; 'Connecting...' stays instead
- Frames are only sent when the screen changes instead of every second
- Reset reasons are logged once instead of every 100 ms
- Removed dead code (unused fonts, WiFiMulti, rotated bitmap, ...)
Tests: new test_esp32 environment (26 tests); native renamed to test_nano
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Uploader sends 'FLASH <baud>' first; the ESP32 restarts the Nano and
bridges at that baud rate, then answers OK
- --baud=auto (default) tries 57600 (old ATmegaBOOT) then 115200 (Optiboot)
- Optional nano_reset GPIO (NPN to Nano RESET) pulses reset after power
on, needed for Optiboot which skips the bootloader after a power cycle
- Clear errors for outdated ESP32 firmware and missing wires
- README: bootloader table, reset wire, troubleshooting
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ESP32: NanoFlashBridge on TCP port 2323. A connection power cycles the
Nano into its bootloader and bridges TCP <-> Serial2 at 57600 baud; the
clock pauses during the session, 10 s idle timeout
- tools/nano_net_upload.py: STK500v1 uploader over TCP (standard library
only), with sync retries, write and read-back verification. avrdude 6.3
net: ports are broken on Windows, so avrdude is not used
- New nano_ota environment: pio run -e nano_ota -t upload
- README: usage, requirements, troubleshooting; TODO for hardware test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New native environment: pio test -e native
- test/mocks: fake Arduino core, EEPROM and recording BROSE9323 driver
- test/test_nano: 25 tests covering every command, bitmaps, bad input,
flip time storage and the periodic full redraw
- Board environments ignore tests so they are never uploaded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- config.h: all constants and debug defines
- command_parser.h/.cpp: CommandParser class, one method per command
- settings.h/.cpp: EEPROM flip time storage
- main.cpp: setup/loop and the periodic full redraw only
- Full redraw now checks frameEnded() after the serial buffer is drained
- Drop the SPI/Adafruit_I2CDevice include workaround (not needed)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ESP32 talks to the Nano on UART2 (GPIO 17 TX / GPIO 16 RX); Serial (USB)
is now free for debug output and boot messages no longer reach the Nano
- README: wiring table, actual schedule (clock from 08:00), TODO section
for the rewiring
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nano / driver:
- No full on/off flip at boot; the first display() after boot is a forced
full redraw instead, since the physical dot state is unknown
- Send READY when booted
ESP32:
- relayOn() only switches and waits when the relay was off, and waits for
the Nano's READY (3 s timeout) instead of a fixed 3 s on every frame
- relayOff() ends Serial and releases the TX pin so the idle-high line no
longer back-powers the Nano through its RX pin
- No display commands are sent while the relay is off
- Switching the relay on cancels a pending power-off countdown
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Commands run on '\n' instead of busy-waiting for a byte count, which
fixes s/F/T waiting for the next command and P drawing 0xFF garbage
- Invalid bitmaps no longer leak their data as commands; bitmap data is
streamed so full-screen bitmaps still fit
- Store flip time as uint16 with magic marker, validate 50-2000 us
- Overflow-safe millis() handling for the periodic refresh
- Run the full refresh only after D or when serial is idle, so incoming
data is not lost while the redraw blocks
- Put the boot refresh measurement behind MEASURE_REFRESH
- Fix DEBUG hex output and use F() strings to save RAM
- Driver: direct mode now updates the old buffer instead of re-setting
the new one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Shared BROSE9323 driver in lib/, ESPSERIAL selected via build_flags
- Nano firmware in src/nano, ESP32 firmware in src/esp32
- Move WiFi/MQTT credentials to gitignored secrets.h (template provided)
- Pin home-assistant-integration to 2.1.0 from the registry
- Add esp32_usb environment for first flash / recovery
- Add READMEs and CLAUDE.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>