Duncan Tourolle 01e79dfa4b
All checks were successful
Python CI / test (3.12) (push) Successful in 22m19s
Python CI / test (3.13) (push) Successful in 8m23s
Test appplication for offdevice testing
2025-11-09 17:47:34 +01:00

189 lines
5.2 KiB
Python

"""
Hardware Abstraction Layer (HAL) interface for DReader.
This module defines the abstract interface that platform-specific
display/input implementations must provide.
The HAL separates the core e-reader logic from platform-specific
hardware details (display, touch input, buttons, etc.).
"""
from abc import ABC, abstractmethod
from typing import AsyncIterator, Optional
from PIL import Image
from .gesture import TouchEvent
class DisplayHAL(ABC):
"""
Abstract interface for display and input hardware.
Platform-specific implementations should subclass this and provide
concrete implementations for all abstract methods.
The HAL is responsible for:
- Displaying images on the screen
- Capturing touch/click input and converting to TouchEvent
- Hardware-specific features (brightness, sleep, etc.)
All methods are async to support non-blocking I/O.
"""
@abstractmethod
async def show_image(self, image: Image.Image):
"""
Display a PIL Image on the screen.
Args:
image: PIL Image to display
This method should handle:
- Converting image format if needed for the display
- Scaling/cropping if image size doesn't match display
- Updating the physical display hardware
"""
pass
@abstractmethod
async def get_touch_event(self) -> Optional[TouchEvent]:
"""
Wait for and return the next touch event.
Returns:
TouchEvent if available, None if no event (non-blocking mode)
This method should:
- Read from touch hardware
- Convert raw coordinates to TouchEvent
- Detect gesture type (tap, swipe, etc.)
- Return None immediately if no event available
Note: For blocking behavior, implement a loop that awaits this
method in the main event loop.
"""
pass
@abstractmethod
async def set_brightness(self, level: int):
"""
Set display brightness.
Args:
level: Brightness level (0-10, where 0=dimmest, 10=brightest)
Platform implementations should map this to their hardware's
actual brightness range.
"""
pass
async def initialize(self):
"""
Initialize the display hardware.
This optional method is called once before the application starts.
Override to perform platform-specific initialization.
"""
pass
async def cleanup(self):
"""
Clean up display hardware resources.
This optional method is called during application shutdown.
Override to perform platform-specific cleanup.
"""
pass
async def show_message(self, message: str, duration: float = 2.0):
"""
Display a text message (for loading screens, errors, etc.).
Args:
message: Text message to display
duration: How long to show message (seconds)
Default implementation creates a simple text image.
Override for platform-specific message display.
"""
from PIL import ImageDraw, ImageFont
# Create simple text image
img = Image.new('RGB', (800, 1200), color=(255, 255, 255))
draw = ImageDraw.Draw(img)
# Try to use a decent font, fall back to default
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32)
except:
font = ImageFont.load_default()
# Draw centered text
bbox = draw.textbbox((0, 0), message, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (800 - text_width) // 2
y = (1200 - text_height) // 2
draw.text((x, y), message, fill=(0, 0, 0), font=font)
await self.show_image(img)
# Wait for duration
if duration > 0:
import asyncio
await asyncio.sleep(duration)
class KeyboardInputHAL(ABC):
"""
Optional abstract interface for keyboard input.
This is separate from DisplayHAL to support platforms that have
both touch and keyboard input (e.g., desktop testing).
"""
@abstractmethod
async def get_key_event(self) -> Optional[str]:
"""
Get the next keyboard event.
Returns:
Key name as string (e.g., "up", "down", "enter", "q")
None if no key event available
"""
pass
class EventLoopHAL(DisplayHAL):
"""
Extended HAL interface that provides its own event loop.
Some platforms (e.g., Pygame, Qt) have their own event loop that
must be used. This interface allows the HAL to run the main loop
and call back to the application.
Usage:
hal = MyEventLoopHAL()
app = DReaderApplication(AppConfig(display_hal=hal, ...))
await hal.run_event_loop(app)
"""
@abstractmethod
async def run_event_loop(self, app):
"""
Run the platform's event loop.
Args:
app: DReaderApplication instance to send events to
This method should:
1. Initialize the display
2. Call app.start()
3. Enter event loop
4. Call app.handle_touch(event) for each event
5. Handle quit events and call app.shutdown()
"""
pass