293 lines
8.1 KiB
Python
Executable File
293 lines
8.1 KiB
Python
Executable File
#!/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)
|