Skip to content

Releases: stanelie/esp-reader

v1.1.2 - denser pages, and a transpose bug behind the menu artefacts

Choose a tag to compare

@stanelie stanelie released this 06 Sep 21:40

Layout and picker fixes for all three boards. No firmware change since
v1.1.0 - the .uf2 and the two .bin images are byte-identical to it.

A row back on every screen

The line pitch followed the font's glyph BOX, but a .pf box usually carries
a spare row of padding that is never inked. It now follows the font's INK,
floored at the ink height so nothing can clip:

line pitch = max(ink height, glyph box + the panel's leading)

On the 296x128 panels (E290 and Badger, leading: -1):

font box ink before after
Literata (default) 15 14 8 lines 9 lines
Dejavu 13 12 9 lines 10 lines
Open Sans 13 13 9 lines 9 lines
Literata Large / Larger 16 / 19 16 / 19 7 / 6 7 / 6

Three of the five fonts have no spare row, so a flat -1 would have clipped
them - the floor is what makes this safe per font. The E213 is unchanged.

The menu artefacts, and what was actually causing them

A dark bar appeared over the top of the first list entry when the selection
moved off it. The cause was not the highlight: it was the banded transpose in
fbrotate, which cleared whole byte columns. One landscape row is one
bit inside a byte column, so a band is a few bits per native row, not a few
bytes - and whenever the band edge fell mid-byte, blanking the column wiped
rows just outside it. The picker's header band ends at the row pitch, so it
destroyed the top of the row below; the highlight XOR then inverted the
damage to black.

It scaled with the pitch - 1 row lost at a pitch of 15, 2 at 14, 4 at 12 -
so it was already present before the layout change, as a thinner line. Now
masked per row, the same arithmetic menufast.xor_band() always used.

This affects the E290 as well, which shares the rotation-3 path.

Also: picker labels are positioned by their ink rather than their box, which
is a per-font offset, so a bar exactly one pitch tall contains the whole
glyph including descenders.

Other fixes

  • "No books yet." A drive with no .txt on it reported
    [Errno 2] No such file/directory: book.txt, naming a file the user never
    had. It now says what to copy, or points at the library when there is an
    .epub waiting to be converted. Deleting the book you were reading was
    already handled and is unchanged.
  • Sleep hold is 3 s, up from 1.2 s, which was easy to trigger by
    accident. The same threshold backs out of the picker and cancels jump-to.
  • The boot log now records the derived layout:
    Font literata.pf: box 15, ink 14, pitch 14, 9 lines/page.

Assets

  • badger-1.1.2.zip - precompiled Badger reader, use this one
  • badger2040-circuitpython-real-light-sleep.uf2 - unchanged since v1.1.0,
    only reflash if coming from older firmware
  • heltec_vision_master_e213.bin / heltec_vision_master_e290_lightsleep.bin
    • unchanged since v1.0.0, flash at 0x0
  • SHA256SUMS

E213/E290 readers: copy the contents of device/ from the repository. The
Badger needs the precompiled package - its RP2040 cannot compile code.py at
boot and still leave a contiguous page buffer.

v1.1.1 - Badger deep sleep powers off

Choose a tag to compare

@stanelie stanelie released this 05 Sep 21:49

Follow-up to v1.1.0. Deep sleep on the Badger 2040 now switches the board
off rather than leaving it drawing 1 mA.

Badger 2040 power, measured on a PPK2

state stock patched
awake, 125 MHz 25 mA 25 mA
light sleep 16 mA 2 mA
deep sleep 1 mA 3 µA
reader average, 1 page / 10 s 18 mA 5 mA

Two separate problems. Light sleep was fixed in v1.1.0 by patching
CircuitPython (firmware/patches/0002-rp2-real-light-sleep.patch).

Deep sleep was the other one, and no firmware change reaches it: the RP2040
is already fully dormant there, and the remaining milliamp is the board -
regulator, panel, divider. This board latches its own rail, so deep sleep now
drops it and powers off outright. 1 mA -> 3 µA, low enough that a cell's
self-discharge dominates.

It costs nothing on wake: deep sleep here was always a full reboot, so a cold
boot on any button is the same experience, and the panel holds the sleep
screen with no power at all. On USB the cable feeds the rail and it cannot be
cut, so the reader falls through to an ordinary deep sleep and debugging is
unaffected.

Assets

  • badger-1.1.1.zip - precompiled Badger reader, use this rather than
    v1.1.0's
    , which predates the deep-sleep change
  • badger2040-circuitpython-real-light-sleep.uf2 - patched CircuitPython,
    unchanged from v1.1.0. Hold BOOTSEL, plug in, copy to the RPI-RP2 drive.
    Back up CIRCUITPY first - flashing can reformat it.
  • heltec_vision_master_e213.bin / heltec_vision_master_e290_lightsleep.bin
    • unchanged since v1.0.0, flash at 0x0
  • SHA256SUMS

A note for Badger users running from source

The RP2040 can no longer compile device/code.py at boot and still have a
contiguous 4736-byte block left for a page buffer - it fails with
MemoryError in the display driver. The precompiled package is not an
optimisation on this board, it is the supported layout. code.py there is a
one-line shim importing lib/ereader.mpy; rebuild with
tools/build_badger_release.py. The E213 and E290 are unaffected and still
run device/ directly.

v1.1.0 - real light sleep on the Badger

Choose a tag to compare

@stanelie stanelie released this 05 Sep 21:32

Measured power work on the Badger 2040, plus EPUB and jump-to fixes for
all three boards.

Badger 2040: 18 mA -> 5 mA

Stock CircuitPython's alarm.light_sleep_until_alarms() barely sleeps on
RP2040. Measured on a PPK2, one page every 10 s:

state stock patched
awake, 125 MHz 25 mA 25 mA
light sleep 16 mA 2 mA
reader average, 1 page / 10 s 18 mA 5 mA

1 mA of the remaining 2 mA is the board itself - regulator, panel, divider -
so the chip's own share went from ~15 mA to ~1 mA. The Badger now draws less
than the E290's 8 mA.

Two causes, both needed fixing: the port's sleep_en masks gate clock
distribution and leave clk_sys and both PLLs running, and the 1024 Hz
supervisor tick drags the core out of WFI every 977 us regardless. Upstream
notes the first itself - this only saves about 2mA right now.

badger2040-circuitpython-real-light-sleep.uf2 is CircuitPython 10.1.0-beta
for the Badger 2040 with that patch. Hold BOOTSEL, plug in, copy the .uf2 to
the RPI-RP2 drive. Back up CIRCUITPY first - flashing can reformat it,
and it holds your books and reading positions. Source:
firmware/patches/0002-rp2-real-light-sleep.patch.

Fixes (all boards)

  • EPUB chapters converted out of order. Calibre splits long chapters into
    numbered fragments, and the converter sorted by fragment index while
    ignoring which chapter it belonged to - so any book with more than one
    multi-fragment chapter came out with every chapter's opening bunched
    together, then every chapter's body. Now reads the EPUB's own spine.
  • A crash that only appeared on hardware: chapters decompressed through
    the pure-Python fallback inflater come back bytearray-backed, and a
    bytearray is not hashable. Invisible in host tests, where CPython's zlib
    always returns bytes.
  • Battery-only EPUB conversion hung after converting on E213/E290 and
    needed a manual reset. The post-conversion restart chose a soft reload
    whenever off USB - a rule that only exists to protect the Badger's power
    latch, which those boards do not have.
  • Jump-to-percent could land on the wrong offset: a hold-to-confirm
    arriving quickly after a tap was swallowed as a double-tap.

Assets

  • badger-1.1.0.zip - precompiled, ready-to-copy Badger 2040 reader
  • badger2040-circuitpython-real-light-sleep.uf2 - patched CircuitPython
  • heltec_vision_master_e213.bin / heltec_vision_master_e290_lightsleep.bin
    • unchanged since v1.0.0, flash at 0x0
  • SHA256SUMS - covers every asset here

For the E213/E290 reader itself, copy the contents of device/ from the
repository; those boards have no precompiled package.

v1.0.2

Choose a tag to compare

@stanelie stanelie released this 05 Sep 19:32

Bug fix release for the Badger, E213 and E290 reader: broken EPUB
chapter order, and a battery-only reboot hang after converting one.

Fixed:

  • EPUB conversion sorted Calibre's multi-part chapter fragments by
    fragment index alone, ignoring which chapter they belonged to - any
    book with more than one multi-fragment chapter (very common) came
    out with every chapter's opening bunched together, followed by every
    chapter's body, instead of each chapter's pieces staying together.
    Now reads the EPUB's own spine (its authoritative reading order)
    instead of guessing from filenames.
  • A crash the above fix introduced on real hardware only: a chapter
    decompressed through the pure-Python fallback inflater comes back
    bytearray-backed, and a bytearray isn't hashable - using one as a
    dict key raised unsupported type for __hash__. Invisible on a
    host-only test, since CPython's zlib always returns bytes.
  • Battery-only EPUB conversions (the only way the E213/E290 converter
    runs at all) reliably hung after converting and needed a manual
    reset. The post-conversion restart was choosing a soft reload
    whenever off USB, for every board - a rule that exists only to
    protect the Badger's power latch. The E213/E290 have no such latch,
    so they're switched to the hard reset that already worked over USB.
  • "Restarting to make room..." reworded to "Please wait..." on the
    conversion screen.

Included: badger-1.0.2.zip, a precompiled, ready-to-copy Badger
2040 build (see README.txt inside for install instructions, or
tools/build_badger_release.py to build your own).

The E213/E290 firmware images from v1.0.0 are unaffected (these are
CircuitPython source-level fixes, not firmware changes) - use v1.0.0's
assets for those.

v1.0.1

Choose a tag to compare

@stanelie stanelie released this 04 Sep 14:22

Bug fix release for the Badger, E213 and E290 reader: jump-to-percent
could land on the wrong offset.

Fixed: the "Jump to..." screen's double-tap-to-decrement gesture
could swallow a hold-to-confirm press that followed a tap too quickly,
silently applying an unwanted -5% before the jump. Dialling to a value
and immediately holding to confirm - a completely normal sequence -
was the exact trigger; reproduced and fixed against a real E213.

Included: badger-1.0.1.zip, a precompiled, ready-to-copy Badger
2040 build (see README.txt inside for install instructions, or
tools/build_badger_release.py to build your own).

The E213/E290 firmware images from v1.0.0 are unaffected by this fix
(it's a CircuitPython-level source change, not a firmware change) - use
the v1.0.0 release assets for those, verified against
v1.0.0's SHA256SUMS.

v1.0.0

Choose a tag to compare

@stanelie stanelie released this 30 Aug 17:33

First release: a precompiled Badger 2040 package, plus the prebuilt Heltec firmware images that used to live in the repo tree.

badger-1.0.0.zip

Copy the contents onto the CIRCUITPY drive's root and reset - that's the whole install. Built from device/code.py and device/lib/*.py with tools/build_badger_release.py: the reader itself and every module it imports at boot (hyphenator, propfont, fbrotate, bookmarks, uc8151badger) are precompiled to .mpy, so the board isn't parsing and compiling ~2700 lines of source on every deep-sleep wake. Cuts wake-to-ready time roughly in half compared to running the same code as source.

To modify the reader: edit the source in device/, then rebuild with python3 tools/build_badger_release.py.

heltec_vision_master_e213.bin / heltec_vision_master_e290_lightsleep.bin

Merged CircuitPython images (bootloader + partition table + app in one file) with the real-light-sleep patch from firmware/patches/ applied - see firmware/README.md for how to build these yourself and how to flash them. Flash at address 0x0, not 0x10000.

Verify any download against SHA256SUMS before flashing or copying to a device.