#!/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)