294 lines
9.0 KiB
Python
Executable File
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)
|