Test appplication for offdevice testing
This commit is contained in:
+188
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user