dreader-application/examples/run_on_hardware.py
2025-11-12 18:52:08 +00:00

293 lines
8.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Run DReader on real e-ink hardware.
This example demonstrates running the DReader application on real e-ink hardware
using the dreader-hal library for hardware abstraction.
Requirements:
- Raspberry Pi (or compatible SBC)
- IT8951 e-ink display
- FT5xx6 capacitive touch sensor
- Optional: BMA400 accelerometer, PCF8523 RTC, INA219 power monitor
Hardware Setup:
See external/dreader-hal/README.md for wiring instructions
Usage:
# On Raspberry Pi with full hardware
python run_on_hardware.py /path/to/library
# For testing without hardware (virtual display mode)
python run_on_hardware.py /path/to/library --virtual
# Disable optional components
python run_on_hardware.py /path/to/library --no-orientation --no-rtc --no-power
"""
import sys
import asyncio
import argparse
import logging
from pathlib import Path
# Add parent directory to path to import dreader
sys.path.insert(0, str(Path(__file__).parent.parent))
from dreader.hal_hardware import HardwareDisplayHAL
from dreader.main import DReaderApplication, AppConfig
async def main(args):
"""
Main application entry point.
Args:
args: Command line arguments
"""
# Set up logging
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
logger.info("Starting DReader on hardware")
logger.info(f"Library path: {args.library_path}")
logger.info(f"Display size: {args.width}x{args.height}")
logger.info(f"VCOM: {args.vcom}V")
logger.info(f"Virtual display: {args.virtual}")
# Create hardware HAL
logger.info("Initializing hardware HAL...")
hal = HardwareDisplayHAL(
width=args.width,
height=args.height,
vcom=args.vcom,
virtual_display=args.virtual,
auto_sleep_display=args.auto_sleep,
enable_orientation=args.orientation,
enable_rtc=args.rtc,
enable_power_monitor=args.power,
battery_capacity_mah=args.battery_capacity,
)
# Create application config
config = AppConfig(
display_hal=hal,
library_path=args.library_path,
page_size=(args.width, args.height),
auto_save_interval=60,
force_library_mode=args.force_library,
log_level=logging.DEBUG if args.verbose else logging.INFO,
)
# Create application
app = DReaderApplication(config)
try:
# Initialize hardware
logger.info("Initializing hardware...")
await hal.initialize()
# Start application
logger.info("Starting application...")
await app.start()
# Show battery level if available
if args.power and not args.virtual:
try:
battery = await hal.get_battery_level()
logger.info(f"Battery level: {battery:.1f}%")
if await hal.is_low_battery():
logger.warning("⚠️ Low battery!")
except Exception as e:
logger.warning(f"Could not read battery: {e}")
# Main event loop
logger.info("Entering main event loop (Ctrl+C to exit)")
logger.info("")
logger.info("Touch gestures:")
logger.info(" - Swipe left: Next page")
logger.info(" - Swipe right: Previous page")
logger.info(" - Swipe up (from bottom): Open navigation/TOC")
logger.info(" - Swipe down (from top): Open settings")
logger.info(" - Tap: Select book/word/link")
logger.info("")
while app.is_running():
# Get touch event (non-blocking)
event = await hal.get_touch_event()
if event:
logger.debug(f"Touch event: {event.gesture.value} at ({event.x}, {event.y})")
# Handle touch event
await app.handle_touch(event)
# Check battery periodically (every ~100 events)
if args.power and not args.virtual and args.show_battery:
if hasattr(app, '_event_count'):
app._event_count += 1
else:
app._event_count = 1
if app._event_count % 100 == 0:
battery = await hal.get_battery_level()
logger.info(f"Battery: {battery:.1f}%")
# Small delay to prevent CPU spinning
await asyncio.sleep(0.01)
except KeyboardInterrupt:
logger.info("Received interrupt signal, shutting down...")
except Exception as e:
logger.error(f"Error in main loop: {e}", exc_info=True)
finally:
# Shutdown
logger.info("Shutting down application...")
await app.shutdown()
logger.info("Cleaning up hardware...")
await hal.cleanup()
logger.info("DReader stopped")
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Run DReader on e-ink hardware",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Run on real hardware
%(prog)s /home/pi/Books
# Test with virtual display (no hardware required)
%(prog)s /home/pi/Books --virtual
# Custom display size and VCOM
%(prog)s /home/pi/Books --width 1200 --height 1600 --vcom -2.3
# Disable optional sensors
%(prog)s /home/pi/Books --no-orientation --no-rtc --no-power
"""
)
# Required arguments
parser.add_argument(
'library_path',
type=str,
help='Path to directory containing EPUB files'
)
# Display arguments
parser.add_argument(
'--width',
type=int,
default=1872,
help='Display width in pixels (default: 1872)'
)
parser.add_argument(
'--height',
type=int,
default=1404,
help='Display height in pixels (default: 1404)'
)
parser.add_argument(
'--vcom',
type=float,
default=-2.0,
help='E-ink VCOM voltage - CHECK YOUR DISPLAY LABEL! (default: -2.0)'
)
# Virtual display mode
parser.add_argument(
'--virtual',
action='store_true',
help='Use virtual display mode for testing without hardware'
)
# Display features
parser.add_argument(
'--no-auto-sleep',
dest='auto_sleep',
action='store_false',
help='Disable automatic display sleep after updates'
)
# Optional hardware components
parser.add_argument(
'--no-orientation',
dest='orientation',
action='store_false',
help='Disable orientation sensor (BMA400)'
)
parser.add_argument(
'--no-rtc',
dest='rtc',
action='store_false',
help='Disable RTC (PCF8523)'
)
parser.add_argument(
'--no-power',
dest='power',
action='store_false',
help='Disable power monitor (INA219)'
)
# Battery monitoring
parser.add_argument(
'--battery-capacity',
type=float,
default=3000,
help='Battery capacity in mAh (default: 3000)'
)
parser.add_argument(
'--show-battery',
action='store_true',
help='Periodically log battery level'
)
# Application behavior
parser.add_argument(
'--force-library',
action='store_true',
help='Always start in library mode (ignore saved state)'
)
# Debugging
parser.add_argument(
'-v', '--verbose',
action='store_true',
help='Enable verbose debug logging'
)
args = parser.parse_args()
# Validate library path
library_path = Path(args.library_path).expanduser()
if not library_path.exists():
parser.error(f"Library path does not exist: {library_path}")
if not library_path.is_dir():
parser.error(f"Library path is not a directory: {library_path}")
args.library_path = str(library_path)
return args
if __name__ == '__main__':
args = parse_args()
# Run async main
try:
asyncio.run(main(args))
except KeyboardInterrupt:
print("\nInterrupted by user")
sys.exit(0)