Skip to content
akoo1010Public

About

Anki on an Xteink X4

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

xteinki

Anki-style spaced-repetition flashcards for the Xteink X4 e-reader.

The X4 is a $69, 77-gram pocket e-reader with a 4.26" e-ink screen, an ESP32-C3, and — crucially — a row of four buttons along the bottom edge that map perfectly onto Anki's Again / Hard / Good / Easy. xteinki is a custom firmware that turns it into a dedicated, offline flashcard device:

  • FSRS-6 scheduling — a faithful C++ port of py-fsrs 6.3.1, validated against 48 generated golden scenarios (224 reviews reproduced exactly: due times to the millisecond, stability/difficulty to 1e-9).
  • Imports real Anki decks — drop a legacy-format .apkg on the microSD card; the device extracts, renders, and schedules it on-device.
  • Progress that survives everything — scheduling state lives on the SD card keyed by Anki note identity, so firmware updates, deck re-exports, power loss, and reflashing never lose a review. An append-only review log doubles as crash recovery and as optimizer input.
  • Paced like Anki — daily new-card cap (default 20/deck), learning steps, learn-ahead, next-interval previews under each grade button.
  • Fully offline — WiFi is used for a few seconds once to learn the time (NTP), then never again. Weeks of battery.

Status

Working end-to-end on hardware and in daily use: import, review, grading, persistence, deep sleep, battery indicator. Current limitations: plain-text cards (non-ASCII folds to closest ASCII, CJK shows as ?), no cloze notes (skipped at import and counted), no media, max 4000 cards per deck, max 8 decks, single-level decks (one .apkg = one deck).

Hardware you need

An Xteink X4 bought through official channels (xteink.com or Xteink's official Amazon store — sold as a "Developer Edition" with unlocked firmware). Grey-market units can be USB-locked and may be permanently bricked by third-party firmware — do not flash those. No soldering, no disassembly: flashing happens over USB-C.

⚠️ Flashing custom firmware voids the Xteink warranty. Before your first flash, back up the stock firmware (once, ~20 minutes):

pip install esptool
esptool.py --baud 921600 read_flash 0 0x1000000 stock-x4-backup.bin

Restore any time with esptool.py write_flash 0 stock-x4-backup.bin, or reflash stock/CrossPoint via the CrossPoint web flasher.

Quick start

  1. Build & flash (requires PlatformIO):

    pio run -t upload
    pio device monitor        # optional: 115200 baud serial log
  2. Export a deck from Anki with "Support older Anki versions" checked (in the export dialog, format "Anki Deck Package"). The modern default format is refused with an on-screen message — it contains a zstd-compressed database this device can't read. Unchecking "Include media" makes the file smaller (media is skipped anyway).

  3. Copy the .apkg into /decks/ on the microSD card. From macOS, avoid Finder's invisible sidecar files with:

    cp -X MyDeck.apkg /Volumes/SD_CARD/decks/ && dot_clean /Volumes/SD_CARD
  4. Create /xteinki/config.txt so the device can learn the time (once):

    wifi_ssid YourNetwork
    wifi_pass YourPassword
    

    2.4GHz networks only (ESP32-C3 limitation). WiFi runs for a few seconds at cold boot for NTP, then powers off; the clock is persisted to SD after every review, so one successful sync lasts indefinitely.

  5. Review. Boot lands on the deck menu; Confirm opens a deck. Any bottom button reveals the answer, then the four buttons grade Again / Hard / Good / Easy with next-interval previews. Side buttons scroll long cards. Short power press: back to the menu (grades are saved instantly — backing out never loses anything). Hold power 1s: deep sleep; hold 1s again: wake. Finishing a deck shows "All caught up!" with the next due time — decks refill as cards come due.

Configuration (/xteinki/params.txt, all optional)

retention 0.9            # FSRS desired retention (0.5-1.0)
new_per_day 20           # new cards introduced per deck per day (0 = none)
tz_offset_hours -8       # shifts the daily rollover from UTC to your midnight
weights 0.212 1.2931 ... # 21 personalized FSRS weights (see below)

Personalized FSRS weights (optional)

The built-in default weights are trained on hundreds of millions of reviews and work well. After ~1,000 of your own reviews, you can personalize:

python3 -m venv .venv && .venv/bin/pip install "fsrs[optimizer]==6.3.1"
.venv/bin/python tools/optimize_params.py /Volumes/SD_CARD/xteinki/*/revlog.jsonl

It prints a weights line to paste into params.txt. The optimizer (which needs PyTorch) runs on your computer — the device only ever runs the lightweight scheduler.

SD card layout

/decks/<name>.apkg            your Anki exports (input)
/xteinki/<name>/cards.bin     rendered card content (rebuilt on import)
/xteinki/<name>/state.bin     FSRS memory per card — keyed by Anki note guid,
                              survives re-imports and firmware updates
/xteinki/<name>/revlog.jsonl  append-only review history
/xteinki/<name>/daycount.txt  today's new-card counter
/xteinki/params.txt           scheduling configuration (optional)
/xteinki/config.txt           WiFi credentials for time sync (optional)
/xteinki/clock.txt            persisted clock (managed by the device)

Backing up your progress = copying /xteinki/ off the card.

Development

pio run                              # firmware build (ESP32-C3)
pio test -e native                   # 20 host-side tests: FSRS golden
                                     #   vectors, import pipeline, stores
python tools/generate_fsrs_vectors.py  # regen FSRS vectors (pip install fsrs==6.3.1)
python tools/make_test_apkg.py         # regen import fixtures (pip install genanki)

The scheduler, importer, and storage are portable C++ tested natively; only src/ (UI, WiFi, power) is device-specific. tools/host_import.cpp builds a host-side import harness (see file header) for debugging real decks under AddressSanitizer.

Hardware notes (Xteink X4)

Function Pins
E-ink (GDEQ0426T82 / SSD1677, SPI) SCLK 8, MOSI 10, CS 21, DC 4, RST 5, BUSY 6
microSD (same SPI bus) CS 12, MISO 7
Bottom buttons (resistor ladder, ADC) GPIO1: Back/Confirm/Left/Right
Side buttons (resistor ladder, ADC) GPIO2: Up/Down
Power button / battery / USB detect GPIO3 / GPIO0 / GPIO20

ESP32-C3 ROM symbol trap (hard-won lesson): the C3's mask ROM contains an ancient miniz, and the IDF ROM linker scripts hard-assign those symbol names (tinfl_decompress etc.), silently replacing same-named functions in your firmware with decade-old ROM code whose struct layouts differ — corrupting memory at a distance. Our vendored lib/miniz/miniz.h renames all 17 colliding symbols. If you add a library defining symbols listed in esp32c3.rom*.ld, it will suffer the same fate.

Credits

  • The open-x4-epaper community for the X4 pin maps, sample firmware, and the vendored InputManager/BatteryMonitor (MIT), and the CrossPoint community for proving the X4 is a hackable platform.
  • Open Spaced Repetition for FSRS and py-fsrs, the reference for our scheduler port.
  • GxEPD2, Sqlite3Esp32, miniz, cJSON, genanki.
  • Anki® is a trademark of Ankitects Pty Ltd. This project is not affiliated with or endorsed by Ankitects; it reads the documented .apkg format via its own independent implementation.

License

MIT (see LICENSE). Vendored components keep their original licenses in their directories.

About

Anki on an Xteink X4

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages