HW integratation
This commit is contained in:
Executable
+293
@@ -0,0 +1,293 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user