#!/usr/bin/env python3 """ Run DReader on hardware using hardware_config.json configuration. This script loads all hardware configuration from hardware_config.json, including display settings, GPIO buttons, and optional components. Usage: # Use default config file (hardware_config.json) python run_on_hardware_config.py # Use custom config file python run_on_hardware_config.py --config my_config.json # Override config settings python run_on_hardware_config.py --library ~/MyBooks --verbose """ import sys import asyncio import argparse import logging import json 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 from dreader.gpio_buttons import load_button_config_from_dict def load_config(config_path: str) -> dict: """Load hardware configuration from JSON file.""" config_file = Path(config_path) if not config_file.exists(): raise FileNotFoundError( f"Configuration file not found: {config_path}\n" f"Run 'sudo python3 setup_rpi.py' to create it." ) with open(config_file, 'r') as f: config = json.load(f) return config async def main(args): """Main application entry point.""" # Load configuration logger = logging.getLogger(__name__) logger.info(f"Loading configuration from {args.config}") try: config = load_config(args.config) except Exception as e: print(f"Error loading configuration: {e}") sys.exit(1) # Apply command-line overrides if args.library: config['application']['library_path'] = args.library if args.verbose: config['application']['log_level'] = 'DEBUG' # Set up logging log_level = getattr(logging, config['application']['log_level'].upper()) logging.basicConfig( level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger.info("="*70) logger.info("DReader Hardware Mode") logger.info("="*70) # Display configuration summary display_cfg = config['display'] logger.info(f"Display: {display_cfg['width']}x{display_cfg['height']}, VCOM={display_cfg['vcom']}V") gpio_cfg = config.get('gpio_buttons', {}) if gpio_cfg.get('enabled', False): logger.info(f"GPIO Buttons: {len(gpio_cfg.get('buttons', []))} configured") accel_cfg = config.get('accelerometer', {}) if accel_cfg.get('enabled', False): logger.info("Accelerometer: Enabled") rtc_cfg = config.get('rtc', {}) if rtc_cfg.get('enabled', False): logger.info("RTC: Enabled") power_cfg = config.get('power_monitor', {}) if power_cfg.get('enabled', False): logger.info("Power Monitor: Enabled") # Create hardware HAL logger.info("\nInitializing hardware HAL...") hal = HardwareDisplayHAL( width=display_cfg['width'], height=display_cfg['height'], vcom=display_cfg['vcom'], spi_hz=display_cfg.get('spi_hz', 24_000_000), virtual_display=False, auto_sleep_display=display_cfg.get('auto_sleep', True), enable_orientation=accel_cfg.get('enabled', True), enable_rtc=rtc_cfg.get('enabled', True), enable_power_monitor=power_cfg.get('enabled', True), shunt_ohms=power_cfg.get('shunt_ohms', 0.1), battery_capacity_mah=power_cfg.get('battery_capacity_mah', 3000), ) # Load accelerometer tilt calibration if enabled if accel_cfg.get('tilt_enabled', False): calib_file = accel_cfg.get('calibration_file', 'accelerometer_config.json') if hal.load_accelerometer_calibration(calib_file): logger.info(f"Accelerometer tilt detection enabled (calibration from {calib_file})") else: logger.warning("Accelerometer tilt detection requested but calibration not loaded") # Set up GPIO buttons button_handler = None if gpio_cfg.get('enabled', False): logger.info("Setting up GPIO buttons...") button_handler = load_button_config_from_dict( config, screen_width=display_cfg['width'], screen_height=display_cfg['height'] ) if button_handler: await button_handler.initialize() logger.info(f"GPIO buttons initialized: {len(gpio_cfg.get('buttons', []))} buttons") # Create application config app_cfg = config['application'] app_config = AppConfig( display_hal=hal, library_path=app_cfg['library_path'], page_size=(display_cfg['width'], display_cfg['height']), auto_save_interval=app_cfg.get('auto_save_interval', 60), force_library_mode=app_cfg.get('force_library_mode', False), log_level=log_level, ) # Create application app = DReaderApplication(app_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 power_cfg.get('enabled', False): try: battery = await hal.get_battery_level() logger.info(f"Battery level: {battery:.1f}%") if await hal.is_low_battery(power_cfg.get('low_battery_threshold', 20.0)): logger.warning("⚠️ Low battery!") except Exception as e: logger.warning(f"Could not read battery: {e}") # Main event loop logger.info("\nApplication ready!") logger.info("="*70) event_count = 0 show_battery_interval = power_cfg.get('show_battery_interval', 100) while app.is_running(): # Check for touch events touch_event = await hal.get_touch_event() if touch_event: logger.debug(f"Touch: {touch_event.gesture.value} at ({touch_event.x}, {touch_event.y})") await app.handle_touch(touch_event) event_count += 1 # Check for button events if button_handler: button_event = await button_handler.get_button_event() if button_event: logger.info(f"Button: {button_event.gesture.value}") await app.handle_touch(button_event) event_count += 1 # Check for tilt gestures if enabled if accel_cfg.get('tilt_enabled', False): tilt_event = await hal.get_tilt_gesture() if tilt_event: logger.info(f"Tilt: {tilt_event.gesture.value}") await app.handle_touch(tilt_event) event_count += 1 # Show battery periodically if power_cfg.get('enabled', False) and event_count % show_battery_interval == 0 and event_count > 0: try: battery = await hal.get_battery_level() logger.info(f"Battery: {battery:.1f}%") except: pass # Small delay to prevent CPU spinning await asyncio.sleep(0.01) except KeyboardInterrupt: logger.info("\nReceived 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 GPIO buttons...") if button_handler: await button_handler.cleanup() 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 using hardware_config.json", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Configuration: Edit hardware_config.json to configure your hardware settings. Run 'sudo python3 setup_rpi.py' to create/update the config file. Examples: # Use default config %(prog)s # Use custom config file %(prog)s --config my_hardware.json # Override library path %(prog)s --library ~/MyBooks # Enable verbose logging %(prog)s --verbose """ ) parser.add_argument( '--config', type=str, default='hardware_config.json', help='Path to hardware configuration file (default: hardware_config.json)' ) parser.add_argument( '--library', type=str, help='Override library path from config' ) parser.add_argument( '-v', '--verbose', action='store_true', help='Enable verbose debug logging' ) args = parser.parse_args() 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)