add dev readme
This commit is contained in:
373
README_dev.md
Normal file
373
README_dev.md
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
Reference in New Issue
Block a user