Files
crosspoint-reader/README_dev.md
2026-01-24 20:51:08 -07:00

9.7 KiB

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

// 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:

// 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:

// 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

// 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

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

// 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:

// 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
// 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

# 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
  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)

// 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

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