dreader-hal/README.md
2025-11-10 18:06:11 +01:00

501 lines
15 KiB
Markdown

# DReader Hardware Abstraction Layer (HAL)
Hardware abstraction layer for the DReader e-reader application, providing a unified interface for e-ink displays, touch input, sensors, and power management.
## Features
- **E-ink Display**: IT8951 controller support with optimized refresh modes
- **Touch Input**: FT5xx6 capacitive touch with gesture detection (tap, swipe, long press, pinch)
- **Touch Calibration**: Multi-point calibration for precise touch-to-display alignment
- **Orientation Sensing**: BMA400 accelerometer for auto-rotation
- **Timekeeping**: PCF8523 RTC with battery backup and alarms
- **Power Monitoring**: INA219 voltage/current/power monitoring for battery management
- **Async-First**: All operations use async/await for non-blocking I/O
- **Polling Mode**: All sensors use polling (no interrupts) for simplicity
- **Testable**: Virtual display mode for development without hardware
## Hardware Components
| Component | Purpose | Driver | Interface |
|-----------|---------|--------|-----------|
| IT8951 | E-ink display controller | IT8951 | SPI |
| FT5316 | Capacitive touch panel | PyFTtxx6 | I2C |
| BMA400 | 3-axis accelerometer | PyBMA400 | I2C |
| PCF8523 | Real-time clock | PyPCF8523 | I2C |
| INA219 | Power monitor | pi_ina219 | I2C |
## Installation
### From Source
```bash
cd dreader-hal
pip install -e .
```
### For Raspberry Pi
```bash
pip install -e .[rpi]
```
### Dependencies
External driver libraries are included in `external/`:
- `IT8951` - E-ink display controller
- `PyFTtxx6` - Touch controller (our repo, can modify)
- `PyBMA400` - Accelerometer (our repo, can modify)
- `PyPCF8523` - RTC (our repo, can modify)
- `pi_ina219` - Power monitor
## Quick Start
### Basic Usage
```python
import asyncio
from dreader_hal import EReaderDisplayHAL, GestureType
from PIL import Image
async def main():
# Create HAL
hal = EReaderDisplayHAL(
width=800,
height=1200,
vcom=-2.0, # Check your device label
virtual_display=False, # Set True for testing
)
# Initialize
await hal.initialize()
# Display an image
image = Image.open("my_page.png")
await hal.show_image(image)
# Handle touch events
while True:
event = await hal.get_touch_event()
if event:
if event.gesture == GestureType.SWIPE_LEFT:
print("Next page!")
elif event.gesture == GestureType.SWIPE_RIGHT:
print("Previous page!")
elif event.gesture == GestureType.TAP:
print(f"Tapped at ({event.x}, {event.y})")
# Cleanup
await hal.cleanup()
if __name__ == "__main__":
asyncio.run(main())
```
### Testing Without Hardware
Use virtual display mode for development:
```python
hal = EReaderDisplayHAL(
width=800,
height=1200,
virtual_display=True, # Uses Tkinter window
enable_orientation=False, # No accelerometer
enable_rtc=False, # No RTC
enable_power_monitor=False, # No INA219
)
```
## Architecture
```
┌─────────────────────────────────────────────────────────┐
│ DReaderApplication │
│ (from dreader-application project) │
└──────────────────┬──────────────────────────────────────┘
│ Uses DisplayHAL interface
┌─────────────────────────────────────────────────────────┐
│ EReaderDisplayHAL │
│ (Main HAL implementation) │
│ │
│ Methods: │
│ • async show_image(PIL.Image) │
│ • async get_touch_event() -> TouchEvent │
│ • async set_brightness(level: int) │
│ • async get_battery_level() -> float │
│ • async get_power_stats() -> PowerStats │
└──────────────────┬──────────────────────────────────────┘
│ Orchestrates
┌─────────────────────────────────────────────────────────┐
│ Hardware Component Wrappers │
│ │
│ IT8951DisplayDriver - E-ink rendering │
│ FT5xx6TouchDriver - Touch + gesture detection │
│ BMA400OrientationSensor - Device orientation │
│ PCF8523RTC - Timekeeping & alarms │
│ INA219PowerMonitor - Battery monitoring │
└─────────────────────────────────────────────────────────┘
```
## Gesture Detection
The HAL implements comprehensive gesture detection:
| Gesture | Detection Criteria | Usage |
|---------|-------------------|-------|
| **TAP** | < 30px movement, < 300ms | Select links, buttons |
| **LONG_PRESS** | < 30px movement, >= 500ms | Context menu, definitions |
| **SWIPE_LEFT** | Horizontal, dx < -30px | Next page |
| **SWIPE_RIGHT** | Horizontal, dx > 30px | Previous page |
| **SWIPE_UP** | Vertical, dy < -30px | Open navigation |
| **SWIPE_DOWN** | Vertical, dy > 30px | Open settings |
| **PINCH_IN** | Two-finger distance decrease | Decrease font size |
| **PINCH_OUT** | Two-finger distance increase | Increase font size |
## E-ink Optimization
The display driver implements several e-ink optimizations:
### Refresh Modes
- **FAST** (DU mode): ~200ms, for text/UI updates
- **QUALITY** (GC16 mode): ~1000ms, for images
- **FULL** (INIT mode): Full refresh to clear ghosting
- **AUTO**: Automatically chooses based on content
### Power Saving
**Automatic Display Sleep**: The display automatically enters sleep mode after each update to save power. E-ink displays only need power during refresh, not for static content. This significantly extends battery life.
To disable auto-sleep (e.g., for rapid successive updates):
```python
hal = EReaderDisplayHAL(
width=800,
height=1200,
auto_sleep_display=False # Keep display awake
)
```
### Automatic Ghosting Prevention
Full refresh every 10 pages to clear accumulated ghosting artifacts.
### Dithering
Floyd-Steinberg dithering improves grayscale image quality on e-ink.
## API Reference
### EReaderDisplayHAL
Main HAL class implementing the DisplayHAL interface.
#### Constructor
```python
EReaderDisplayHAL(
width: int = 800,
height: int = 1200,
vcom: float = -2.0,
spi_hz: int = 24_000_000,
virtual_display: bool = False,
auto_sleep_display: bool = True,
enable_orientation: bool = True,
enable_rtc: bool = True,
enable_power_monitor: bool = True,
shunt_ohms: float = 0.1,
battery_capacity_mah: float = 3000,
)
```
#### Methods
**Core Methods (from DisplayHAL spec):**
- `async initialize()` - Initialize all hardware
- `async cleanup()` - Cleanup resources
- `async show_image(image: Image.Image)` - Display image on screen
- `async get_touch_event() -> Optional[TouchEvent]` - Get touch event with gesture
- `async set_brightness(level: int)` - Set brightness 0-10
**Extended Methods:**
Power Management:
- `async get_battery_level() -> float` - Get battery % (0-100)
- `async get_power_stats() -> PowerStats` - Get detailed power stats
- `async is_low_battery(threshold: float = 20.0) -> bool` - Check low battery
- `async set_low_power_mode(enabled: bool)` - Enable/disable low power mode
Orientation:
- `async enable_orientation_monitoring()` - Start orientation monitoring
- `async disable_orientation_monitoring()` - Stop orientation monitoring
- `current_orientation` property - Get current device orientation
RTC (Real-Time Clock):
- `async get_datetime() -> time.struct_time` - Get current date/time from RTC
- `async set_datetime(dt: time.struct_time)` - Set RTC date/time
- `async set_alarm(minute: int, hour: int)` - Set RTC alarm (minute precision)
### Types
```python
from dreader_hal import GestureType, TouchEvent, PowerStats, Orientation
# Touch event
event = TouchEvent(
gesture=GestureType.TAP,
x=400,
y=600,
timestamp_ms=1234567890.0
)
# Power statistics
stats = PowerStats(
voltage=3.7,
current=150.0,
power=555.0,
battery_percent=85.0,
time_remaining=180, # minutes
is_charging=False
)
# Orientation
orientation = Orientation.PORTRAIT_0 # or LANDSCAPE_90, etc.
```
## Real-Time Clock (RTC)
The HAL includes a PCF8523 RTC for accurate timekeeping with battery backup:
### Features
- **Battery-backed timekeeping** - Maintains time when device is off
- **Alarm support** - Minute-precision alarms for wake events
- **Auto-sync** - Syncs with system time after power loss
- **Calibration** - Clock accuracy adjustment support
### Usage
```python
import time
# Get current time
current_time = await hal.get_datetime()
print(time.strftime("%Y-%m-%d %H:%M:%S", current_time))
# Set time
new_time = time.localtime() # Use current system time
await hal.set_datetime(new_time)
# Set alarm for 7:30 AM
await hal.set_alarm(hour=7, minute=30)
```
See [examples/rtc_demo.py](examples/rtc_demo.py) for a complete example.
## Touchscreen Calibration
The HAL includes touchscreen calibration to align touch coordinates with display pixels:
```bash
# Run calibration (displays touch targets)
cd examples
python3 calibrate_touch.py
# Test calibration
python3 test_calibration.py
```
Calibration uses affine transformation with least-squares fitting to achieve sub-5-pixel accuracy. See **[CALIBRATION.md](CALIBRATION.md)** for detailed documentation.
## Examples
See the `examples/` directory for complete examples:
- **[simple_display.py](examples/simple_display.py)** - Basic display and touch demo
- **[battery_monitor.py](examples/battery_monitor.py)** - Power monitoring demo
- **[rtc_demo.py](examples/rtc_demo.py)** - RTC timekeeping and alarms demo
- **[calibrate_touch.py](examples/calibrate_touch.py)** - Touchscreen calibration utility
- **[test_calibration.py](examples/test_calibration.py)** - Test calibration accuracy
Run examples:
```bash
cd examples
python3 simple_display.py
```
## Hardware Setup
### Connections
All components connect via I2C and SPI:
| Component | Interface | Default Address | Pins |
|-----------|-----------|-----------------|------|
| IT8951 | SPI | N/A | MOSI, MISO, SCK, CS, HRDY, RESET |
| FT5316 | I2C | 0x38 | SDA, SCL |
| BMA400 | I2C | 0x14 | SDA, SCL |
| PCF8523 | I2C | 0x68 | SDA, SCL |
| INA219 | I2C | 0x40 | SDA, SCL |
### Enable I2C and SPI
On Raspberry Pi:
```bash
sudo raspi-config
# Navigate to: Interface Options > I2C > Enable
# Navigate to: Interface Options > SPI > Enable
```
### Verify Devices
```bash
# Check I2C devices
i2cdetect -y 1
# Expected output shows addresses: 0x14, 0x38, 0x40, 0x68
```
## Testing
The project uses pytest for testing:
```bash
# Install dev dependencies
pip install -e .[dev]
# Run tests
pytest tests/
# Run with coverage
pytest --cov=dreader_hal tests/
```
Note: Hardware-specific tests require actual devices connected. Mock tests run without hardware.
## Development
### Project Structure
```
dreader-hal/
├── src/
│ └── dreader_hal/
│ ├── __init__.py
│ ├── hal.py # DisplayHAL abstract class
│ ├── types.py # Type definitions
│ ├── gesture.py # Gesture detection
│ ├── ereader_hal.py # Main HAL implementation
│ ├── display/
│ │ └── it8951.py # Display driver wrapper
│ ├── touch/
│ │ └── ft5xx6.py # Touch driver wrapper
│ ├── sensors/
│ │ └── bma400.py # Accelerometer wrapper
│ ├── rtc/
│ │ └── pcf8523.py # RTC wrapper
│ └── power/
│ └── ina219.py # Power monitor wrapper
├── external/ # External driver libraries
│ ├── IT8951/
│ ├── PyFTtxx6/ # Our repo - can modify
│ ├── PyBMA400/ # Our repo - can modify
│ ├── PyPCF8523/ # Our repo - can modify
│ └── pi_ina219/
├── examples/ # Example scripts
├── tests/ # Test suite
├── setup.py
├── requirements.txt
└── README.md
```
### Adding Tests
Since PyBMA400, PyFTtxx6, and PyPCF8523 are our repos, we can add tests directly to them as needed.
For HAL-level tests, use the `tests/` directory:
```python
# tests/unit/test_hal.py
import pytest
from dreader_hal import EReaderDisplayHAL
@pytest.mark.asyncio
async def test_hal_initialization():
hal = EReaderDisplayHAL(virtual_display=True)
await hal.initialize()
assert hal._initialized
await hal.cleanup()
```
## Performance
Target performance metrics (from HAL spec):
| Metric | Target | Critical |
|--------|--------|----------|
| Image display latency | < 100ms | < 500ms |
| Touch event latency | < 50ms | < 200ms |
| E-ink refresh (fast) | < 200ms | < 500ms |
| E-ink refresh (full) | < 1000ms | < 2000ms |
## Troubleshooting
### Display not working
- Check SPI is enabled: `ls /dev/spi*`
- Verify connections (MOSI, MISO, SCK, CS, HRDY, RESET)
- Check VCOM voltage matches your device
### Touch not detecting
- Check I2C device: `i2cdetect -y 1` (should see 0x38)
- Verify SDA/SCL connections
- Check polling interval (default 10ms)
### Power monitor errors
- Verify shunt resistor value (default 0.)
- Check I2C address (default 0x40)
- Ensure max_expected_amps is appropriate
## Contributing
Contributions are welcome! Please:
1. Follow PEP 8 style guidelines
2. Add tests for new features
3. Update documentation
4. Submit pull requests to our Gitea repo
## License
MIT License - see LICENSE file for details.
## Credits
- **IT8951 Driver**: [GregDMeyer/IT8951](https://github.com/GregDMeyer/IT8951)
- **FT5xx6 Driver**: PyFTtxx6 (our repo)
- **BMA400 Driver**: PyBMA400 (our repo)
- **PCF8523 Driver**: PyPCF8523 (our repo)
- **INA219 Driver**: [chrisb2/pi_ina219](https://github.com/chrisb2/pi_ina219)
## Related Projects
- **[dreader-application](https://gitea.tourolle.paris/dtourolle/dreader-application)** - Main e-reader application
- **[pyWebLayout](https://github.com/jneug/pyWebLayout)** - HTML/EPUB rendering engine
## Support
For issues and questions:
- File issues on [Gitea](https://gitea.tourolle.paris/dtourolle/dreader-hal/issues)
- Check documentation at [Wiki](https://gitea.tourolle.paris/dtourolle/dreader-hal/wiki)