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