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

374 lines
9.7 KiB
Markdown

# 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