CrossInk is firmware for the Xteink X4 and X3, built with PlatformIO targeting the ESP32-C3 microcontroller.
At a high level, it is firmware that uses an activity-driven application architecture loop with persistent settings/state, SD-card-first caching, and a rendering pipeline optimized for e-ink constraints.
graph TD
A[Hardware: ESP32-C3 + SD + E-ink + Buttons] --> B[freeink-sdk]
B --> C[lib/hal wrappers]
C --> D[src/main.cpp runtime loop]
D --> E[Activities layer]
D --> F[State and settings]
E --> G[Reader flows]
E --> H[Home/Library/Settings flows]
E --> I[Network/Web server flows]
G --> J[lib/Epub parsing + layout + hyphenation]
J --> K[SD cache in .crosspoint]
E --> L[GfxRenderer]
L --> M[E-ink display buffer]
Primary entry point is src/main.cpp.
flowchart TD
A[Boot] --> B[Init GPIO and optional serial]
B --> C[Init SD storage]
C --> D[Load settings and app state]
D --> E[Init display and fonts]
E --> F{Resume reader?}
F -->|No| G[Enter Home activity]
F -->|Yes| H[Enter Reader activity]
G --> I[Main loop]
H --> I
I --> J[Poll input and run current activity]
J --> K{Sleep condition met?}
K -->|No| I
K -->|Yes| L[Persist state and enter deep sleep]
In each loop iteration, the firmware updates input, runs the active activity, handles auto-sleep/power behavior, and applies a short delay policy to balance responsiveness and power.
Activities are screen-level controllers deriving from src/activities/Activity.h.
Nested flows use the ActivityManager stack with startActivityForResult(), setResult(), and finish().
onEnter()andonExit()manage setup/teardownloop()handles per-frame behaviorskipLoopDelay()andpreventAutoSleep()are used by long-running flows (for example web server mode)
Top-level activity groups:
src/activities/home/: home and library navigationsrc/activities/reader/: EPUB/XTC/TXT reading flowssrc/activities/settings/: settings menus and configurationsrc/activities/network/: Wi-Fi selection, AP/STA mode, file transfer serversrc/activities/boot_sleep/: boot and sleep transitions
Reader orchestration starts in src/activities/reader/ReaderActivity.h and dispatches to format-specific readers.
EPUB processing is implemented in lib/Epub/.
flowchart TD
A[Select book] --> B[ReaderActivity]
B --> C{Format}
C -->|EPUB| D[EPUB loader]
C -->|XTC| X[XTC reader]
C -->|TXT| T[TXT reader]
D --> E[Metadata and TOC]
E --> F[CSS rules]
F --> G[Section layout]
G --> H[Page cache]
H --> I[Page model]
I --> J[GfxRenderer]
J --> K[E-ink display]
X --> I
T --> I
For EPUBs, the metadata step reads or writes book.bin, the CSS step reads or
writes css_rules.cache, and the section layout step writes sections/*.bin.
Why caching matters:
- RAM is limited on ESP32-C3, so expensive parsed/layout data is persisted to SD
- repeat opens/page navigation can reuse cached data instead of full reparsing
This diagram zooms into the EPUB path to show the main control and data flow from activity entry to on-screen draw.
flowchart TD
A[ReaderActivity onEnter] --> B{File type}
B -->|EPUB| C[Create Epub object]
B -->|XTC/TXT| Z[Use format-specific reader]
C --> D[Epub load]
D --> E[Locate container and OPF]
E --> F[Build or load BookMetadataCache]
F --> G[Load TOC and spine]
G --> H[Load CSS cache or parse manifest/base-dir CSS]
H --> I[EpubReaderActivity]
I --> J{Section cache exists for current settings?}
J -->|Yes| K[Read section bin from SD cache]
J -->|No| L[Parse chapter HTML and layout text]
L --> M[Apply typography settings and hyphenation]
M --> N[Write section cache bin]
K --> O[Build page model]
N --> O
O --> P[GfxRenderer draw calls]
P --> Q[HAL display framebuffer update]
Q --> R[E-ink refresh policy]
S[SETTINGS singleton] -. influences .-> J
S -. influences .-> M
T[APP_STATE singleton] -. persists .-> U[Reading progress and resume context]
U -. used by .-> I
Notes:
- CSS files are collected from the OPF manifest and, when needed, discovered by streaming ZIP paths under the OPF content base directory; the firmware avoids preloading the full ZIP central directory for large books.
- "section cache exists" depends on cache-busting parameters such as font, viewport size, paragraph alignment, forced paragraph indents, hyphenation, embedded CSS, image rendering, Bionic Reading, and Guide Dots settings
- rendering favors reusing precomputed layout data to keep page turns responsive on constrained hardware
- progress/session state is persisted so the reader can reopen at the last position after reboot/sleep
Two singletons are central:
src/CrossPointSettings.h(SETTINGS): user preferences and behavior flagssrc/CrossPointState.h(APP_STATE): runtime/session state such as current book and sleep context
Most SD-card persistence lives under /.crosspoint/. See file inventory in
docs/data-cache.md; for binary cache formats, see docs/file-formats.md.
Network file transfer is controlled by src/activities/network/CrossPointWebServerActivity.h and served by src/network/CrossPointWebServer.h.
Modes:
- STA: join existing Wi-Fi network
- AP: create hotspot
- Calibre Wireless: STA flow specialized for Calibre plugin uploads
Server behavior:
- HTTP server on port 80
- WebSocket upload server on port 81
- WebDAV handler on the HTTP server
- UDP discovery listener for upload clients
- file operations backed by SD storage
- browser APIs for file management, settings, fonts, OPDS servers, and saved Wi-Fi networks
- activity requests faster loop responsiveness while server is running
Endpoint reference: docs/webserver-endpoints.md.
Some sources are generated and should not be edited manually.
scripts/build_web.pygeneratessrc/network/html/*.generated.hfrom theweb/sourcesscripts/gen_i18n.pygenerateslib/I18n/I18nKeys.h,I18nStrings.h, andI18nStrings.cppscripts/generate_hyphenation_trie.pygenerates hyphenation headers underlib/Epub/Epub/hyphenation/generated/
When editing related source assets, regenerate via normal build steps/scripts.
When editing the web portal under web/, use the local preview server to see
the composed pages without flashing a device:
python3 scripts/preview_web.pyThe preview is available at http://localhost:8000. Pass a port number to use a
different port:
python3 scripts/preview_web.py 9000The preview server re-renders web/templates/base.html, web/pages/*, and
shared assets on refresh, and it serves mock API responses for Files, Settings,
Fonts, Wi-Fi, and OPDS data. It is for browser iteration only; firmware builds
still use scripts/build_web.py to generate the flash-served headers.
src/: app orchestration, settings/state, and activity implementationssrc/network/: web server and OTA/update networkingsrc/components/: theming and shared UI componentslib/hal/: hardware abstraction wrappers around freeink-sdklib/Epub/: EPUB parser, layout, CSS handling, and hyphenationlib/: supporting libraries (fonts, text, filesystem helpers, etc.)freeink-sdk/: hardware SDK submodule (display, input, storage, battery). Docs: https://freeink.org/docsdocs/: user and technical documentation
- constrained RAM drives SD-first caching and careful allocations
- e-ink refresh cost drives render/update batching choices
- main loop responsiveness matters for input, power handling, and watchdog safety
- background/network flows must cooperate with sleep and loop timing logic
Before implementing larger ideas, check: