Open-source EPUB e-reader firmware for Xteink X4 (ESP32-C3 RISC-V, 160MHz, 380KB RAM, no PSRAM, 16MB flash, 800×480 E-Ink monochrome single-buffer).
See CLAUDE.md for the full development guide. This file is the condensed "things you'd otherwise guess wrong" reference.
pio run # build (default env, LOG_LEVEL=2)
pio run -t clean && pio run # clean build
pio run -e gh_release # production build (LOG_LEVEL=1)
pio run -t upload # build + flash device
pio run -t upload && pio device monitor # flash + serial (115200 baud)
pio check # cppcheck static analysis (--fail-on-defect=low)Formatting: clang-format 21 via project script (not raw clang-format -i):
./bin/clang-format-fix # all tracked .c/.cpp/.h/.hpp (excludes generated/)
./bin/clang-format-fix -g # only git-modified filesCI runs on push to master and all PRs: clang-format → cppcheck → build (gated by test-status job). PR titles must follow conventional commits (feat:, fix:, etc.) — enforced by semantic-pull-request bot.
| Generated file | Source | Generator |
|---|---|---|
lib/I18n/I18nKeys.h, I18nStrings.h, I18nStrings.cpp |
lib/I18n/translations/*.yaml |
scripts/gen_i18n.py (runs pre: build) |
src/network/html/*.generated.h |
data/html/*.html |
scripts/build_html.py (runs pre: build) |
All generated files + .pio/, build/, compile_commands.json, platformio.local.ini are in .gitignore.
To add translations: edit YAML, run python scripts/gen_i18n.py lib/I18n/translations lib/I18n/, commit only the YAML.
- Activity lifecycle: Activities are heap-allocated (
new), not singletons. Navigation =deleteold +newnext. Pattern:onEnter()allocates + renders,loop()handles input,onExit()frees everything. FreeRTOS tasks created inonEnter()MUST bevTaskDelete()'d inonExit()BEFORE activity destruction. - HAL layer: Always use
HalDisplay,HalGPIO,HalStorage— never the raw SDK classes (EInkDisplay,InputManager,SDCardManager). - Button mapping: Use
MappedInputManager::Buttonenums (Button::Confirm,Button::PageForward), never raw GPIO indices. - File I/O:
FsFile(SdFat), NOT ArduinoFile. Must call.close()explicitly. - UI strings: All user-facing text uses
tr(STR_XXX). Log messages (LOG_DBG/LOG_ERR) may be hardcoded. - Single framebuffer:
EINK_DISPLAY_SINGLE_BUFFER_MODE=1. Grayscale rendering requiresrenderer.storeBwBuffer()/renderer.restoreBwBuffer().
Font IDs are integer #defines in src/fontIds.h (NOT string identifiers). Use like:
#include "fontIds.h"
renderer.drawText(UI_10_FONT_ID, x, y, "text");Available: NOTOSERIF_12_FONT_ID through 18, NOTOSANS_*, OPENDYSLEXIC_8 through 14, UI_10_FONT_ID, UI_12_FONT_ID, SMALL_FONT_ID.
- C++20, no exceptions (
-fno-exceptions), no RTTI. - Stack locals < 256 bytes.
std::vector→ call.reserve(N)beforepush_backloops. constexpr/static constfor large data (lives in flash, not DRAM).std::string_viewis NOT null-terminated. Convert viastd::string(v).c_str()before any C API.- ESP32-C3 = RISC-V → faults on unaligned multi-byte loads. Use
memcpy, never pointer casts on raw buffers. IRAM_ATTRfor ISRs,DRAM_ATTRfor data accessed by ISR code.xSemaphoreTake()crashes in ISR context → usexSemaphoreGiveFromISR()/xQueueSendFromISR().
SD-based binary cache at .crosspoint/epub_<hash>/ on SD card root. Auto-invalidated on format version bump or render setting change. Current versions: BOOK_CACHE_VERSION = 5 (BookMetadataCache.cpp:12), SECTION_FILE_VERSION = 21 (Section.cpp:13). When modifying cache structs, increment the version and update docs/file-formats.md.