# CrossPoint Reader - Developer Guide This guide provides essential information for developers working on the CrossPoint Reader firmware for the Xteink X4 e-paper device. ## Project Overview CrossPoint Reader is an open-source firmware replacement for the Xteink X4 e-paper reader, built using PlatformIO and targeting the ESP32-C3 microcontroller. The firmware provides EPUB reading capabilities with a focus on memory efficiency due to the ESP32-C3's limited RAM (~380KB). ## Architecture ### Main Components 1. **Activity System**: The firmware uses an activity-based architecture where each screen/state is an `Activity` subclass 2. **Display System**: E-ink display handling via `EInkDisplay` and `GfxRenderer` from the OpenX4 SDK 3. **Input System**: Button handling via `InputManager` and `MappedInputManager` with configurable button layouts 4. **File System**: SD card management via `SDCardManager` 5. **State Management**: `CrossPointState` and `CrossPointSettings` for persistent configuration ### Key Directories - `src/activities/` - All screen activities (Home, Reader, Settings, etc.) - `src/network/` - Web server and OTA update functionality - `src/util/` - Utility functions - `src/images/` - Embedded image assets - `open-x4-sdk/` - Community SDK with hardware libraries (submodule) ## Display Programming ### E-Ink Display Basics The display is a 480x800 e-paper panel controlled via SPI. Key display-related files: - `src/main.cpp` - Display initialization and SPI pin definitions - Uses `EInkDisplay` from OpenX4 SDK - Uses `GfxRenderer` for drawing operations ### Display Initialization ```cpp // Display SPI pins (custom pins for XteinkX4) #define EPD_SCLK 8 // SPI Clock #define EPD_MOSI 10 // SPI MOSI #define EPD_CS 21 // Chip Select #define EPD_DC 4 // Data/Command #define EPD_RST 5 // Reset #define EPD_BUSY 6 // Busy EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY); GfxRenderer renderer(einkDisplay); ``` ### Drawing Operations The `GfxRenderer` class provides drawing primitives: ```cpp // Basic drawing functions renderer.clearScreen(); // Clear display buffer renderer.drawText(fontId, x, y, text); // Draw text renderer.drawRect(x, y, width, height); // Draw rectangle outline renderer.fillRect(x, y, width, height); // Fill rectangle renderer.drawCenteredText(fontId, y, text); // Center text horizontally renderer.displayBuffer(); // Update physical display // Screen dimensions int width = renderer.getScreenWidth(); // 480 in portrait int height = renderer.getScreenHeight(); // 800 in portrait ``` ### Font System Multiple font families are available (Bookerly, NotoSans, OpenDyslexic) in various sizes. Fonts are registered in `main.cpp`: ```cpp // Font registration example renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily); ``` Font IDs are defined in `src/fontIds.h`. ### Display Refresh Optimization Due to e-paper characteristics: - Use `renderer.displayBuffer()` sparingly (causes screen refresh) - Partial refreshes are handled automatically by the SDK - Full refresh frequency is configurable in settings ## Button Input Handling ### Hardware Button Mapping The Xteink X4 has: - **Front buttons**: 4 buttons (Back, Confirm, Left, Right) - **Side buttons**: 2 buttons (Up/Down for page turning) - **Power button**: Separate power/sleep control ### Input Manager System Two-layer input system: 1. **`InputManager`** (OpenX4 SDK): Raw button state 2. **`MappedInputManager`**: Configurable button mapping ### Button Configuration Button layouts are configurable in settings: - **Front button layout**: Back/Confirm/Left/Right can be rearranged - **Side button layout**: Page up/down can be swapped ### Using MappedInputManager ```cpp // In Activity constructor MappedInputManager& mappedInput; // Check button states if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { // Handle confirm press } if (mappedInput.isPressed(MappedInputManager::Button::Power)) { // Power button currently held } // Get button labels for UI auto labels = mappedInput.mapLabels("Back", "Select", "Prev", "Next"); ``` ### Available Button Constants ```cpp MappedInputManager::Button::Back MappedInputManager::Button::Confirm MappedInputManager::Button::Left MappedInputManager::Button::Right MappedInputManager::Button::Up MappedInputManager::Button::Down MappedInputManager::Button::Power MappedInputManager::Button::PageBack MappedInputManager::Button::PageForward ``` ### Power Button Behavior - **Short press**: Configurable (ignore, sleep, or page turn) - **Long press** (>400ms): Always enters deep sleep - **Wake from sleep**: Requires long press verification ## Activity System ### Creating a New Activity 1. Create header file in appropriate `src/activities/` subdirectory 2. Inherit from `Activity` base class 3. Implement required methods ### Activity Template ```cpp // MyActivity.h #pragma once #include "activities/Activity.h" class MyActivity : public Activity { public: MyActivity(GfxRenderer& renderer, MappedInputManager& mappedInput); void onEnter() override; void onExit() override; void loop() override; private: void render(); }; // MyActivity.cpp MyActivity::MyActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) : Activity("MyActivity", renderer, mappedInput) {} void MyActivity::onEnter() { Activity::onEnter(); // Initialization code } void MyActivity::onExit() { // Cleanup code Activity::onExit(); } void MyActivity::loop() { // Handle input if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { // Handle action } // Update and render if needed render(); } void MyActivity::render() { renderer.clearScreen(); // Drawing code renderer.displayBuffer(); } ``` ### Activity Navigation Activities are managed in `main.cpp`: ```cpp // Switch to new activity exitActivity(); // Clean up current enterNewActivity(new MyActivity(renderer, mappedInputManager)); ``` ## Memory Management ### Constraints - ESP32-C3 has ~380KB usable RAM - Display buffer: ~45KB (480x800 / 8 bits per pixel) - Must cache EPUB data to SD card ### Caching Strategy EPUB content is cached to `.crosspoint/` directory on SD card: - Chapter data cached after first load - Book metadata stored in `book.bin` - Reading progress in `progress.bin` - Cover images in `cover.bmp` ### Best Practices 1. **Avoid dynamic allocations in loop()** 2. **Use stack allocation when possible** 3. **Free resources in `onExit()`** 4. **Monitor memory usage** (serial output shows free heap) ## Configuration System ### CrossPointSettings Singleton class for user settings: - Display settings (orientation, refresh rate) - Reader settings (font, spacing, alignment) - Button layouts - Sleep timeouts ```cpp // Access settings SETTINGS.fontFamily; // Current font family SETTINGS.getSleepTimeoutMs(); // Auto-sleep timeout SETTINGS.getPowerButtonDuration(); // Long-press duration ``` ### CrossPointState Singleton for runtime state: - Currently open EPUB path - Last sleep screen image index ## Development Workflow ### Building and Flashing ```bash # Build project pio run # Flash to device pio run --target upload # Monitor serial output pio device monitor ``` ### Debugging 1. **Serial Monitor**: View logs at 115200 baud 2. **Memory Monitoring**: Heap usage printed every 10 seconds 3. **Error Handling**: Full-screen error activities for SD card failures ### Common Pitfalls 1. **Memory Exhaustion**: Watch for heap fragmentation 2. **Display Artifacts**: Ensure proper buffer management 3. **Button Debouncing**: Handled by InputManager 4. **SD Card Access**: Use `SdMan` helper for file operations ## Testing ### Manual Testing Areas 1. **Button mappings** in different layout configurations 2. **Memory usage** with large EPUB files 3. **Sleep/wake cycles** with power button 4. **SD card** removal/insertion during operation 5. **Battery indicator** accuracy ### Automated Testing - PlatformIO test framework available - Run tests: `pio test` - Test directory: `test/` ## Contributing Guidelines ### Code Style - Follow existing code patterns - Use `clang-format` for formatting - Add serial logging for major operations - Include error handling for SD card operations ### Adding Features 1. Check [ideas discussion board](https://github.com/daveallie/crosspoint-reader/discussions/categories/ideas) 2. Create feature branch 3. Implement with memory constraints in mind 4. Test on actual hardware 5. Submit PR with clear description ### Performance Considerations - Minimize display refreshes - Cache computed values (text measurements, etc.) - Use `skipLoopDelay()` for activities needing fast response (webserver) - Implement `preventAutoSleep()` for activities with background work ## Hardware Reference ### Pin Definitions (from main.cpp) ```cpp // Display SPI #define EPD_SCLK 8 #define EPD_MOSI 10 #define EPD_CS 21 #define EPD_DC 4 #define EPD_RST 5 #define EPD_BUSY 6 // SD Card SPI #define SD_SPI_MISO 7 // Battery monitoring #define BAT_GPIO0 0 // USB detection #define UART0_RXD 20 ``` ### Power Management - Deep sleep mode for power saving - Wake on power button press (GPIO low) - Auto-sleep after configurable inactivity period - Battery monitoring via ADC on GPIO0 ## Useful Resources - [OpenX4 SDK Documentation](https://github.com/open-x4-epaper/community-sdk) - [PlatformIO Documentation](https://docs.platformio.org/) - [ESP32-C3 Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/) - [EPUB Format Specification](https://www.w3.org/publishing/epub3/epub-overview.html) ## Getting Help - Check existing issues and discussions - Review `docs/` directory for internal documentation - Test with actual Xteink X4 hardware when possible - Enable serial logging for debugging