dreader-application/examples/run_on_hardware_config.py
Duncan Tourolle 70c0b4a1f2
All checks were successful
Python CI / test (3.12) (push) Successful in 7m1s
Python CI / test (3.13) (push) Successful in 7m10s
HW integratation
2025-11-11 11:57:39 +01:00

294 lines
9.0 KiB
Python
Executable File

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