2025-11-10 18:06:11 +01:00
2025-11-10 18:10:22 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:10:22 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00
2025-11-10 18:06:11 +01:00

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

cd dreader-hal
pip install -e .

For Raspberry Pi

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

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:

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

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

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

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

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 for a complete example.

Touchscreen Calibration

The HAL includes touchscreen calibration to align touch coordinates with display pixels:

# 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 for detailed documentation.

Examples

See the examples/ directory for complete examples:

Run examples:

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:

sudo raspi-config
# Navigate to: Interface Options > I2C > Enable
# Navigate to: Interface Options > SPI > Enable

Verify Devices

# Check I2C devices
i2cdetect -y 1

# Expected output shows addresses: 0x14, 0x38, 0x40, 0x68

Testing

The project uses pytest for testing:

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

# 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
  • FT5xx6 Driver: PyFTtxx6 (our repo)
  • BMA400 Driver: PyBMA400 (our repo)
  • PCF8523 Driver: PyPCF8523 (our repo)
  • INA219 Driver: chrisb2/pi_ina219

Support

For issues and questions:

  • File issues on Gitea
  • Check documentation at Wiki
Description
No description provided
Readme MIT 104 KiB
Languages
Python 98%
Shell 2%