Frames were sent as F0 plus one s command per yellow dot (thousands of
characters). During scrolling that overran the Nano's serial buffer, and
half-received frames redrew the whole screen. showBitmap() now sends two B
commands over the changed rectangle (dots off, dots on), or one B command for
a full frame, and the pacing counts the characters sent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Text elements can scroll in a window at a set speed; clock, countdown and
stopwatch can show seconds. Moving pages are sent as instant frames (new
Nano command D1) and paced by the estimated flip time.
- Countdown to a date and time (smart, days, days + time, with seconds),
stopwatch (web UI, HA switch and reset button), image element (fixed or
by Home Assistant state), multiplier for HA values, degree sign (char 127)
in all fonts and the font converter.
- Page format version 2; version 1 pages are converted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hardcoded clock is now the built-in dynamic page "Clock". A dynamic page
has a background bitmap per time of day and up to 8 elements (clock, date,
Home Assistant value, text), each with position, alignment, font and overlay.
Pages are drawn on the ESP32, also for the web previews.
- Pages tab in the web UI: page editor with drag and drop, font upload
(TTF/OTF converted in the browser to the FDF1 dot font format)
- Built-in fonts Small, Normal, Large and Full height (generated by
tools/make_builtin_fonts.py), built-in bitmaps Pikachu and Pikachu yawning
- Home Assistant values over the REST API (URL and token in the HA tab)
- Settings version 4: evening settings removed (now time slots of the Clock
page), old records are converted
- HA Mode dropdown lists the dynamic pages and bitmaps
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home Assistant: remove the Clock switch (its entity is deleted on connect),
add an IP sensor and Mode / Off image dropdowns whose options follow the
saved bitmaps. Remove the built-in "Nintendo border" bitmap.
Off image: a bitmap (or blank) left on the dots when the display switches off.
Nano: pause after each flip pulse and repeat pulses for changed dots, both
stored in EEPROM (G/N commands). Refresh effects (E command): instant,
sparkle, wipe, rain, from the middle out, diagonal, spiral, flip all then
reveal, push and random, optionally only for small changes. Effects run in
steps from loop() so serial input keeps flowing.
Web UI: flip settings, refresh effect and test patterns under Settings; the
System tab shows why the ESP32 last restarted. Settings records from
versions 1 and 2 are migrated. Firmware 2.3.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Web UI on port 80: live view of the display, choose the page (clock or
a bitmap), bitmaps (list, show, live, edit, delete), draw / image upload
with conversion in the browser, live mode, settings, Home Assistant
- Live view from a shadow buffer in the BROSE9323 driver (shownPixel)
- Settings in NVS with the original behaviour as defaults: schedule times,
quarter hours, yawning Pikachu, No WiFi screen, flip time, status LED,
Home Assistant broker (restart on change)
- Bitmaps on LittleFS; new 320 KB partition, ota slots now 1.31 MB
- DisplayController: pages, live mode, settings; app.cpp holds the objects
- README for the recovery app; docs for the web UI
- Tests: settings, time parsing, bitmap format and names, schedule with
settings, pages and live mode (95 total)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Clock firmware: 'Connecting to WiFi...' and 'Getting the time...' at
boot, 'Crashed!' in the OTA-only modes, and 'No WiFi' with the time
when WiFi is down for over a minute (new DisplayController screen)
- Recovery app: shows its reason, the setup WiFi name, or its IP address,
then powers the display off (flipdots keep the image)
- StatusScreen and drawTextLines shared by both apps
- Driver: ESP begin() no longer draws 'ESP Connected'
- Tests: No WiFi screen, short reason texts (70 total)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>