# DReader Hardware Abstraction Layer (HAL) Implementation Specification

**Version**: 1.0
**Date**: 2025-11-09
**Target**: Hardware driver developers implementing platform-specific HALs

---

## Table of Contents

1. [Overview](#1-overview)
2. [Architecture](#2-architecture)
3. [HAL Interface Specification](#3-hal-interface-specification)
4. [Touch Event & Gesture System](#4-touch-event--gesture-system)
5. [Implementation Requirements](#5-implementation-requirements)
6. [Testing & Validation](#6-testing--validation)
7. [Reference Implementation](#7-reference-implementation)
8. [Platform-Specific Considerations](#8-platform-specific-considerations)
9. [Example Implementations](#9-example-implementations)

---

## 1. Overview

### 1.1 Purpose

The DReader HAL provides a **platform-independent abstraction** for:
- **Display output**: Rendering PIL images to hardware screen
- **Touch input**: Capturing touch/mouse events and converting to gestures
- **Hardware control**: Brightness, power management, etc.

### 1.2 Design Goals

- **Portability**: Same application code runs on different hardware platforms
- **Async-first**: All operations are async for non-blocking I/O
- **Testability**: Easy desktop testing before deploying to device
- **Simplicity**: Minimal interface with clear contracts

### 1.3 Key Responsibilities

**What the HAL MUST do:**
- Display PIL Images on physical screen
- Detect touch events and classify gestures
- Return standardized `TouchEvent` objects
- Handle platform-specific initialization/cleanup

**What the HAL does NOT do:**
- Application logic (handled by `EbookReader` and `DReaderApplication`)
- Gesture routing (handled by `GestureRouter`)
- State management (handled by `StateManager`)
- Content rendering (handled by pyWebLayout)

---

## 2. Architecture

### 2.1 System Layers

```
┌─────────────────────────────────────────────────────────┐
│          DReaderApplication (main.py)                   │
│  - State management, mode switching, event routing      │
└───────────────────────┬─────────────────────────────────┘
                        │
                        │ Uses DisplayHAL interface
                        ↓
┌─────────────────────────────────────────────────────────┐
│          DisplayHAL (Abstract Interface)                │
│  - show_image(PIL.Image)                                │
│  - get_touch_event() -> TouchEvent                      │
│  - set_brightness(int)                                  │
│  - initialize() / cleanup()                             │
└───────────────────────┬─────────────────────────────────┘
                        │
                        │ Implemented by
                        ↓
┌─────────────────────────────────────────────────────────┐
│       Platform-Specific HAL Implementation              │
│                                                          │
│  Examples:                                              │
│  • PygameDisplayHAL (Desktop/Testing)                   │
│  • EinkDisplayHAL (E-ink devices)                       │
│  • KoboDisplayHAL (Kobo readers)                        │
│  • RemarkableDisplayHAL (reMarkable tablets)            │
│  • [Your Custom HAL]                                    │
└───────────────────────┬─────────────────────────────────┘
                        │
                        │ Controls
                        ↓
┌─────────────────────────────────────────────────────────┐
│           Hardware Drivers                              │
│  • Framebuffer / Display controller                     │
│  • Touch sensor / Digitizer                             │
│  • Backlight / Frontlight                               │
│  • Power management                                     │
└─────────────────────────────────────────────────────────┘
```

### 2.2 Event Flow

```
Hardware Touch Sensor
        ↓
    HAL Driver
        ↓
  Touch Detection
        ↓
 Gesture Classification ← HAL responsibility
        ↓
    TouchEvent
        ↓
DReaderApplication.handle_touch()
        ↓
   GestureRouter
        ↓
 Gesture Handlers
        ↓
  GestureResponse
        ↓
    Display Update
        ↓
HAL.show_image()
        ↓
  Hardware Display
```

---

## 3. HAL Interface Specification

### 3.1 Base Interface: `DisplayHAL`

**File**: `dreader/hal.py`

```python
from abc import ABC, abstractmethod
from typing import Optional
from PIL import Image
from dreader.gesture import TouchEvent

class DisplayHAL(ABC):
    """Abstract interface for display and input hardware"""
```

### 3.2 Required Methods

#### 3.2.1 `show_image(image: PIL.Image.Image)` ⭐ CRITICAL

**Purpose**: Display a PIL Image on the physical screen

**Contract**:
```python
async def show_image(self, image: Image.Image):
    """
    Display a PIL Image on the screen.

    Args:
        image: PIL Image to display (typically RGB mode)

    Requirements:
        - MUST handle image format conversion if needed
        - MUST handle resolution mismatch (scale/crop)
        - MUST update physical display hardware
        - SHOULD complete within 100ms for responsiveness
        - SHOULD support e-ink optimization if applicable

    Image Specifications:
        - Format: Usually RGB (may be 'L' for grayscale)
        - Size: Typically matches page_size config (e.g., 800x1200)
        - Color depth: 8-bit per channel RGB
    """
    pass
```

**Implementation Notes**:
- **E-ink devices**: Convert to grayscale, apply dithering, use appropriate refresh mode
- **Color displays**: May need RGB↔BGR conversion depending on driver
- **Framebuffer**: May need to write directly to `/dev/fb0` or use platform API
- **Performance**: Cache converted images if format conversion is expensive

**Example (Pseudocode)**:
```python
async def show_image(self, image: Image.Image):
    # Convert to device format
    if self.is_eink:
        image = image.convert('L')  # Grayscale
        image = apply_dithering(image)

    # Resize if needed
    if image.size != (self.width, self.height):
        image = image.resize((self.width, self.height), Image.LANCZOS)

    # Write to framebuffer or display API
    self.framebuffer.write(image)

    # Refresh display
    self.display_controller.refresh()
```

---

#### 3.2.2 `get_touch_event() -> Optional[TouchEvent]` ⭐ CRITICAL

**Purpose**: Wait for and return the next touch event with gesture classification

**Contract**:
```python
async def get_touch_event(self) -> Optional[TouchEvent]:
    """
    Get the next touch event from hardware.

    Returns:
        TouchEvent if available, None if no event

    Requirements:
        - MUST classify gesture type (tap, swipe, long press, etc.)
        - MUST return None immediately if no event (non-blocking)
        - SHOULD detect gestures accurately with sensible thresholds
        - MUST provide accurate pixel coordinates
        - MAY implement timeouts for responsiveness

    Gesture Detection Thresholds (Recommended):
        - Tap: < 30px movement, < 300ms duration
        - Long press: < 30px movement, >= 500ms duration
        - Swipe: >= 30px movement, < 500ms duration
        - Drag: >= 30px movement, sustained motion
        - Pinch: Two-finger distance change

    Coordinate System:
        - Origin (0, 0) = top-left corner
        - X increases right
        - Y increases down
        - Range: (0, 0) to (width-1, height-1)
    """
    pass
```

**TouchEvent Format**:
```python
@dataclass
class TouchEvent:
    gesture: GestureType         # Required: gesture classification
    x: int                        # Required: primary touch X
    y: int                        # Required: primary touch Y
    x2: Optional[int] = None      # Optional: secondary touch X (pinch)
    y2: Optional[int] = None      # Optional: secondary touch Y (pinch)
    timestamp_ms: float = 0       # Optional: timestamp
```

**Implementation Pattern**:
```python
async def get_touch_event(self) -> Optional[TouchEvent]:
    # Read from touch hardware
    touch_data = await self.read_touch_sensor()

    if not touch_data:
        return None

    # Track touch state
    if touch_data.is_down:
        self._touch_start = (touch_data.x, touch_data.y)
        self._touch_start_time = time.time()

    elif touch_data.is_up:
        # Calculate gesture
        dx = touch_data.x - self._touch_start[0]
        dy = touch_data.y - self._touch_start[1]
        distance = (dx**2 + dy**2) ** 0.5
        duration = time.time() - self._touch_start_time

        # Classify gesture
        if distance < 30:
            if duration >= 0.5:
                gesture = GestureType.LONG_PRESS
            else:
                gesture = GestureType.TAP
        else:
            # Swipe direction
            if abs(dx) > abs(dy):
                gesture = GestureType.SWIPE_LEFT if dx < 0 else GestureType.SWIPE_RIGHT
            else:
                gesture = GestureType.SWIPE_UP if dy < 0 else GestureType.SWIPE_DOWN

        return TouchEvent(gesture, touch_data.x, touch_data.y)

    return None
```

---

#### 3.2.3 `set_brightness(level: int)`

**Purpose**: Control display brightness/frontlight

**Contract**:
```python
async def set_brightness(self, level: int):
    """
    Set display brightness.

    Args:
        level: Brightness level (0-10)
            0 = dimmest (may be off)
            10 = brightest

    Requirements:
        - MUST accept range 0-10
        - SHOULD map to hardware's native range
        - MAY be no-op if brightness control unavailable
        - SHOULD persist setting if supported by hardware
    """
    pass
```

**Implementation Notes**:
- Map 0-10 to hardware range (e.g., 0-255)
- E-ink frontlight: Control LED PWM or similar
- LCD backlight: Control via sysfs or platform API
- Desktop: May be no-op or control window alpha

---

#### 3.2.4 `initialize()` (Optional)

**Purpose**: One-time hardware initialization

**Contract**:
```python
async def initialize(self):
    """
    Initialize the display hardware.

    Called once before application starts.

    Typical Tasks:
        - Open framebuffer device
        - Initialize touch sensor
        - Configure display controller
        - Set default brightness
        - Calibrate touch screen
        - Clear screen to white/black
    """
    pass
```

---

#### 3.2.5 `cleanup()` (Optional)

**Purpose**: Hardware cleanup on shutdown

**Contract**:
```python
async def cleanup(self):
    """
    Clean up display hardware resources.

    Called during application shutdown.

    Typical Tasks:
        - Close file descriptors
        - Restore default display state
        - Power down display
        - Release hardware locks
    """
    pass
```

---

#### 3.2.6 `show_message(message: str, duration: float)` (Optional)

**Purpose**: Display simple text messages (loading screens, errors)

**Contract**:
```python
async def show_message(self, message: str, duration: float = 2.0):
    """
    Display a text message.

    Args:
        message: Text to display
        duration: How long to show (seconds)

    Default implementation provided (creates PIL Image with text).
    Override for platform-specific message display.
    """
    pass
```

---

### 3.3 Extended Interface: `EventLoopHAL`

**Purpose**: For platforms that provide their own event loop (Pygame, Qt, Tkinter)

**File**: `dreader/hal.py`

```python
class EventLoopHAL(DisplayHAL):
    """Extended HAL that provides its own event loop"""

    @abstractmethod
    async def run_event_loop(self, app):
        """
        Run the platform's event loop.

        Args:
            app: DReaderApplication instance

        Typical Flow:
            1. await self.initialize()
            2. await app.start()
            3. Enter event loop:
                a. Get events
                b. await app.handle_touch(event)
                c. Handle quit/close
            4. await app.shutdown()
            5. await self.cleanup()
        """
        pass
```

**Use Case**: When the platform requires control of the main loop (e.g., Pygame, Qt)

---

## 4. Touch Event & Gesture System

### 4.1 GestureType Enumeration

**File**: `dreader/gesture.py`

All gestures that HAL must be able to detect:

```python
class GestureType(Enum):
    TAP = "tap"                    # Single finger tap
    LONG_PRESS = "long_press"      # Hold for 500ms+
    SWIPE_LEFT = "swipe_left"      # Horizontal swipe left
    SWIPE_RIGHT = "swipe_right"    # Horizontal swipe right
    SWIPE_UP = "swipe_up"          # Vertical swipe up
    SWIPE_DOWN = "swipe_down"      # Vertical swipe down
    PINCH_IN = "pinch_in"          # Two-finger pinch (zoom out)
    PINCH_OUT = "pinch_out"        # Two-finger spread (zoom in)
    DRAG_START = "drag_start"      # Start dragging
    DRAG_MOVE = "drag_move"        # Continue dragging
    DRAG_END = "drag_end"          # End dragging
```

### 4.2 Gesture Priority & Requirements

#### 4.2.1 Essential Gestures (MUST implement)

| Gesture | Usage | Detection |
|---------|-------|-----------|
| **TAP** | Link/button activation, word selection | < 30px movement, < 300ms |
| **SWIPE_LEFT** | Next page | Horizontal, dx < -30px |
| **SWIPE_RIGHT** | Previous page | Horizontal, dx > 30px |
| **SWIPE_UP** | Open navigation/TOC | Vertical, dy < -30px |
| **SWIPE_DOWN** | Open settings (from top 20%) | Vertical, dy > 30px |

#### 4.2.2 Important Gestures (SHOULD implement)

| Gesture | Usage | Detection |
|---------|-------|-----------|
| **LONG_PRESS** | Word definition, context menu | < 30px movement, >= 500ms |
| **PINCH_OUT** | Increase font size | Two-finger distance increase |
| **PINCH_IN** | Decrease font size | Two-finger distance decrease |

#### 4.2.3 Advanced Gestures (MAY implement)

| Gesture | Usage | Detection |
|---------|-------|-----------|
| **DRAG_START/MOVE/END** | Text selection | Sustained motion >= 30px |

### 4.3 Gesture Detection Algorithm

#### 4.3.1 State Machine

```
IDLE
  ↓ touch_down
TOUCHING
  ↓ timeout(500ms) → LONG_PRESS
  ↓ movement > 30px → MOVING
  ↓ touch_up → TAP
MOVING
  ↓ touch_up → SWIPE_*
  ↓ sustained → DRAG_*
```

#### 4.3.2 Recommended Thresholds

```python
# Distance thresholds
TAP_THRESHOLD = 30          # pixels
SWIPE_MIN_DISTANCE = 30     # pixels
DRAG_THRESHOLD = 30         # pixels

# Time thresholds
LONG_PRESS_DURATION = 0.5   # seconds (500ms)
TAP_MAX_DURATION = 0.3      # seconds (300ms)
SWIPE_MAX_DURATION = 0.5    # seconds

# Direction thresholds
SWIPE_ANGLE_THRESHOLD = 45  # degrees (for horizontal vs vertical)
```

#### 4.3.3 Pseudocode

```python
class GestureDetector:
    def on_touch_down(self, x, y):
        self.start_pos = (x, y)
        self.start_time = time.now()
        self.state = TOUCHING

    def on_touch_move(self, x, y):
        dx = x - self.start_pos[0]
        dy = y - self.start_pos[1]
        distance = sqrt(dx**2 + dy**2)

        if distance > TAP_THRESHOLD and self.state == TOUCHING:
            self.state = MOVING
            # Could emit DRAG_START here

    def on_touch_up(self, x, y):
        dx = x - self.start_pos[0]
        dy = y - self.start_pos[1]
        distance = sqrt(dx**2 + dy**2)
        duration = time.now() - self.start_time

        if distance < TAP_THRESHOLD:
            if duration >= LONG_PRESS_DURATION:
                return GestureType.LONG_PRESS
            else:
                return GestureType.TAP
        else:
            # Determine swipe direction
            if abs(dx) > abs(dy):  # Horizontal
                if dx > 0:
                    return GestureType.SWIPE_RIGHT
                else:
                    return GestureType.SWIPE_LEFT
            else:  # Vertical
                if dy > 0:
                    return GestureType.SWIPE_DOWN
                else:
                    return GestureType.SWIPE_UP
```

### 4.4 TouchEvent Construction

```python
# Example: Creating a TAP event
event = TouchEvent(
    gesture=GestureType.TAP,
    x=450,
    y=320,
    timestamp_ms=time.time() * 1000
)

# Example: Creating a SWIPE event
event = TouchEvent(
    gesture=GestureType.SWIPE_LEFT,
    x=600,  # End position
    y=400,
    timestamp_ms=time.time() * 1000
)

# Example: Creating a PINCH event (two fingers)
event = TouchEvent(
    gesture=GestureType.PINCH_OUT,
    x=400,   # Finger 1
    y=500,
    x2=600,  # Finger 2
    y2=700,
    timestamp_ms=time.time() * 1000
)
```

---

## 5. Implementation Requirements

### 5.1 Mandatory Features

✅ **MUST implement**:
- [ ] `show_image()` - Display PIL Images
- [ ] `get_touch_event()` - Return TouchEvents
- [ ] Basic gesture detection (TAP, SWIPE_LEFT/RIGHT/UP/DOWN)
- [ ] Correct coordinate system (0,0 = top-left)
- [ ] Async/await support for all methods

### 5.2 Recommended Features

⭐ **SHOULD implement**:
- [ ] `initialize()` and `cleanup()` - Proper lifecycle management
- [ ] `set_brightness()` - Brightness control
- [ ] LONG_PRESS gesture
- [ ] PINCH_IN/OUT gestures (if multitouch available)
- [ ] Efficient image format conversion
- [ ] E-ink optimization (partial refresh, dithering)

### 5.3 Optional Features

💡 **MAY implement**:
- [ ] `show_message()` override - Custom loading screens
- [ ] DRAG gestures - Text selection
- [ ] Touch pressure sensitivity
- [ ] Multi-touch tracking
- [ ] Hardware acceleration
- [ ] Custom refresh modes (e-ink A2/DU/GC16)

### 5.4 Performance Targets

| Metric | Target | Critical |
|--------|--------|----------|
| Image display latency | < 100ms | < 500ms |
| Touch event latency | < 50ms | < 200ms |
| Frame rate (color) | 30 FPS | 10 FPS |
| E-ink refresh (fast) | < 200ms | < 500ms |
| E-ink refresh (full) | < 1000ms | < 2000ms |
| Gesture detection accuracy | > 95% | > 80% |

---

## 6. Testing & Validation

### 6.1 Unit Tests

Test each HAL method in isolation:

```python
import pytest
from your_hal import YourDisplayHAL

@pytest.mark.asyncio
async def test_show_image():
    hal = YourDisplayHAL()
    await hal.initialize()

    # Create test image
    img = Image.new('RGB', (800, 1200), color=(255, 0, 0))

    # Should not raise
    await hal.show_image(img)

    await hal.cleanup()

@pytest.mark.asyncio
async def test_gesture_detection():
    hal = YourDisplayHAL()
    await hal.initialize()

    # Simulate tap
    event = await hal.get_touch_event()
    assert event.gesture == GestureType.TAP
    assert 0 <= event.x < hal.width
    assert 0 <= event.y < hal.height
```

### 6.2 Integration Tests

Test with actual DReaderApplication:

```python
from dreader.main import DReaderApplication, AppConfig

async def test_integration():
    hal = YourDisplayHAL()
    config = AppConfig(
        display_hal=hal,
        library_path="./test_books",
        page_size=(800, 1200)
    )
    app = DReaderApplication(config)

    await app.start()

    # Simulate touch
    event = TouchEvent(GestureType.TAP, 400, 600)
    await app.handle_touch(event)

    await app.shutdown()
```

### 6.3 Manual Test Checklist

- [ ] Display shows full-screen images without distortion
- [ ] Tap on link navigates to chapter
- [ ] Swipe left advances page
- [ ] Swipe right goes back page
- [ ] Swipe up opens TOC overlay
- [ ] Swipe down (from top) opens settings
- [ ] Long press on word shows definition
- [ ] Pinch gestures change font size (if supported)
- [ ] Brightness control works (if supported)
- [ ] No memory leaks after 100+ page turns
- [ ] No display artifacts or tearing

### 6.4 Validation Tools

**Debug Logging**:
```python
import logging
logger = logging.getLogger(__name__)

async def show_image(self, image):
    logger.info(f"Displaying image: {image.size}, {image.mode}")
    # ... implementation

async def get_touch_event(self):
    event = # ... detect gesture
    logger.info(f"Gesture detected: {event.gesture} at ({event.x}, {event.y})")
    return event
```

**Touch Coordinate Visualization**:
- Draw red dot at touch coordinates
- Display gesture type as text overlay
- Show swipe trajectory lines

---

## 7. Reference Implementation

### 7.1 PygameDisplayHAL

**File**: `dreader/hal_pygame.py`

The Pygame HAL is a **complete reference implementation** for desktop testing.

**Key features**:
- Mouse → touch event conversion
- Gesture detection algorithm
- Keyboard shortcuts
- Debug logging

**Study this implementation for**:
- Gesture detection state machine
- Coordinate handling
- Event loop structure
- Error handling patterns

### 7.2 Minimal HAL Template

```python
from dreader.hal import DisplayHAL
from dreader.gesture import TouchEvent, GestureType
from PIL import Image
from typing import Optional
import asyncio

class MinimalHAL(DisplayHAL):
    """Minimal HAL implementation template"""

    def __init__(self, width: int = 800, height: int = 1200):
        self.width = width
        self.height = height

        # Gesture tracking
        self._touch_start = None
        self._touch_start_time = 0

    async def initialize(self):
        """Initialize hardware"""
        # TODO: Open framebuffer, initialize touch sensor
        pass

    async def cleanup(self):
        """Cleanup hardware"""
        # TODO: Close file descriptors, cleanup
        pass

    async def show_image(self, image: Image.Image):
        """Display image on screen"""
        # Convert to device format
        if image.mode != 'RGB':
            image = image.convert('RGB')

        # Resize if needed
        if image.size != (self.width, self.height):
            image = image.resize(
                (self.width, self.height),
                Image.Resampling.LANCZOS
            )

        # TODO: Write to framebuffer or display API
        # framebuffer.write(image)

    async def get_touch_event(self) -> Optional[TouchEvent]:
        """Get next touch event"""
        # TODO: Read from touch hardware
        # touch_data = await read_touch_sensor()

        # If no touch, return None
        # if not touch_data:
        #     return None

        # Classify gesture based on touch_down/touch_up
        # See gesture detection algorithm in section 4.3

        pass

    async def set_brightness(self, level: int):
        """Set brightness (0-10)"""
        # Map to hardware range
        hardware_value = int(level * 255 / 10)

        # TODO: Write to brightness control
        # with open('/sys/class/backlight/.../brightness', 'w') as f:
        #     f.write(str(hardware_value))
```

---

## 8. Platform-Specific Considerations

### 8.1 E-Ink Devices (Kobo, Kindle, reMarkable)

#### Display Characteristics:
- Grayscale only (8-bit per pixel)
- Slow refresh (200ms - 1000ms)
- Multiple refresh modes (A2, DU, GC16, GL16)
- Requires dithering for images

#### Optimization Strategies:

**1. Partial Refresh**:
```python
async def show_image(self, image: Image.Image, partial: bool = True):
    if partial:
        # Use fast A2 mode for text-only updates
        self.controller.set_mode('A2')
    else:
        # Full GC16 refresh every N pages
        self.controller.set_mode('GC16')

    self.framebuffer.write(image)
    self.controller.refresh()
```

**2. Dithering**:
```python
from PIL import ImageDraw

def apply_dithering(image: Image.Image) -> Image.Image:
    # Convert to grayscale
    image = image.convert('L')

    # Apply Floyd-Steinberg dithering
    image = image.convert('1', dither=Image.FLOYDSTEINBERG)
    image = image.convert('L')

    return image
```

**3. Refresh Strategy**:
- Text pages: A2 mode (fast, ~200ms)
- Image-heavy pages: GC16 mode (slow, ~1000ms)
- Full refresh every 5-10 pages to clear ghosting

#### Touch Input:
- Often resistive touch (single-point only)
- Lower resolution than display
- May require calibration

**Example: Kobo HAL skeleton**:
```python
class KoboDisplayHAL(DisplayHAL):
    def __init__(self):
        self.fb = open('/dev/fb0', 'wb')
        self.touch = open('/dev/input/event0', 'rb')
        self.width = 1072
        self.height = 1448
        self.refresh_counter = 0

    async def show_image(self, image: Image.Image):
        # Convert to grayscale
        image = image.convert('L')

        # Resize
        image = image.resize((self.width, self.height))

        # Write to framebuffer
        self.fb.write(image.tobytes())

        # Decide refresh mode
        self.refresh_counter += 1
        if self.refresh_counter % 10 == 0:
            # Full refresh every 10 pages
            self._trigger_refresh('GC16')
        else:
            # Fast refresh
            self._trigger_refresh('A2')
```

---

### 8.2 Linux Framebuffer

#### Display:
```python
async def show_image(self, image: Image.Image):
    # Open framebuffer
    with open('/dev/fb0', 'wb') as fb:
        # Convert to RGB565 or RGB888 depending on device
        if self.color_depth == 16:
            # RGB565
            data = image.convert('RGB').tobytes('raw', 'RGB')
            # Convert to RGB565 packed format
            data = self._convert_rgb888_to_rgb565(data)
        else:
            # RGB888
            data = image.convert('RGB').tobytes()

        fb.write(data)
```

#### Touch Input (evdev):
```python
import evdev

async def get_touch_event(self) -> Optional[TouchEvent]:
    device = evdev.InputDevice('/dev/input/event0')

    async for event in device.async_read_loop():
        if event.type == evdev.ecodes.EV_ABS:
            if event.code == evdev.ecodes.ABS_X:
                self.current_x = event.value
            elif event.code == evdev.ecodes.ABS_Y:
                self.current_y = event.value

        elif event.type == evdev.ecodes.EV_KEY:
            if event.code == evdev.ecodes.BTN_TOUCH:
                if event.value == 1:  # Touch down
                    self._on_touch_down(self.current_x, self.current_y)
                else:  # Touch up
                    gesture = self._on_touch_up(self.current_x, self.current_y)
                    if gesture:
                        return TouchEvent(gesture, self.current_x, self.current_y)
```

---

### 8.3 Embedded Systems (Raspberry Pi, etc.)

#### Recommendations:
- Use hardware-accelerated PIL if available (Pillow-SIMD)
- Minimize memory allocations (reuse image buffers)
- Consider using mmap for framebuffer access
- Implement watchdog for crash recovery

```python
import mmap

class EmbeddedHAL(DisplayHAL):
    def __init__(self):
        self.fb_fd = os.open('/dev/fb0', os.O_RDWR)
        self.fb_size = self.width * self.height * 4  # RGB888
        self.fb_mmap = mmap.mmap(
            self.fb_fd,
            self.fb_size,
            mmap.MAP_SHARED,
            mmap.PROT_READ | mmap.PROT_WRITE
        )

    async def show_image(self, image: Image.Image):
        # Write directly to mmap'd framebuffer
        data = image.convert('RGB').tobytes()
        self.fb_mmap.seek(0)
        self.fb_mmap.write(data)
```

---

### 8.4 Desktop Testing (Windows/macOS/Linux)

Use **PygameDisplayHAL** (already provided) or implement similar with:
- **Tkinter**: For simple cross-platform windows
- **Qt (PyQt5/PySide6)**: For advanced GUI features
- **SDL2**: For game-like responsiveness

---

## 9. Example Implementations

### 9.1 Complete Example: Pygame HAL

See `dreader/hal_pygame.py` for full implementation.

**Key sections to study**:
- Lines 158-240: Gesture detection in `get_touch_event()`
- Lines 111-140: Image display in `show_image()`
- Lines 302-354: Event loop in `run_event_loop()`

---

### 9.2 Pseudocode: E-Ink HAL

```python
from dreader.hal import DisplayHAL
from dreader.gesture import TouchEvent, GestureType
from PIL import Image
import time

class EInkHAL(DisplayHAL):
    """E-Ink device HAL with partial refresh support"""

    def __init__(self, width=1072, height=1448):
        self.width = width
        self.height = height
        self.refresh_count = 0

        # Hardware interfaces
        self.framebuffer = None
        self.touch_device = None
        self.frontlight = None

        # Gesture tracking
        self._touch_down_pos = None
        self._touch_down_time = 0

    async def initialize(self):
        """Initialize e-ink display and touch sensor"""
        # Open framebuffer
        self.framebuffer = open('/dev/fb0', 'r+b')

        # Open touch device
        self.touch_device = evdev.InputDevice('/dev/input/event0')

        # Initialize frontlight
        self.frontlight = open('/sys/class/backlight/mxc_mst716/brightness', 'w')

        # Clear screen
        blank = Image.new('L', (self.width, self.height), 255)
        await self.show_image(blank)

    async def cleanup(self):
        """Cleanup hardware resources"""
        if self.framebuffer:
            self.framebuffer.close()
        if self.frontlight:
            self.frontlight.close()

    async def show_image(self, image: Image.Image):
        """Display image with e-ink optimization"""
        # Convert to grayscale
        if image.mode != 'L':
            image = image.convert('L')

        # Resize if needed
        if image.size != (self.width, self.height):
            image = image.resize((self.width, self.height), Image.LANCZOS)

        # Write to framebuffer
        self.framebuffer.seek(0)
        self.framebuffer.write(image.tobytes())

        # Decide refresh mode
        self.refresh_count += 1
        if self.refresh_count % 10 == 0:
            # Full refresh every 10 pages (remove ghosting)
            self._trigger_refresh('FULL')
            await asyncio.sleep(1.0)  # Wait for full refresh
        else:
            # Partial refresh (fast)
            self._trigger_refresh('PARTIAL')
            await asyncio.sleep(0.2)  # Wait for partial refresh

    def _trigger_refresh(self, mode: str):
        """Trigger display refresh"""
        if mode == 'FULL':
            # Device-specific: trigger full GC16 refresh
            # Example: ioctl(MXCFB_SEND_UPDATE, waveform=GC16)
            pass
        else:
            # Device-specific: trigger partial A2 refresh
            # Example: ioctl(MXCFB_SEND_UPDATE, waveform=A2)
            pass

    async def get_touch_event(self) -> Optional[TouchEvent]:
        """Read touch events from evdev"""
        current_x = 0
        current_y = 0

        # Read events (with timeout)
        try:
            events = await asyncio.wait_for(
                self._read_touch_events(),
                timeout=0.1
            )
        except asyncio.TimeoutError:
            return None

        for event in events:
            if event.type == evdev.ecodes.EV_ABS:
                if event.code == evdev.ecodes.ABS_X:
                    current_x = event.value
                elif event.code == evdev.ecodes.ABS_Y:
                    current_y = event.value

            elif event.type == evdev.ecodes.EV_KEY:
                if event.code == evdev.ecodes.BTN_TOUCH:
                    if event.value == 1:  # Touch down
                        self._touch_down_pos = (current_x, current_y)
                        self._touch_down_time = time.time()

                    else:  # Touch up
                        if not self._touch_down_pos:
                            continue

                        # Calculate gesture
                        dx = current_x - self._touch_down_pos[0]
                        dy = current_y - self._touch_down_pos[1]
                        distance = (dx**2 + dy**2) ** 0.5
                        duration = time.time() - self._touch_down_time

                        # Classify
                        if distance < 30:
                            if duration >= 0.5:
                                gesture = GestureType.LONG_PRESS
                            else:
                                gesture = GestureType.TAP
                        else:
                            if abs(dx) > abs(dy):
                                gesture = GestureType.SWIPE_LEFT if dx < 0 else GestureType.SWIPE_RIGHT
                            else:
                                gesture = GestureType.SWIPE_UP if dy < 0 else GestureType.SWIPE_DOWN

                        self._touch_down_pos = None
                        return TouchEvent(gesture, current_x, current_y)

        return None

    async def _read_touch_events(self):
        """Helper to read touch events with async"""
        events = []
        async for event in self.touch_device.async_read_loop():
            events.append(event)
            if event.type == evdev.ecodes.EV_SYN:
                break  # End of event batch
        return events

    async def set_brightness(self, level: int):
        """Set frontlight brightness"""
        # Map 0-10 to hardware range (e.g., 0-100)
        value = level * 10
        self.frontlight.seek(0)
        self.frontlight.write(str(value))
        self.frontlight.flush()
```

---

### 9.3 Usage Example

```python
import asyncio
from your_hal import YourDisplayHAL
from dreader.main import DReaderApplication, AppConfig

async def main():
    # Create HAL
    hal = YourDisplayHAL(width=800, height=1200)

    # Configure application
    config = AppConfig(
        display_hal=hal,
        library_path="/home/user/Books",
        page_size=(800, 1200),
        log_level=logging.INFO
    )

    # Create application
    app = DReaderApplication(config)

    # If using EventLoopHAL:
    await hal.run_event_loop(app)

    # OR if using basic DisplayHAL:
    # await app.start()
    # while app.is_running():
    #     event = await hal.get_touch_event()
    #     if event:
    #         await app.handle_touch(event)
    #     await asyncio.sleep(0.01)
    # await app.shutdown()

if __name__ == "__main__":
    asyncio.run(main())
```

---

## 10. Troubleshooting

### Common Issues

**Issue**: Images not displaying
- Check framebuffer path (`/dev/fb0`)
- Verify color format (RGB vs BGR)
- Check image size matches display
- Verify write permissions

**Issue**: Touch not detected
- Check evdev device path
- Verify touch coordinates are mapped correctly
- Check coordinate system (origin, scaling)
- Test with `evtest` utility

**Issue**: Gestures not recognized
- Check thresholds (TAP_THRESHOLD, etc.)
- Add debug logging to gesture detection
- Verify touch up/down events are paired
- Check timestamp accuracy

**Issue**: Slow performance
- Profile image conversion (use cProfile)
- Cache converted images
- Use partial refresh on e-ink
- Consider hardware acceleration

---

## 11. Resources

### Documentation
- DReader Architecture: `INTEGRATION_DOCUMENTATION_INDEX.md`
- pyWebLayout Integration: `PYWEBLAYOUT_INTEGRATION_GUIDE.md`
- Async Rendering: `ASYNC_DIRTY_FLAG_RENDERING.md`

### Reference Code
- Pygame HAL: `dreader/hal_pygame.py`
- HAL Interface: `dreader/hal.py`
- Gesture Types: `dreader/gesture.py`
- Main Application: `dreader/main.py`

### External References
- Linux Framebuffer: https://www.kernel.org/doc/Documentation/fb/api.txt
- evdev Input: https://python-evdev.readthedocs.io/
- PIL/Pillow: https://pillow.readthedocs.io/
- E-Ink Controllers: Device-specific documentation (Kobo, Kindle, reMarkable)

---

## 12. Checklist for HAL Implementers

Use this checklist to track your implementation:

### Setup
- [ ] Read this specification completely
- [ ] Study PygameDisplayHAL reference implementation
- [ ] Set up development environment
- [ ] Test DReader with Pygame HAL first

### Core Implementation
- [ ] Create HAL class inheriting from DisplayHAL
- [ ] Implement `__init__()` with device configuration
- [ ] Implement `initialize()` - hardware setup
- [ ] Implement `cleanup()` - resource cleanup
- [ ] Implement `show_image()` - display rendering
- [ ] Implement `get_touch_event()` - input handling
- [ ] Implement gesture detection algorithm
- [ ] Implement `set_brightness()` (if supported)

### Testing
- [ ] Unit test each method
- [ ] Test with minimal application
- [ ] Test with full DReader application
- [ ] Test all essential gestures
- [ ] Performance profiling
- [ ] Memory leak testing
- [ ] Long-term stability testing

### Optimization
- [ ] Profile and optimize hot paths
- [ ] Implement caching where appropriate
- [ ] Add platform-specific optimizations
- [ ] Tune gesture thresholds
- [ ] Optimize display refresh strategy

### Documentation
- [ ] Document platform-specific requirements
- [ ] Document known limitations
- [ ] Create usage examples
- [ ] Write troubleshooting guide

---

## Version History

| Version | Date | Changes |
|---------|------|---------|
| 1.0 | 2025-11-09 | Initial specification |

---

**End of HAL Implementation Specification**

For questions or issues, please refer to the DReader documentation or create an issue in the project repository.
