501 lines
15 KiB
Markdown
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.1Ω)
|
|
- 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)
|