Skip to content

Latest commit

 

History

History
236 lines (156 loc) · 19.1 KB

File metadata and controls

236 lines (156 loc) · 19.1 KB

PicoRead

PicoRead Logo

PicoRead is my (DerJan's) personal playground fork of CrossPoint Reader — a genuinely excellent open-source e-reader firmware built by Dave Allie and contributors. I'm not trying to compete with or replace it; I just wanted a place to bolt on my own ideas without waiting on someone else's review queue. If you're not already tinkering with the upstream project, go check it out first — everything good here was built on top of it. Not affiliated with the upstream maintainers or with Xteink.

Hardware: ESP32-C3-based Xteink X4 and X3 e-ink readers.

What's in the box

Everything CrossPoint Reader already does well is still here: EPUB 2/3 rendering, hyphenation and kerning, chapter navigation, footnotes, bookmarks, go-to-percent, auto page turn, orientation control, focus reading, KOReader sync, .xtc/.xtch/.txt/.bmp support, a folder browser with hidden-file toggle and SD-cache management, a full wireless toolkit (file transfer, EPUB Optimizer, web settings, WebSocket uploads, WebDAV, hotspot/join-network modes with QR helpers, Calibre connect, OPDS browsing), four themes, sleep screens, remappable buttons, and 24 languages with RTL support. That's the foundation — full credit to upstream for all of it.

On top of that, this fork so far adds:

  • Web-based firmware updates — a Firmware Update page in the web menu with a live progress bar and SHA256 verification before anything gets flashed. No cable required once you're already on a PicoRead build.
  • Cross-book bookmarks — a Bookmarks tile on the home screen jumps straight to any bookmark across your recent books, not just the one you're currently reading.
  • Offline dictionary lookup — inline word lookup in the reader menu using FreeDict dictionaries you install yourself.
  • A working PDF trick, no firmware bloat — the reader has always been able to flip through a folder of .bmp images with the Left/Right buttons. Export a PDF's pages as images on your computer, drop them in one folder on the SD card, and you've got a readable "PDF" with zero code changes and zero flash cost.
  • A leaner EPUB Optimizer — it now strips embedded fonts on request, since the reader never uses @font-face anyway and those files are pure dead weight in most EPUBs.
  • PicoRead theme — a new default theme with a 2-column tile home screen and a 3-cover "Continue Reading" strip up top, alongside the existing Classic/Lyra/RoundedRaff themes (pick any of them in Settings > Display).
  • Reading Statistics — a home-screen tile with per-book stats (sessions, reading time, pages turned, average session length, pages/minute) plus an "All Books" aggregate card.
  • RSS Reader — subscribe to feeds, sync them over Wi-Fi, and read articles fully offline afterwards. See RSS Reader below.
  • Wikipedia — today's featured article, an on-this-day digest, and five random articles, synced with one tap and read fully offline afterward. See Wikipedia below.
  • Gutenberg — a random pick plus the current top-5 most-downloaded public-domain books from Project Gutenberg, downloaded as real EPUBs. See Gutenberg below.
  • Restart / Shut Down — both are now one tap away in Settings > System instead of requiring a button-combo or waiting out the sleep timer.
  • Flappy — a slow-tick, e-ink-appropriate take on Flappy Bird for killing 5 minutes between chapters. Home screen tile, one button to flap.
  • Larger UI font — Settings > System > "Larger UI Font" bumps the text in lists, headers, and popups app-wide (on by default) — off reverts to the original, more compact sizing.

More to come as I find time. See Credits for the full attribution.


USB-locked devices

Some Xteink units bought through third-party resellers (AliExpress and similar) ship with USB flashing locked. Units bought directly from xteink.com are not affected — skip this section.

If flashing over USB fails and you suspect a lock, try the SD-card method first — the OEM bootloader reads update.bin straight off the SD card with no USB/computer involved at all, so a USB lock doesn't apply to it. Only reach for the Xteink Unlocker (part of the upstream CrossPoint tooling, at https://crosspointreader.com/#unlock-tool) if the SD-card flash fails too — locked units are locked for USB flashing specifically, so unlocking is what gets USB working again, not a prerequisite for the SD-card path.

Before you touch the unlocker: it only officially lists CrossPoint and CrossInk, not PicoRead. Once the device is unlocked, use its "Custom .bin" option to flash a PicoRead build instead. Flashing an unsupported firmware on a locked device can brick it permanently or trap it on that firmware with no way back — if USB gets re-locked and the firmware you're stuck with has no OTA path, that's it. Don't experiment here.

Installing

Easiest: the web installer. Go to https://uniquedroid.github.io/unique-esp-web-flasher, connect the device over USB-C, and click "Install PicoRead". It's a small self-hosted tool built on ESP Web Tools that always mirrors this repo's latest release — works the same whether it's a first flash or you're overwriting an existing install.

Already on a PicoRead build? Skip USB entirely — open File Transfer on the device, go to Firmware Update in the web menu (or use Settings > Check for Updates on-device), and it'll check this repo's latest release, verify the SHA256 checksum, and flash with a progress bar.

Prefer the generic flasher? Grab a firmware.bin from Releases (or build your own), head to https://crosspointreader.com/#flash-tools, and use its "Custom .bin" option — PicoRead isn't in that tool's own release list, but any compatible .bin works.

No computer at all? The OEM bootloader can flash from the SD card:

  1. Download update.bin and copy it to the SD card's root folder.
  2. Plug the USB cable into power only (no computer needed), then hold Power + Up (the top-left button on X3) while powering on.
  3. The OEM bootloader flashes PicoRead straight from the SD card.

This is the same recovery mechanism resellers ship for locked units (see USB-locked devices above) - update.bin is just firmware.bin under the filename that bootloader looks for.

Command line, if you'd rather script it:

pip install esptool
# find the port: `dmesg` after connecting on Linux, or on macOS:
#   log stream --predicate 'subsystem == "com.apple.iokit"' --info
esptool.py --chip esp32c3 --port /dev/ttyACM0 --baud 921600 write_flash 0x10000 /path/to/firmware.bin

Want to go back to stock CrossPoint? Same web flasher, https://crosspointreader.com/#flash-tools, pick the official release instead of a custom .bin.

Building it yourself instead of downloading a release? Jump to Development below.


RSS Reader

The RSS tile on the home screen manages a small offline feed reader. Subscribed feeds list at the top (or a "no feeds yet" placeholder), with the actions below a divider:

  • Add Feed — enter a feed URL (RSS 2.0 or Atom) on-device via the on-screen keyboard. Connects to Wi-Fi automatically if you're not already connected.

  • Sync Now — re-fetches every subscribed feed and saves its articles to the SD card as plain .txt files (title, link, then the article text with HTML stripped — and any invisible zero-width Unicode padding some feeds embed stripped too), written one at a time as they're parsed rather than collected in RAM first. No EPUB generation involved — it reuses the existing .txt reader, so there's no new reading UI or extra RAM cost.

  • Import from SD Card — bulk-add feeds from a JSON file at /rss_feeds_import.json on the SD card root, no Wi-Fi needed for this step. Handy for setting up a batch of feeds from a computer instead of typing each URL on-device. A sample file is at sdcard/rss_feeds_import.json:

    {
      "feeds": [
        {
          "url": "https://www.heise.de/rss/heise-atom.xml",
          "title": "heise online"
        },
        {
          "url": "https://www.tagesschau.de/xml/rss2"
        }
      ]
    }

    url is required; title is optional — if omitted, the feed URL is used as the display name until the next sync, which fills in the real title from the feed itself.

    A few ready-made feed lists are also included, so you don't have to hunt down URLs yourself:

    File Contents
    sdcard/rss_feeds_import_NEWS_DE.json German general news (Tagesschau, SPIEGEL, ZEIT, FAZ, SZ, n-tv, WELT, stern, FOCUS, ZDFheute)
    sdcard/rss_feeds_import_NEWS_EN.json English general news (BBC, NYT, The Guardian, CNN, Washington Post, NPR, ABC News, Al Jazeera, TIME, Financial Times)
    sdcard/rss_feeds_import_TECHNEWS_DE.json German tech news (heise, Golem, ComputerBase, t3n, CHIP, WinFuture, Caschys Blog, PCGH, Hardwareluxx, Notebookcheck)
    sdcard/rss_feeds_import_TECHNEWS_EN.json English tech news (Ars Technica, The Verge, TechCrunch, Wired, Engadget, CNET, Tom's Hardware, VentureBeat, 9to5Mac, ZDNET)
    sdcard/rss_feeds_import_TECHNEWS_AND_NEWS_DE.json The two German lists above combined (20 feeds)
    sdcard/rss_feeds_import_TECHNEWS_AND_NEWS_EN.json The two English lists above combined (20 feeds)
  • Manage Feeds — lists every feed for one-tap removal, plus a "Delete All Feeds" row to clear everything at once.

Tapping a synced feed shows a list of every synced article's title to pick from. Paging past the first/last page of an article jumps straight into the previous/next one, and the status bar shows your position in the feed ("3 / 12"). Back returns to the RSS overview instead of Home.

On the SD card: the feed subscription list (URL + title per feed) is the one piece of RSS data that's real JSON, at .picoread/rss_feeds.json — the same file Import from SD Card reads/writes, just persisted rather than one-off. Each feed's synced articles, on the other hand, live under .picoread/rss/<feed-name>/ (folder named after the feed's title, sanitized for the SD card's filesystem, with a numeric suffix if two feeds sanitize to the same name) as plain numbered files — 0.txt, 1.txt, ... — each just the article title on line 1, then link, then the body text with HTML stripped. A sync also writes an index.txt next to them: one title per line, in article order, so the article list can populate by reading one small file instead of opening every article just to show its title. (Feeds synced before index.txt existed still work — the list falls back to opening each article and reading its first line — but re-syncing rebuilds the fast index.) All of this follows the same caching philosophy as the rest of the firmware (see How the caching works).


Wikipedia

The Wikipedia tile on the home screen fetches content from Wikipedia's own REST API, in the language you've set in Settings:

  • Article of the Day — the current featured article.
  • On This Day — a digest of historical events for today's date.
  • Random Article 1-5 — five random articles, each its own entry.

None of these fetch on their own when selected — tap Sync Now (below a divider, like the RSS reader's action row) to download everything and save it to the SD card, then read it fully offline afterward (handy for e.g. syncing once before leaving the house). Selecting an entry before ever syncing offers to sync right away instead of just bouncing back to the list. Back returns to the Wikipedia overview instead of Home.

On the SD card: Wikipedia's REST API replies in JSON, but since the list of entries is fixed (not user-managed like RSS feeds or Gutenberg's catalog), there's no title index to maintain — each entry just gets its own fixed-name plain-text file straight under .picoread/wikipedia/: article.txt, onthisday.txt, random0.txt...random4.txt. Every sync overwrites all of them — no history is kept.


Gutenberg

The Gutenberg tile on the home screen browses public-domain books via Gutendex, a JSON index over Project Gutenberg:

  • Random Book — a random pick from the catalog, freshly chosen every time you select it.
  • Popular Book 1-5 — the current top-5 most-downloaded books.

Unlike Wikipedia's "sync everything at once" model, Sync Now only fetches the popular list's metadata (titles + EPUB links, one small request) — selecting an entry downloads just that one book's real EPUB (proper chapters and table of contents, not a plain-text dump), once, and reuses the saved copy after that. Random Book always re-downloads fresh.

Manage Books lists everything you've downloaded, with two actions per book:

  • Move to Library — relocates the file into your own library folder (/ebooks by default, editable via Change Library Folder in the same screen) under a name based on the book's title, so it shows up as a permanent book instead of living in Gutenberg's cache.
  • Delete — removes the downloaded file.

On the SD card, everything lives under .picoread/gutenberg/. Gutendex itself answers in JSON, but PicoRead only ever needs title + EPUB link out of that response, so Sync Now reduces it down to a plain list.txt (one title line, one url line, per book — no JSON parsing needed again once it's on the card) instead of caching the raw API response. The books themselves are saved as random.epub and popular0.epub...popular4.epub; your chosen library folder path is a one-line library_path.txt.


Custom SD-card fonts

No firmware reflash needed for this one — install your own TTF/OTF as SD-card fonts:

  1. Open https://crosspointreader.com/fonts (upstream's font builder — the .cpfont output isn't CrossPoint-specific, it works here unchanged).
  2. Upload up to four styles, set the family name, sizes, and Unicode range.
  3. Drop the generated .cpfont files under /fonts/YourFont/ (or /.fonts/YourFont/ to keep the SD root tidy) and pick the font from the device's font settings.

Runs through lib/EpdFont/scripts/fontconvert_sdcard.py unmodified, so a local build produces byte-identical output.


Documentation


Development

git clone --recursive https://github.com/UniqueDroid/PicoRead
cd PicoRead
# forgot --recursive? git submodule update --init --recursive

pio run --target upload

You'll need pioarduino (CLI or the VS Code plugin), Python 3.8+, clang-format 21, and a USB-C cable that actually does data.

Before sending anything anywhere:

./bin/clang-format-fix
pio check -e default
pio run -e default

For live serial debugging: python3 -m pip install pyserial colorama matplotlib, then python3 scripts/debugging_monitor.py (macOS: pass the device path, e.g. /dev/cu.usbmodem2101). Windows may need minor tweaks.


How the caching works

RAM is the whole ballgame on an ESP32-C3 — ~380KB usable, so the firmware leans hard on the SD card instead. First open of a book parses and caches everything under .picoread/ on the card; every open after that reads straight from cache:

.picoread/
├── epub_<hash>/         one directory per book, keyed by content hash
│   ├── progress.bin     reading position
│   ├── cover.bmp        generated cover
│   ├── book.bin         title/author/spine/TOC
│   ├── css_rules.cache  parsed CSS
│   ├── img_*            rendered image cache
│   └── sections/        per-chapter layout cache
├── settings.json
├── state.json
└── recent.json

Deleting, moving, or renaming a book file changes its hash, so the firmware and web UI clean up the old cache automatically; editing files directly on the card from a computer won't, so you can end up with orphaned cache folders that way. If something looks stale or corrupted, deleting .picoread/ forces a full rebuild. Format details: file formats document.


If PicoRead doesn't do what you want

It's one person's fork, evolving in whatever direction I find interesting — not a general-purpose platform. If you need something outside that direction, the wider CrossPoint ecosystem has plenty of other forks worth a look:

Building your own hardware rather than buying one? de-link is worth a look too.

Since this is a solo fork, I'm not set up to take contributions the way the upstream project is — but the contributing docs are still a good architecture reference if you're poking around the code, and the upstream ideas board is the right place for feature discussion that isn't specific to my fork.


Credits

Everything foundational here — the reader engine, the activity architecture, the HAL, the years of design work that make this run well on a 380KB-RAM chip — is CrossPoint Reader's, by Dave Allie and contributors, MIT-licensed (see LICENSE). Go star it, and if you're able to, chip in for the people actually maintaining it — this fork wouldn't exist without their work.

CrossPoint itself credits diy-esp32-epub-reader as its original inspiration.