diff --git a/ACCELEROMETER_PAGE_FLIP.md b/ACCELEROMETER_PAGE_FLIP.md deleted file mode 100644 index 364ebcd..0000000 --- a/ACCELEROMETER_PAGE_FLIP.md +++ /dev/null @@ -1,410 +0,0 @@ -# Accelerometer-Based Page Flipping - -This document describes the accelerometer-based page flipping feature that allows users to navigate pages by tilting the device. - -## Overview - -The accelerometer page flipping feature uses the BMA400 3-axis accelerometer to detect device tilt and automatically turn pages. This provides a hands-free way to read, which is useful when: - -- Eating or drinking while reading -- Holding the device with one hand -- Device is mounted (e.g., on a stand) -- Accessibility needs - -## Architecture - -### Components - -1. **Gesture Types** ([dreader/gesture.py](dreader/gesture.py:29-30)) - - `TILT_FORWARD` - Tilt device forward to go to next page - - `TILT_BACKWARD` - Tilt device backward to go to previous page - -2. **HAL Integration** ([dreader/hal_hardware.py](dreader/hal_hardware.py:414-563)) - - `load_accelerometer_calibration()` - Loads calibration from JSON file - - `get_tilt_gesture()` - Polls accelerometer and detects tilt gestures - - Gravity direction calculation based on calibrated "up" vector - - Debouncing to prevent multiple page flips from single tilt - -3. **Gesture Handlers** ([dreader/handlers/gestures.py](dreader/handlers/gestures.py:84-87)) - - `TILT_FORWARD` → calls `_handle_page_forward()` - - `TILT_BACKWARD` → calls `_handle_page_back()` - - Uses same page navigation logic as swipe gestures - -4. **Calibration Tool** ([examples/calibrate_accelerometer.py](examples/calibrate_accelerometer.py)) - - Interactive calibration using display - - Shows live arrow pointing in gravity direction - - User rotates device until arrow points "up" - - Saves calibration to JSON file - -5. **Demo Application** ([examples/demo_accelerometer_page_flip.py](examples/demo_accelerometer_page_flip.py)) - - Complete integration example - - Combines touch and accelerometer gestures - - Shows how to poll both input sources - -## How It Works - -### Calibration - -The calibration process establishes which direction is "up" for the device: - -1. Run `python examples/calibrate_accelerometer.py` -2. Device displays an arrow showing gravity direction -3. Rotate device until arrow points up -4. Tap screen to save calibration -5. Calibration stored in `accelerometer_config.json` - -**Calibration Data:** -```json -{ - "up_vector": { - "x": 0.0, - "y": 9.8, - "z": 0.0 - }, - "tilt_threshold": 0.3, - "debounce_time": 0.5 -} -``` - -### Tilt Detection Algorithm - -The algorithm detects when the device is tilted beyond a threshold angle from the calibrated "up" position: - -1. **Read Accelerometer**: Get (x, y, z) acceleration in m/s² -2. **Normalize Vectors**: Normalize both current gravity and calibrated up vector -3. **Calculate Tilt Angle**: - - Project gravity onto plane perpendicular to up vector - - Calculate angle using `atan2(perpendicular_magnitude, vertical_component)` -4. **Compare to Threshold**: Default 0.3 radians (~17 degrees) -5. **Determine Direction**: - - Positive perpendicular y-component → Forward tilt → Next page - - Negative perpendicular y-component → Backward tilt → Previous page -6. **Debounce**: Prevent repeated triggers within debounce time (default 0.5s) - -**Math Details:** - -Given: -- Up vector (calibrated): `U = (ux, uy, uz)` -- Current gravity: `G = (gx, gy, gz)` - -Calculate: -```python -# Dot product: component of G along U -dot = gx*ux + gy*uy + gz*uz - -# Perpendicular component -perp = G - dot*U -perp_magnitude = |perp| - -# Tilt angle -angle = atan2(perp_magnitude, |dot|) - -# Direction (simplified) -if perp_y > 0: - gesture = TILT_FORWARD -else: - gesture = TILT_BACKWARD -``` - -### Event Loop Integration - -The main application event loop polls both touch and accelerometer: - -```python -while running: - # Check touch events - touch_event = await hal.get_touch_event() - if touch_event: - handle_gesture(touch_event) - - # Check accelerometer tilt (if calibrated) - if calibrated: - tilt_event = await hal.get_tilt_gesture() - if tilt_event: - handle_gesture(tilt_event) - - await asyncio.sleep(0.05) # ~20Hz polling -``` - -## Usage - -### 1. Calibration (One-time) - -```bash -python examples/calibrate_accelerometer.py -``` - -This creates `accelerometer_config.json` in the current directory. - -### 2. Load Calibration in Your Application - -```python -from dreader.hal_hardware import HardwareDisplayHAL - -# Create HAL with accelerometer enabled -hal = HardwareDisplayHAL( - width=1872, - height=1404, - enable_orientation=True # Important! -) - -await hal.initialize() - -# Load calibration -if hal.load_accelerometer_calibration("accelerometer_config.json"): - print("Accelerometer calibrated!") -else: - print("No calibration found - tilt gestures disabled") -``` - -### 3. Poll for Gestures - -**Option A: Unified Event API (Recommended)** - -```python -# Main event loop - simplest approach -while True: - # Get event from any source (touch or accelerometer) - event = await hal.get_event() - - if event: - response = gesture_router.handle_touch(event) - # ... process response - - await asyncio.sleep(0.01) -``` - -**Option B: Separate Polling (More Control)** - -```python -# Main event loop - explicit control -while True: - # Get touch events - touch_event = await hal.get_touch_event() - - # Get tilt events (returns None if not calibrated) - tilt_event = await hal.get_tilt_gesture() - - # Handle events - if touch_event: - response = gesture_router.handle_touch(touch_event) - # ... process response - - if tilt_event: - response = gesture_router.handle_touch(tilt_event) - # ... process response - - await asyncio.sleep(0.05) -``` - -### 4. Run Demo - -```bash -# Simple demo using unified API -python examples/demo_accelerometer_simple.py ~/Books/mybook.epub - -# Full-featured demo with separate polling -python examples/demo_accelerometer_page_flip.py ~/Books/mybook.epub -``` - -## Configuration - -### Tilt Threshold - -Adjust sensitivity by changing `tilt_threshold` in the config file: - -- **0.1 rad (~6°)**: Very sensitive, small tilts trigger pages -- **0.3 rad (~17°)**: Default, moderate sensitivity -- **0.5 rad (~29°)**: Less sensitive, requires larger tilt - -### Debounce Time - -Adjust `debounce_time` to control how quickly you can trigger repeated page flips: - -- **0.2s**: Fast, can quickly flip multiple pages -- **0.5s**: Default, prevents accidental double-flips -- **1.0s**: Slow, requires deliberate pauses between flips - -### Example Custom Configuration - -```json -{ - "up_vector": { - "x": 0.0, - "y": 9.8, - "z": 0.0 - }, - "tilt_threshold": 0.2, - "debounce_time": 0.3 -} -``` - -## Testing - -Run the test suite: - -```bash -python -m pytest tests/test_accelerometer_gestures.py -v -``` - -**Tests include:** -- Calibration loading -- Tilt angle calculation (forward, backward, upright) -- Threshold detection -- Gesture type definitions - -## Troubleshooting - -### "Accelerometer calibration file not found" - -Run the calibration script first: -```bash -python examples/calibrate_accelerometer.py -``` - -### Tilt gestures not working - -1. Check accelerometer is enabled in HAL: - ```python - hal = HardwareDisplayHAL(enable_orientation=True) - ``` - -2. Verify calibration loaded: - ```python - result = hal.load_accelerometer_calibration() - print(f"Calibrated: {result}") - ``` - -3. Check you're polling tilt events: - ```python - tilt_event = await hal.get_tilt_gesture() - ``` - -### Tilt too sensitive / not sensitive enough - -Edit `accelerometer_config.json` and adjust `tilt_threshold`: -- Lower value = more sensitive -- Higher value = less sensitive - -### Pages flip too fast / too slow - -Edit `accelerometer_config.json` and adjust `debounce_time`: -- Lower value = faster repeat flips -- Higher value = slower repeat flips - -### Wrong direction (forward goes backward) - -The tilt direction detection is device-specific. You may need to adjust the direction logic in [dreader/hal_hardware.py](dreader/hal_hardware.py:547-550): - -```python -# Current logic (line 547) -if perp_y > 0: - gesture = AppGestureType.TILT_FORWARD -else: - gesture = AppGestureType.TILT_BACKWARD - -# Try inverting: -if perp_y < 0: # Changed > to < - gesture = AppGestureType.TILT_FORWARD -else: - gesture = AppGestureType.TILT_BACKWARD -``` - -## Limitations - -1. **Orientation Lock**: Tilt detection assumes fixed device orientation. Auto-rotation may interfere. - -2. **Walking/Movement**: May trigger false positives when walking. Use higher threshold or disable while moving. - -3. **Calibration Drift**: Accelerometer may drift over time. Re-calibrate periodically. - -4. **Direction Heuristic**: Current direction detection is simplified. Complex orientations may not work correctly. - -5. **Single Axis**: Only detects tilt in one plane. Doesn't distinguish left/right tilts. - -## Future Improvements - -- [ ] Shake gesture to open TOC/settings -- [ ] Multi-axis tilt for 4-direction navigation -- [ ] Auto-calibration on startup -- [ ] Gyroscope integration for rotation gestures -- [ ] Adaptive threshold based on reading posture -- [ ] Tilt gesture visualization for debugging - -## API Reference - -### HardwareDisplayHAL - -#### `load_accelerometer_calibration(config_path: str = "accelerometer_config.json") -> bool` - -Load accelerometer calibration from JSON file. - -**Parameters:** -- `config_path`: Path to calibration JSON file - -**Returns:** -- `True` if calibration loaded successfully, `False` otherwise - -#### `async get_event() -> Optional[TouchEvent]` - -**[Recommended]** Get the next event from any input source (touch or accelerometer). - -This is a convenience method that polls both touch and accelerometer in a single call. - -**Returns:** -- `TouchEvent` from either touch sensor or accelerometer -- `None` if no event available -- Touch events are prioritized over tilt events - -**Example:** -```python -while running: - event = await hal.get_event() - if event: - handle_gesture(event) - await asyncio.sleep(0.01) -``` - -#### `async get_tilt_gesture() -> Optional[TouchEvent]` - -Poll accelerometer and check for tilt gestures. - -**Returns:** -- `TouchEvent` with `TILT_FORWARD` or `TILT_BACKWARD` gesture if tilt detected -- `None` if no tilt, not calibrated, or within debounce period - -**Note:** Must call `load_accelerometer_calibration()` first. Consider using `get_event()` instead for simpler code. - -### GestureType - -#### `TILT_FORWARD = "tilt_forward"` -Gesture type for forward tilt (next page) - -#### `TILT_BACKWARD = "tilt_backward"` -Gesture type for backward tilt (previous page) - -### Calibration File Format - -```json -{ - "up_vector": { - "x": float, // X-component of gravity when upright (m/s²) - "y": float, // Y-component of gravity when upright (m/s²) - "z": float // Z-component of gravity when upright (m/s²) - }, - "tilt_threshold": float, // Tilt angle threshold in radians - "debounce_time": float // Minimum time between gestures in seconds -} -``` - -## Examples - -See the `examples/` directory for complete working examples: - -- **[calibrate_accelerometer.py](examples/calibrate_accelerometer.py)** - Interactive calibration tool -- **[demo_accelerometer_simple.py](examples/demo_accelerometer_simple.py)** - Simple demo using unified `get_event()` API -- **[demo_accelerometer_page_flip.py](examples/demo_accelerometer_page_flip.py)** - Full-featured demo with separate event polling - -## License - -Same as the main DReader project. diff --git a/GPIO_BUTTONS.md b/GPIO_BUTTONS.md deleted file mode 100644 index 2481841..0000000 --- a/GPIO_BUTTONS.md +++ /dev/null @@ -1,286 +0,0 @@ -# GPIO Button Configuration Guide - -This guide explains how to configure physical buttons for the DReader e-reader. - -## Overview - -Physical buttons provide tactile feedback for page turns and navigation without requiring touch input. Buttons are connected between GPIO pins and GND, using internal pull-up resistors. - -## Hardware Setup - -### Basic Button Wiring - -``` - +3.3V - | - R (internal pull-up) - | -GPIO --|------ Button ------ GND - | - (to BCM2835) -``` - -When the button is pressed, it connects the GPIO pin to GND (0V), pulling the pin LOW. - -### Recommended Button Layout - -For this e-reader device with 3 buttons: - -``` -┌─────────────────────┐ -│ │ -│ [Power Off] │ ← Side button (GPIO 21) -│ │ -│ │ -│ E-INK │ -│ DISPLAY │ -│ 1872x1404 │ -│ │ -│ [Prev] [Next] │ ← Bottom edge -│ │ -└─────────────────────┘ -``` - -**Button Mapping:** -- **Previous Page** (GPIO 22) - Bottom left - Previous page -- **Next Page** (GPIO 27) - Bottom right - Next page -- **Power Off** (GPIO 21) - Side button - Shutdown device (long press) - -## Software Configuration - -### Using Interactive Setup (Recommended) - -```bash -sudo python3 setup_rpi.py -``` - -The setup script will: -1. Ask if you want GPIO buttons enabled -2. Let you configure each button individually -3. Allow custom GPIO pin assignments -4. Generate hardware_config.json automatically - -### Manual Configuration - -Edit `hardware_config.json`: - -```json -{ - "gpio_buttons": { - "enabled": true, - "pull_up": true, - "bounce_time_ms": 200, - "buttons": [ - { - "name": "prev_page", - "gpio": 22, - "gesture": "swipe_right", - "description": "Previous page button" - }, - { - "name": "next_page", - "gpio": 27, - "gesture": "swipe_left", - "description": "Next page button" - }, - { - "name": "power_off", - "gpio": 21, - "gesture": "long_press", - "description": "Power off button (long press to shutdown)" - } - ] - } -} -``` - -### Configuration Options - -- **enabled** (bool): Enable/disable all GPIO buttons -- **pull_up** (bool): Use internal pull-up resistors (always true for button-to-GND wiring) -- **bounce_time_ms** (int): Debounce time in milliseconds (default 200ms) -- **buttons** (array): List of button configurations - -### Button Configuration - -Each button has: -- **name** (string): Unique identifier for the button -- **gpio** (int): BCM GPIO pin number (2-27) -- **gesture** (string): Gesture type to generate when pressed -- **description** (string): Human-readable description - -### Available Gestures - -Buttons can trigger any gesture type: - -| Gesture | Description | Typical Use | -|---------|-------------|-------------| -| `swipe_left` | Swipe left | Next page | -| `swipe_right` | Swipe right | Previous page | -| `swipe_up` | Swipe up from bottom | Open navigation/TOC | -| `swipe_down` | Swipe down from top | Open settings | -| `tap` | Single tap | Select item | -| `long_press` | Hold | Context menu | -| `pinch_in` | Pinch zoom out | Decrease font size | -| `pinch_out` | Pinch zoom in | Increase font size | - -## GPIO Pin Selection - -### Safe GPIO Pins (BCM numbering) - -**Recommended for buttons:** -- GPIO 5, 6, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 - -**Avoid these pins:** -- GPIO 2, 3 - I2C (SDA, SCL) - Used for touch, sensors -- GPIO 7-11 - SPI - Used for e-ink display -- GPIO 14, 15 - UART - Used for serial console -- GPIO 0, 1 - Reserved for ID EEPROM - -### Pin Layout (BCM Mode) - -``` - 3V3 (1) (2) 5V - GPIO2 (3) (4) 5V GPIO 2,3 = I2C - GPIO3 (5) (6) GND - GPIO4 (7) (8) GPIO14 GPIO 7-11 = SPI - GND (9) (10) GPIO15 GPIO 14,15 = UART - GPIO17 (11) (12) GPIO18 - GPIO27 (13) (14) GND - GPIO22 (15) (16) GPIO23 ← Good for buttons - 3V3 (17) (18) GPIO24 ← Good for buttons - GPIO10 (19) (20) GND - GPIO9 (21) (22) GPIO25 ← Good for buttons - GPIO11 (23) (24) GPIO8 - GND (25) (26) GPIO7 - GPIO0 (27) (28) GPIO1 - GPIO5 (29) (30) GND ← Good for buttons - GPIO6 (31) (32) GPIO12 ← Good for buttons - GPIO13 (33) (34) GND ← Good for buttons - GPIO19 (35) (36) GPIO16 ← Good for buttons - GPIO26 (37) (38) GPIO20 ← Good for buttons - GND (39) (40) GPIO21 ← Good for buttons -``` - -## Testing Buttons - -### Test Button Connections - -```bash -# Install GPIO utilities -sudo apt install gpiod - -# Monitor GPIO 23 (next page button) -gpioget gpiochip0 23 - -# Press button - should show 0 (LOW) -# Release button - should show 1 (HIGH with pull-up) -``` - -### Test in Application - -Run with verbose logging to see button events: - -```bash -python examples/run_on_hardware_config.py --verbose -``` - -Press each button and verify you see log messages like: -``` -Button pressed: next_page (GPIO 23) -Button event queued: next_page -> swipe_left -``` - -## Troubleshooting - -### Buttons Not Working - -**Problem:** Buttons don't generate events - -**Solutions:** -1. Check wiring - button should connect GPIO to GND -2. Verify GPIO pin number in config (BCM mode, not physical pin) -3. Check permissions: `sudo usermod -a -G gpio $USER` then log out/in -4. Test GPIO with `gpioget` (see above) -5. Check logs: `python examples/run_on_hardware_config.py --verbose` - -### False Triggers - -**Problem:** Button triggers multiple times from single press - -**Solutions:** -1. Increase `bounce_time_ms` in config (try 300-500ms) -2. Add hardware debounce capacitor (0.1µF between GPIO and GND) -3. Check for loose connections - -### Wrong Action - -**Problem:** Button does wrong action - -**Solutions:** -1. Check `gesture` field in button config -2. Verify button name matches intended function -3. Check logs to see what gesture is generated - -## Advanced: Custom Button Functions - -You can map buttons to any gesture, creating custom layouts: - -### Example: Reading Mode Buttons - -```json -{ - "gpio_buttons": { - "enabled": true, - "buttons": [ - {"name": "next", "gpio": 23, "gesture": "swipe_left"}, - {"name": "prev", "gpio": 24, "gesture": "swipe_right"}, - {"name": "zoom_in", "gpio": 25, "gesture": "pinch_out"}, - {"name": "zoom_out", "gpio": 22, "gesture": "pinch_in"} - ] - } -} -``` - -### Example: Simple 2-Button Layout - -```json -{ - "gpio_buttons": { - "enabled": true, - "buttons": [ - {"name": "next", "gpio": 23, "gesture": "swipe_left"}, - {"name": "prev", "gpio": 24, "gesture": "swipe_right"} - ] - } -} -``` - -## Hardware Tips - -### Button Quality - -- Use momentary pushbuttons (normally open) -- Tactile switches provide good feedback -- Rated for at least 10,000 cycles -- Consider waterproof buttons for outdoor use - -### Mounting - -- Mount buttons accessible from device edge -- Label buttons for user convenience -- Consider button guards to prevent accidental presses -- Use hot glue or button caps for secure mounting - -### Wiring - -- Keep wires short to reduce noise -- Use stranded wire for flexibility -- Consider using a ribbon cable for clean routing -- Add strain relief at connection points - -## See Also - -- [HARDWARE_SETUP.md](HARDWARE_SETUP.md) - Complete hardware integration guide -- [hardware_config.json](hardware_config.json) - Example configuration -- [dreader/gpio_buttons.py](dreader/gpio_buttons.py) - Button handler source code diff --git a/HARDWARE_PINOUT.md b/HARDWARE_PINOUT.md deleted file mode 100755 index d35af02..0000000 --- a/HARDWARE_PINOUT.md +++ /dev/null @@ -1,230 +0,0 @@ -# DReader Hardware Pinout Reference - -Quick reference for the DReader e-ink device hardware configuration. - -## Display Specifications - -- **Resolution:** 1872 × 1404 pixels -- **Controller:** IT8951 (SPI) -- **Touch Panel:** FT5316 (I2C) - -## GPIO Pin Assignments - -### Buttons (BCM Numbering) - -| GPIO | Function | Action | Notes | -|------|----------|--------|-------| -| 21 | Power Off | Long Press (500ms+) | Shutdown button | -| 22 | Previous Page | Swipe Right | Left button | -| 27 | Next Page | Swipe Left | Right button | - -**Wiring:** All buttons connect between GPIO and GND (pull-up resistors enabled in software) - -### SPI (IT8951 E-ink Display) - -| GPIO | Function | Pin | -|------|----------|-----| -| 8 | SPI0 CE0 | 24 | -| 9 | SPI0 MISO | 21 | -| 10 | SPI0 MOSI | 19 | -| 11 | SPI0 SCLK | 23 | -| 17 | RST | 11 | -| 24 | HRDY | 18 | - -### I2C (Touch, Sensors, RTC, Power Monitor) - -| GPIO | Function | Pin | Devices | -|------|----------|-----|---------| -| 2 | I2C1 SDA | 3 | FT5316 (0x38), BMA400 (0x14), PCF8523 (0x68), INA219 (0x40) | -| 3 | I2C1 SCL | 5 | All I2C devices | - -**Note:** I2C bus is shared by all I2C devices. Each device has a unique address. - -## I2C Device Addresses - -| Address | Device | Description | -|---------|--------|-------------| -| 0x38 | FT5316 | Capacitive touch panel | -| 0x14 | BMA400 | 3-axis accelerometer (optional) | -| 0x68 | PCF8523 | Real-time clock (optional) | -| 0x40 | INA219 | Power monitor (optional) | - -## Physical Layout - -``` -Raspberry Pi GPIO Header (BCM Numbering) - - 3V3 (1) (2) 5V - GPIO2 (3) (4) 5V ← I2C1 SDA (touch, sensors) - GPIO3 (5) (6) GND ← I2C1 SCL - GPIO4 (7) (8) GPIO14 - GND (9) (10) GPIO15 - GPIO17 (11) (12) GPIO18 ← Display RST - GPIO27 (13) (14) GND - GPIO22 (15) (16) GPIO23 - 3V3 (17) (18) GPIO24 ← Display HRDY - GPIO10 (19) (20) GND ← SPI0 MOSI - GPIO9 (21) (22) GPIO25 - GPIO11 (23) (24) GPIO8 ← SPI0 SCLK, CE0 - GND (25) (26) GPIO7 - GPIO0 (27) (28) GPIO1 - GPIO5 (29) (30) GND - GPIO6 (31) (32) GPIO12 - GPIO13 (33) (34) GND - GPIO19 (35) (36) GPIO16 - GPIO26 (37) (38) GPIO20 - GND (39) (40) GPIO21 ← Power off button - - -Button Connections: - GPIO21 ──┤ ├── GND (Power off) - GPIO22 ──┤ ├── GND (Previous page) - GPIO27 ──┤ ├── GND (Next page) -``` - -## Power Requirements - -- **Input:** 5V via USB-C or GPIO header -- **Display:** ~3.3V, peak 500mA during refresh -- **Touch Panel:** 3.3V, ~20mA -- **Total (active):** ~1-2W -- **Total (sleep):** ~50-100mW - -## Configuration Files - -### hardware_config.json - -```json -{ - "display": { - "width": 1872, - "height": 1404, - "vcom": -2.0, - "spi_hz": 24000000 - }, - "gpio_buttons": { - "enabled": true, - "buttons": [ - {"name": "prev_page", "gpio": 22, "gesture": "swipe_right"}, - {"name": "next_page", "gpio": 27, "gesture": "swipe_left"}, - {"name": "power_off", "gpio": 21, "gesture": "long_press"} - ] - } -} -``` - -## Testing Connections - -### Check I2C Devices - -```bash -# Scan I2C bus 1 (GPIO 2/3) -i2cdetect -y 1 - -# Expected output: -# 0 1 2 3 4 5 6 7 8 9 a b c d e f -# 00: -- -- -- -- -- -- -- -- -- -- -- -- -- -# 10: -- -- -- -- 14 -- -- -- -- -- -- -- -- -- -- -- -# 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -# 30: -- -- -- -- -- -- -- -- 38 -- -- -- -- -- -- -- -# 40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -# 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -# 60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- -# 70: -- -- -- -- -- -- -- -- -``` - -### Check SPI - -```bash -ls /dev/spi* -# Should show: /dev/spidev0.0 /dev/spidev0.1 -``` - -### Test GPIO Buttons - -```bash -# Install GPIO tools -sudo apt install gpiod - -# Test previous page button (GPIO 22) -gpioget gpiochip0 22 -# Press button: shows 0 (LOW) -# Release button: shows 1 (HIGH, pulled up) - -# Test next page button (GPIO 27) -gpioget gpiochip0 27 - -# Test power button (GPIO 21) -gpioget gpiochip0 21 -``` - -## Quick Start - -```bash -# 1. Clone and install -git clone https://gitea.tourolle.paris/dtourolle/dreader-application.git -cd dreader-application -python3 -m venv venv -source venv/bin/activate -pip install -e . -./install_hardware_drivers.sh - -# 2. Run interactive setup -sudo python3 setup_rpi.py - -# 3. Run DReader -python examples/run_on_hardware_config.py -``` - -## Troubleshooting - -### No I2C Devices Detected - -```bash -# Enable I2C -sudo raspi-config -# Navigate to: Interface Options -> I2C -> Enable - -# Check I2C is loaded -lsmod | grep i2c -# Should show: i2c_dev, i2c_bcm2835 - -# Add user to i2c group -sudo usermod -a -G i2c $USER -# Log out and back in -``` - -### SPI Not Working - -```bash -# Enable SPI -sudo raspi-config -# Navigate to: Interface Options -> SPI -> Enable - -# Check SPI devices -ls -l /dev/spi* - -# Add user to spi group -sudo usermod -a -G spi $USER -``` - -### Buttons Not Responding - -```bash -# Add user to gpio group -sudo usermod -a -G gpio $USER - -# Test button with direct GPIO access -sudo gpioget gpiochip0 22 # Prev button -sudo gpioget gpiochip0 27 # Next button -sudo gpioget gpiochip0 21 # Power button - -# Check for conflicts -# Make sure no other programs are using these GPIOs -``` - -## See Also - -- [HARDWARE_SETUP.md](HARDWARE_SETUP.md) - Complete setup guide -- [GPIO_BUTTONS.md](GPIO_BUTTONS.md) - Button configuration reference -- [hardware_config.json](hardware_config.json) - Hardware configuration file diff --git a/HARDWARE_SETUP.md b/HARDWARE_SETUP.md deleted file mode 100644 index b4e98e1..0000000 --- a/HARDWARE_SETUP.md +++ /dev/null @@ -1,472 +0,0 @@ -# Hardware Integration Guide - -This guide explains how to run DReader on real e-ink hardware using the dreader-hal library. - -## Quick Start - -**On Raspberry Pi:** -```bash -# 1. Clone and setup -git clone https://gitea.tourolle.paris/dtourolle/dreader-application.git -cd dreader-application -python3 -m venv venv -source venv/bin/activate - -# 2. Install everything -pip install -e . -./install_hardware_drivers.sh - -# 3. Run interactive setup (detects hardware and configures) -sudo python3 setup_rpi.py - -# 4. Run DReader (uses hardware_config.json) -python examples/run_on_hardware_config.py -``` - -## Overview - -The DReader application uses a plugin-based Hardware Abstraction Layer (HAL) architecture. You can choose different HAL implementations: - -- **PygameDisplayHAL** ([dreader/hal_pygame.py](dreader/hal_pygame.py)) - Desktop testing with pygame window -- **HardwareDisplayHAL** ([dreader/hal_hardware.py](dreader/hal_hardware.py)) - Real e-ink hardware via dreader-hal - -### Configuration Files - -- **hardware_config.json** - Hardware configuration (display, buttons, sensors) -- **accelerometer_config.json** - Accelerometer calibration for tilt gestures - -## Hardware Requirements - -### Required Components -- **Raspberry Pi** (or compatible SBC) -- **IT8951 E-ink Display Controller** (1872×1404 resolution) -- **FT5316 Capacitive Touch Panel** - -### Optional Components -- **BMA400 Accelerometer** - Auto-rotation based on device orientation -- **PCF8523 RTC** - Timekeeping with battery backup -- **INA219 Power Monitor** - Battery level monitoring - -## Software Installation - -### 1. Install System Dependencies - -On Raspberry Pi: - -```bash -# Update package list -sudo apt update - -# Install system dependencies -sudo apt install -y python3-dev python3-pip python3-venv -sudo apt install -y i2c-tools python3-smbus - -# Enable I2C and SPI interfaces -sudo raspi-config -# Navigate to: Interface Options -> Enable I2C and SPI -``` - -### 2. Clone and Set Up DReader Application - -```bash -# Clone the application -git clone https://gitea.tourolle.paris/dtourolle/dreader-application.git -cd dreader-application - -# Initialize and update submodules (includes dreader-hal) -git submodule update --init --recursive - -# Create virtual environment -python3 -m venv venv -source venv/bin/activate - -# Install dreader-application -pip install -e . -``` - -### 3. Install dreader-hal and Dependencies - -The dreader-hal library has external driver dependencies in its `external/` directory: - -```bash -# Install dreader-hal in editable mode -pip install -e external/dreader-hal - -# Install external driver libraries -cd external/dreader-hal/external - -# Install each driver -pip install -e IT8951 -pip install -e PyFTtxx6 -pip install -e PyBMA400 -pip install -e PyPCF8523 -pip install -e pi_ina219 - -cd ../../.. # Back to dreader-application root -``` - -### 4. Install Raspberry Pi GPIO (if on RPi) - -```bash -pip install RPi.GPIO spidev -``` - -## Hardware Wiring - -### IT8951 E-ink Display (SPI) -| IT8951 Pin | Raspberry Pi Pin | Description | -|------------|------------------|-------------| -| VCC | 3.3V (Pin 1) | Power supply | -| GND | GND (Pin 6) | Ground | -| MISO | GPIO 9 (Pin 21) | SPI MISO | -| MOSI | GPIO 10 (Pin 19) | SPI MOSI | -| SCK | GPIO 11 (Pin 23) | SPI Clock | -| CS | GPIO 8 (Pin 24) | SPI Chip Select | -| RST | GPIO 17 (Pin 11) | Reset | -| HRDY | GPIO 24 (Pin 18) | Ready signal | - -### FT5316 Touch Panel (I2C) -| FT5316 Pin | Raspberry Pi Pin | Description | -|------------|------------------|-------------| -| VCC | 3.3V (Pin 1) | Power supply | -| GND | GND (Pin 6) | Ground | -| SDA | GPIO 2 (Pin 3) | I2C Data | -| SCL | GPIO 3 (Pin 5) | I2C Clock | -| INT | GPIO 27 (Pin 13) | Interrupt (optional) | - -### BMA400 Accelerometer (I2C) - Optional -| BMA400 Pin | Raspberry Pi Pin | Description | -|------------|------------------|-------------| -| VCC | 3.3V (Pin 1) | Power supply | -| GND | GND (Pin 6) | Ground | -| SDA | GPIO 2 (Pin 3) | I2C Data | -| SCL | GPIO 3 (Pin 5) | I2C Clock | -| I2C Address | 0x14 or 0x15 | Check your module | - -### PCF8523 RTC (I2C) - Optional -| PCF8523 Pin | Raspberry Pi Pin | Description | -|------------|------------------|-------------| -| VCC | 3.3V (Pin 1) | Power supply | -| GND | GND (Pin 6) | Ground | -| SDA | GPIO 2 (Pin 3) | I2C Data | -| SCL | GPIO 3 (Pin 5) | I2C Clock | -| BAT | CR2032 Battery | Backup battery | - -### INA219 Power Monitor (I2C) - Optional -| INA219 Pin | Raspberry Pi Pin | Description | -|------------|------------------|-------------| -| VCC | 3.3V (Pin 1) | Power supply | -| GND | GND (Pin 6) | Ground | -| SDA | GPIO 2 (Pin 3) | I2C Data | -| SCL | GPIO 3 (Pin 5) | I2C Clock | -| VIN+ | Battery + | Positive voltage sense | -| VIN- | Shunt resistor | Through shunt to load | - -**Note**: Multiple I2C devices can share the same SDA/SCL pins. Ensure each has a unique I2C address. - -### GPIO Buttons (Optional) - -Physical buttons for navigation: - -| Button Function | GPIO Pin | Connection | -|----------------|----------|------------| -| Previous Page | GPIO 22 | Button between GPIO 22 and GND | -| Next Page | GPIO 27 | Button between GPIO 27 and GND | -| Power Off | GPIO 21 | Button between GPIO 21 and GND | - -**Wiring:** -- Connect one side of button to GPIO pin -- Connect other side to GND -- Internal pull-up resistors are enabled in software -- Button press pulls GPIO LOW (0V) - -**Available GPIOs** (BCM numbering): -- Safe to use: 5-27 (except 14, 15 if using UART) -- Avoid: GPIO 2, 3 (I2C), GPIO 7-11 (SPI), GPIO 14, 15 (UART) - -## Verify Hardware Connections - -### Check I2C Devices - -```bash -# Scan I2C bus -i2cdetect -y 1 - -# Expected addresses (approximate): -# 0x38 - FT5316 touch panel -# 0x14 - BMA400 accelerometer -# 0x68 - PCF8523 RTC -# 0x40 - INA219 power monitor -``` - -### Check SPI - -```bash -# List SPI devices -ls /dev/spi* -# Should show: /dev/spidev0.0 /dev/spidev0.1 -``` - -## Important: VCOM Voltage - -⚠️ **CRITICAL**: Each e-ink display has a unique VCOM voltage printed on a label (usually on the back). - -- Check your display label for VCOM voltage (e.g., -2.06V, -1.98V, etc.) -- Pass this value to the HAL using the `--vcom` parameter -- Using incorrect VCOM can damage your display! - -Example from label: `VCOM = -2.06V` → use `--vcom -2.06` - -## Running on Hardware - -### Recommended Method: Interactive Setup - -The easiest way to get started is using the interactive setup script: - -```bash -# 1. Run setup (detects hardware, configures GPIO buttons, etc.) -sudo python3 setup_rpi.py - -# 2. Run DReader using generated config -python examples/run_on_hardware_config.py -``` - -The setup script will: -- Detect connected I2C devices (touch, accelerometer, RTC, power monitor) -- Enable I2C/SPI interfaces if needed -- Configure GPIO button mappings -- Set VCOM voltage -- Generate hardware_config.json - -### Manual Configuration - -Edit `hardware_config.json` to customize your hardware setup: - -```json -{ - "display": { - "width": 1872, - "height": 1404, - "vcom": -2.06 - }, - "gpio_buttons": { - "enabled": true, - "buttons": [ - {"name": "prev_page", "gpio": 22, "gesture": "swipe_right"}, - {"name": "next_page", "gpio": 27, "gesture": "swipe_left"}, - {"name": "power_off", "gpio": 21, "gesture": "long_press"} - ] - } -} -``` - -Then run: -```bash -python examples/run_on_hardware_config.py -``` - -### Direct Command Line (No Config File) - -```bash -# Activate virtual environment -source venv/bin/activate - -# Run with correct VCOM voltage (CHECK YOUR DISPLAY LABEL!) -python examples/run_on_hardware.py /path/to/books --vcom -2.06 -``` - -### Testing Without Hardware (Virtual Display) - -You can test the integration on your development machine using virtual display mode: - -```bash -python examples/run_on_hardware.py /path/to/books \ - --virtual \ - --no-orientation \ - --no-rtc \ - --no-power -``` - -This creates a Tkinter window simulating the e-ink display. - -### Advanced Options - -```bash -# Disable optional hardware components -python examples/run_on_hardware.py /path/to/books \ - --vcom -2.06 \ - --no-orientation \ # Disable accelerometer - --no-rtc \ # Disable RTC - --no-power # Disable battery monitor - -# Show battery level periodically -python examples/run_on_hardware.py /path/to/books \ - --vcom -2.06 \ - --show-battery - -# Custom battery capacity -python examples/run_on_hardware.py /path/to/books \ - --vcom -2.06 \ - --battery-capacity 5000 # mAh - -# Enable verbose debug logging -python examples/run_on_hardware.py /path/to/books \ - --vcom -2.06 \ - --verbose - -# Always start in library mode (ignore saved state) -python examples/run_on_hardware.py /path/to/books \ - --vcom -2.06 \ - --force-library -``` - -### Full Options Reference - -```bash -python examples/run_on_hardware.py --help -``` - -## Touch Gestures - -Once running, the following touch gestures are supported: - -| Gesture | Action | -|---------|--------| -| **Swipe Left** | Next page | -| **Swipe Right** | Previous page | -| **Swipe Up** (from bottom) | Open navigation/TOC overlay | -| **Swipe Down** (from top) | Open settings overlay | -| **Tap** | Select book, word, or link | -| **Long Press** | Show word definition (if implemented) | -| **Pinch In/Out** | Adjust font size | - -## Troubleshooting - -### Display Not Working - -1. **Check VCOM voltage** - Must match label on display -2. **Check SPI connections** - Run `ls /dev/spi*` -3. **Check SPI permissions** - Add user to `spi` group: `sudo usermod -a -G spi $USER` -4. **Try virtual display mode** - Test software without hardware - -### Touch Not Working - -1. **Check I2C connections** - Run `i2cdetect -y 1` -2. **Check I2C permissions** - Add user to `i2c` group: `sudo usermod -a -G i2c $USER` -3. **Check touch panel I2C address** - Should be 0x38 for FT5316 -4. **Calibrate touch** - See dreader-hal calibration docs - -### Import Errors - -If you see `ModuleNotFoundError` for drivers: - -```bash -# Ensure all external drivers are installed -cd external/dreader-hal/external -for dir in */; do pip install -e "$dir"; done -cd ../../.. -``` - -### Permission Errors - -```bash -# Add user to required groups -sudo usermod -a -G spi,i2c,gpio $USER - -# Log out and back in for changes to take effect -``` - -### Display Ghosting - -E-ink displays can show ghosting (image retention). The HAL automatically performs full refreshes every 10 page turns, but you can force one: - -- The `RefreshMode.FULL` is automatically triggered periodically -- Check dreader-hal documentation for manual refresh control - -## Programmatic Usage - -You can also use the hardware HAL programmatically in your own scripts: - -```python -import asyncio -from dreader.hal_hardware import HardwareDisplayHAL -from dreader.main import DReaderApplication, AppConfig - -async def main(): - # Create hardware HAL - hal = HardwareDisplayHAL( - width=1872, - height=1404, - vcom=-2.06, # YOUR DISPLAY'S VCOM! - virtual_display=False, - enable_orientation=True, - enable_rtc=True, - enable_power_monitor=True, - ) - - # Create application config - config = AppConfig( - display_hal=hal, - library_path="/home/pi/Books", - page_size=(1872, 1404), - ) - - # Create and run application - app = DReaderApplication(config) - - try: - await hal.initialize() - await app.start() - - # Main event loop - while app.is_running(): - event = await hal.get_touch_event() - if event: - await app.handle_touch(event) - await asyncio.sleep(0.01) - - finally: - await app.shutdown() - await hal.cleanup() - -if __name__ == '__main__': - asyncio.run(main()) -``` - -## Performance Notes - -### E-ink Refresh Modes - -The dreader-hal library automatically selects refresh modes: - -- **Fast (DU mode)**: ~200ms - Used for text updates -- **Quality (GC16 mode)**: ~1000ms - Used for images -- **Full (INIT mode)**: ~1000ms - Used every 10 pages to clear ghosting - -### Battery Life - -With default settings: -- Active reading: ~10-20 hours -- Standby (display sleeping): ~1-2 weeks -- Enable low power mode for extended battery life - -### Memory Usage - -- Base application: ~30-50MB -- Per book: ~10-30MB (depends on book size) -- Ensure Raspberry Pi has at least 512MB RAM - -## Next Steps - -- See [README.md](README.md) for application features -- See [external/dreader-hal/README.md](external/dreader-hal/README.md) for HAL details -- See [examples/](examples/) for more usage examples -- Check dreader-hal documentation for touch calibration and advanced features - -## Support - -For hardware-specific issues, check: -- [dreader-hal issues](https://gitea.tourolle.paris/dtourolle/dreader-hal/issues) - -For application issues, check: -- [dreader-application issues](https://gitea.tourolle.paris/dtourolle/dreader-application/issues) diff --git a/README.md b/README.md index a8a6570..ab9ec9a 100644 --- a/README.md +++ b/README.md @@ -506,7 +506,7 @@ The repository includes a pre-configured **[hardware_config.json](hardware_confi - **Display**: 1872×1404 IT8951 e-ink - **I2C Bus**: GPIO 2/3 (touch, sensors, RTC, power) -See [HARDWARE_SETUP.md](HARDWARE_SETUP.md) for complete wiring diagrams and setup instructions. +See [docs/HARDWARE.md](docs/HARDWARE.md) for complete wiring diagrams and setup instructions. ### HAL Architecture @@ -524,14 +524,15 @@ app = DReaderApplication(config) - **HardwareDisplayHAL** - Real e-ink hardware (IT8951 + dreader-hal) - **PygameDisplayHAL** - Desktop testing with pygame window -See [HARDWARE_PINOUT.md](HARDWARE_PINOUT.md) for pin assignments and [GPIO_BUTTONS.md](GPIO_BUTTONS.md) for button configuration. +See [docs/HARDWARE.md](docs/HARDWARE.md) for pin assignments and button configuration. ## Documentation - [README.md](README.md) - This file, main project documentation -- [REQUIREMENTS.md](REQUIREMENTS.md) - Application requirements specification -- [ARCHITECTURE.md](ARCHITECTURE.md) - System architecture and design details -- [HAL_IMPLEMENTATION_SPEC.md](HAL_IMPLEMENTATION_SPEC.md) - Hardware integration guide +- [docs/HARDWARE.md](docs/HARDWARE.md) - Wiring, configuration, and running on real hardware +- [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md) - Application requirements specification +- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) - System architecture and design details +- [docs/HAL_IMPLEMENTATION_SPEC.md](docs/HAL_IMPLEMENTATION_SPEC.md) - Writing a HAL for other hardware ## Performance diff --git a/ARCHITECTURE.md b/docs/ARCHITECTURE.md similarity index 90% rename from ARCHITECTURE.md rename to docs/ARCHITECTURE.md index 80343d5..34a34e9 100644 --- a/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -49,7 +49,7 @@ Think of it as: ### 1. EbookReader (Main Coordinator) -**Location**: [application.py](dreader/application.py) +**Location**: [application.py](../dreader/application.py) The central orchestrator that coordinates all subsystems: @@ -76,7 +76,7 @@ class EbookReader: ### 2. Document Manager -**Location**: [managers/document.py](dreader/managers/document.py) +**Location**: [managers/document.py](../dreader/managers/document.py) Handles document loading and metadata extraction. @@ -87,7 +87,7 @@ Handles document loading and metadata extraction. ### 3. Settings Manager -**Location**: [managers/settings.py](dreader/managers/settings.py) +**Location**: [managers/settings.py](../dreader/managers/settings.py) Manages all display settings with persistence. @@ -104,7 +104,7 @@ Manages all display settings with persistence. ### 4. Gesture Router -**Location**: [handlers/gestures.py](dreader/handlers/gestures.py) +**Location**: [handlers/gestures.py](../dreader/handlers/gestures.py) Routes touch events to appropriate handlers based on application state. @@ -128,13 +128,13 @@ Touch Event → GestureRouter ### 5. Overlay System -**Location**: [overlays/](dreader/overlays/) +**Location**: [overlays/](../dreader/overlays/) The overlay system provides modal UI panels over the reading content. #### Overlay Manager -**Location**: [overlays/base.py](dreader/overlays/base.py) +**Location**: [overlays/base.py](../dreader/overlays/base.py) Core overlay rendering and compositing infrastructure. @@ -147,7 +147,7 @@ Core overlay rendering and compositing infrastructure. #### Navigation Overlay -**Location**: [overlays/navigation.py](dreader/overlays/navigation.py) +**Location**: [overlays/navigation.py](../dreader/overlays/navigation.py) Unified overlay with tabbed interface for: - **Contents Tab**: Chapter navigation (TOC) @@ -161,7 +161,7 @@ Unified overlay with tabbed interface for: #### Settings Overlay -**Location**: [overlays/settings.py](dreader/overlays/settings.py) +**Location**: [overlays/settings.py](../dreader/overlays/settings.py) Interactive settings panel with real-time preview. @@ -175,7 +175,7 @@ Interactive settings panel with real-time preview. ### 6. Library Manager -**Location**: [library.py](dreader/library.py) +**Location**: [library.py](../dreader/library.py) Manages the library browsing experience. @@ -190,7 +190,7 @@ Manages the library browsing experience. ### 7. State Manager -**Location**: [state.py](dreader/state.py) +**Location**: [state.py](../dreader/state.py) Persistent application state across sessions. @@ -426,18 +426,18 @@ class ActionType(Enum): To add a new overlay type: -1. Define new `OverlayState` enum value in [state.py](dreader/state.py#L27-L33) -2. Create HTML generator in [html_generator.py](dreader/html_generator.py) +1. Define new `OverlayState` enum value in [state.py](../dreader/state.py#L27-L33) +2. Create HTML generator in [html_generator.py](../dreader/html_generator.py) 3. Add overlay class in `overlays/` directory -4. Implement open/close methods in [overlay manager](dreader/overlays/base.py) -5. Add gesture handling in [application.py](dreader/application.py) +4. Implement open/close methods in [overlay manager](../dreader/overlays/base.py) +5. Add gesture handling in [application.py](../dreader/application.py) ### Custom Gesture Handlers To add custom gestures: -1. Define gesture type in [gesture.py](dreader/gesture.py) -2. Add handler in [gestures.py](dreader/handlers/gestures.py) +1. Define gesture type in [gesture.py](../dreader/gesture.py) +2. Add handler in [gestures.py](../dreader/handlers/gestures.py) 3. Define action type for response 4. Update gesture router logic @@ -486,10 +486,10 @@ Examples: ### Example-Based Testing Working examples demonstrate full integration: -- [simple_ereader_example.py](examples/simple_ereader_example.py) -- [library_reading_integration.py](examples/library_reading_integration.py) -- [navigation_overlay_example.py](examples/navigation_overlay_example.py) -- [demo_settings_overlay.py](examples/demo_settings_overlay.py) +- [simple_ereader_example.py](../examples/simple_ereader_example.py) +- [library_reading_integration.py](../examples/library_reading_integration.py) +- [navigation_overlay_example.py](../examples/navigation_overlay_example.py) +- [demo_settings_overlay.py](../examples/demo_settings_overlay.py) ## Design Patterns @@ -548,5 +548,5 @@ Enable third-party extensions: - [pyWebLayout Documentation](https://gitea.tourolle.paris/dtourolle/pyWebLayout) - [REQUIREMENTS.md](REQUIREMENTS.md) - Detailed feature specifications -- [README.md](README.md) - User-facing documentation -- [examples/](examples/) - Working code examples +- [README.md](../README.md) - User-facing documentation +- [examples/](../examples/) - Working code examples diff --git a/HAL_IMPLEMENTATION_SPEC.md b/docs/HAL_IMPLEMENTATION_SPEC.md similarity index 100% rename from HAL_IMPLEMENTATION_SPEC.md rename to docs/HAL_IMPLEMENTATION_SPEC.md diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md new file mode 100644 index 0000000..8b0c781 --- /dev/null +++ b/docs/HARDWARE.md @@ -0,0 +1,853 @@ +# DReader Hardware Guide + +Everything needed to build, wire, configure and run DReader on real e-ink hardware. + +This guide covers the reference device: a Raspberry Pi driving an IT8951 e-ink +controller with an FT5316 touch panel, optional sensors and three physical buttons. +If you are *implementing a HAL for different hardware*, see +[HAL_IMPLEMENTATION_SPEC.md](HAL_IMPLEMENTATION_SPEC.md) instead. + +## Contents + +- [Quick Start](#quick-start) +- [1. Hardware Requirements](#1-hardware-requirements) +- [2. Wiring](#2-wiring) +- [3. Software Installation](#3-software-installation) +- [4. Configuration](#4-configuration) +- [5. Running DReader](#5-running-dreader) +- [6. Input Reference](#6-input-reference) +- [7. Accelerometer Page Flipping](#7-accelerometer-page-flipping) +- [8. Verifying Hardware](#8-verifying-hardware) +- [9. Troubleshooting](#9-troubleshooting) +- [10. Performance Notes](#10-performance-notes) +- [Appendix: Programmatic Usage](#appendix-programmatic-usage) + +--- + +## Quick Start + +On a Raspberry Pi with the hardware already wired: + +```bash +# 1. Clone and set up +git clone https://gitea.tourolle.paris/dtourolle/dreader-application.git +cd dreader-application +git submodule update --init --recursive +python3 -m venv venv +source venv/bin/activate + +# 2. Install the application and hardware drivers +pip install -e . +./install_hardware_drivers.sh + +# 3. Interactive setup — detects hardware, writes hardware_config.json +sudo python3 setup_rpi.py + +# 4. Run +python examples/run_on_hardware_config.py +``` + +> ⚠️ **Before first run, check your display's VCOM voltage.** See +> [VCOM Voltage](#vcom-voltage) — an incorrect value can damage the panel. + +--- + +## 1. Hardware Requirements + +### Required + +| Component | Notes | +|-----------|-------| +| **Raspberry Pi** (or compatible SBC) | 512MB RAM minimum | +| **IT8951 E-ink Display Controller** | 1872 × 1404, SPI | +| **FT5316 Capacitive Touch Panel** | I2C, address `0x38` | + +### Optional + +| Component | Purpose | I2C Address | +|-----------|---------|-------------| +| **BMA400 Accelerometer** | Auto-rotation and tilt page-flipping | `0x14` (or `0x15`) | +| **PCF8523 RTC** | Timekeeping with battery backup | `0x68` | +| **INA219 Power Monitor** | Battery level monitoring | `0x40` | +| **Momentary pushbuttons** ×3 | Page turns and power off | — | + +### Power Requirements + +- **Input:** 5V via USB-C or GPIO header +- **Display:** ~3.3V, peak 500mA during refresh +- **Touch panel:** 3.3V, ~20mA +- **Total (active):** ~1–2W +- **Total (sleep):** ~50–100mW + +--- + +## 2. Wiring + +### Pin Assignment Summary + +All GPIO numbers in this guide use **BCM numbering**, not physical pin numbers. + +| GPIO | Physical Pin | Function | +|------|--------------|----------| +| 2 | 3 | I2C1 SDA — touch, accelerometer, RTC, power monitor | +| 3 | 5 | I2C1 SCL — all I2C devices | +| 8 | 24 | SPI0 CE0 — display chip select | +| 9 | 21 | SPI0 MISO | +| 10 | 19 | SPI0 MOSI | +| 11 | 23 | SPI0 SCLK | +| 17 | 11 | Display RST | +| 21 | 40 | Power off button | +| 22 | 15 | Previous page button | +| 24 | 18 | Display HRDY | +| 27 | 13 | Next page button | + +### IT8951 E-ink Display (SPI) + +| IT8951 Pin | Raspberry Pi | Description | +|------------|--------------|-------------| +| VCC | 3.3V (Pin 1) | Power supply | +| GND | GND (Pin 6) | Ground | +| MISO | GPIO 9 (Pin 21) | SPI MISO | +| MOSI | GPIO 10 (Pin 19) | SPI MOSI | +| SCK | GPIO 11 (Pin 23) | SPI Clock | +| CS | GPIO 8 (Pin 24) | SPI Chip Select | +| RST | GPIO 17 (Pin 11) | Reset | +| HRDY | GPIO 24 (Pin 18) | Ready signal | + +### I2C Devices + +All I2C devices share SDA (GPIO 2, Pin 3) and SCL (GPIO 3, Pin 5), plus 3.3V and GND. +Each must have a unique address. + +| Device | Extra connections | +|--------|-------------------| +| **FT5316 Touch Panel** | INT is optional and unused in the reference build — see note below | +| **BMA400 Accelerometer** | None. Confirm the address on your module (`0x14` or `0x15`) | +| **PCF8523 RTC** | BAT → CR2032 backup battery | +| **INA219 Power Monitor** | VIN+ → battery positive; VIN- → through shunt resistor to load | + +> **Note on the FT5316 INT pin:** some wiring guides route it to GPIO 27, which +> collides with the next-page button on this device. The reference build leaves INT +> unconnected and polls the touch panel instead. If you want to use INT, move it to a +> free GPIO (see [safe pins](#choosing-gpio-pins)) or relocate the next-page button. + +### GPIO Buttons + +The reference device has three buttons: + +``` +┌─────────────────────┐ +│ │ +│ [Power Off] │ ← Side button (GPIO 21) +│ │ +│ E-INK │ +│ DISPLAY │ +│ 1872x1404 │ +│ │ +│ [Prev] [Next] │ ← Bottom edge (GPIO 22, GPIO 27) +│ │ +└─────────────────────┘ +``` + +| GPIO | Function | Gesture Generated | Wiring | +|------|----------|-------------------|--------| +| 22 | Previous Page | `swipe_right` | GPIO → button → **GND**, internal pull-up, active LOW | +| 27 | Next Page | `swipe_left` | GPIO → button → **GND**, internal pull-up, active LOW | +| 21 | Power Off (long press) | `long_press` | GPIO → button → **3.3V**, pull-up disabled, active HIGH | + +> ⚠️ **The power button is wired differently from the page buttons.** Prev/Next pull +> LOW to GND; the power button pulls HIGH when pressed and sets `"pull_up": false` in +> the config. Wiring it to GND like the others will make it appear permanently pressed. + +Active-low page button wiring: + +``` + +3.3V + | + R (internal pull-up) + | +GPIO --|------ Button ------ GND + | + (to BCM2835) +``` + +Use momentary pushbuttons (normally open) rated for at least 10,000 cycles. Tactile +switches give the best feedback. Keep wires short to reduce noise, use stranded wire +for flexibility, and add strain relief at the connection points. + +### Physical Layout + +``` +Raspberry Pi GPIO Header (BCM Numbering) + + 3V3 (1) (2) 5V + GPIO2 (3) (4) 5V ← I2C1 SDA (touch, sensors) + GPIO3 (5) (6) GND ← I2C1 SCL + GPIO4 (7) (8) GPIO14 + GND (9) (10) GPIO15 + GPIO17 (11) (12) GPIO18 ← Display RST + GPIO27 (13) (14) GND ← Next page button + GPIO22 (15) (16) GPIO23 ← Previous page button + 3V3 (17) (18) GPIO24 ← Display HRDY + GPIO10 (19) (20) GND ← SPI0 MOSI + GPIO9 (21) (22) GPIO25 ← SPI0 MISO + GPIO11 (23) (24) GPIO8 ← SPI0 SCLK, CE0 + GND (25) (26) GPIO7 + GPIO0 (27) (28) GPIO1 + GPIO5 (29) (30) GND + GPIO6 (31) (32) GPIO12 + GPIO13 (33) (34) GND + GPIO19 (35) (36) GPIO16 + GPIO26 (37) (38) GPIO20 + GND (39) (40) GPIO21 ← Power off button +``` + +### Choosing GPIO Pins + +If you are remapping buttons: + +**Safe for buttons:** GPIO 5, 6, 12, 13, 16, 18, 19, 20, 21, 22, 23, 25, 26, 27 + +**Avoid:** + +| Pins | Reserved for | +|------|--------------| +| GPIO 0, 1 | ID EEPROM | +| GPIO 2, 3 | I2C — touch and sensors | +| GPIO 7–11 | SPI — e-ink display | +| GPIO 14, 15 | UART — serial console | +| GPIO 17, 24 | Display RST and HRDY on this device | + +--- + +## 3. Software Installation + +### 1. System Dependencies + +```bash +sudo apt update +sudo apt install -y python3-dev python3-pip python3-venv +sudo apt install -y i2c-tools python3-smbus + +# Enable I2C and SPI +sudo raspi-config +# Navigate to: Interface Options -> Enable I2C and SPI +``` + +### 2. Clone and Set Up the Application + +```bash +git clone https://gitea.tourolle.paris/dtourolle/dreader-application.git +cd dreader-application + +# dreader-hal is a submodule +git submodule update --init --recursive + +python3 -m venv venv +source venv/bin/activate +pip install -e . +``` + +### 3. Install dreader-hal and Drivers + +The simplest route is the bundled script: + +```bash +./install_hardware_drivers.sh +``` + +To do it by hand — `dreader-hal` keeps its driver dependencies in its own +`external/` directory: + +```bash +pip install -e external/dreader-hal + +cd external/dreader-hal/external +pip install -e IT8951 +pip install -e PyFTtxx6 +pip install -e PyBMA400 +pip install -e PyPCF8523 +pip install -e pi_ina219 +cd ../../.. +``` + +### 4. Raspberry Pi GPIO + +```bash +pip install RPi.GPIO spidev +``` + +### 5. Permissions + +```bash +sudo usermod -a -G spi,i2c,gpio $USER +# Log out and back in for group changes to take effect +``` + +--- + +## 4. Configuration + +DReader uses two configuration files: + +| File | Purpose | +|------|---------| +| `hardware_config.json` | Display, buttons, sensors, application defaults | +| `accelerometer_config.json` | Tilt calibration — see [section 7](#7-accelerometer-page-flipping) | + +### Generating the Config + +The interactive setup script detects your hardware and writes `hardware_config.json`: + +```bash +sudo python3 setup_rpi.py +``` + +It will detect connected I2C devices, enable I2C/SPI if needed, configure the GPIO +button mapping and set the VCOM voltage. + +### VCOM Voltage + +⚠️ **CRITICAL**: Every e-ink panel has a unique VCOM voltage printed on a label, +usually on the ribbon cable or the back of the panel. + +- Read the value from your display's label (e.g. `VCOM = -2.06V`) +- Set it as `display.vcom` in the config, or pass `--vcom -2.06` on the command line +- **Using the wrong VCOM can damage the display** + +### hardware_config.json Reference + +```json +{ + "display": { + "width": 1872, + "height": 1404, + "vcom": -1.7, + "spi_hz": 24000000, + "rotate": "CW", + "auto_sleep": true + }, + + "gpio_buttons": { + "enabled": true, + "pull_up": true, + "bounce_time_ms": 200, + "buttons": [ + {"name": "prev_page", "gpio": 22, "gesture": "swipe_right"}, + {"name": "next_page", "gpio": 27, "gesture": "swipe_left"}, + {"name": "power_off", "gpio": 21, "gesture": "long_press", "pull_up": false} + ] + }, + + "accelerometer": { + "enabled": true, + "tilt_enabled": false, + "orientation_enabled": true, + "calibration_file": "accelerometer_config.json" + }, + + "rtc": {"enabled": true}, + + "power_monitor": { + "enabled": true, + "shunt_ohms": 0.1, + "battery_capacity_mah": 3000, + "low_battery_threshold": 20.0, + "show_battery_interval": 100 + }, + + "application": { + "library_path": "/home/pi/Books", + "auto_save_interval": 60, + "force_library_mode": false, + "log_level": "INFO" + } +} +``` + +#### `gpio_buttons` + +| Key | Type | Description | +|-----|------|-------------| +| `enabled` | bool | Enable or disable all GPIO buttons | +| `pull_up` | bool | Default pull-up for all buttons; override per button | +| `bounce_time_ms` | int | Debounce time in milliseconds (default 200) | +| `buttons` | array | Button definitions | + +Each button takes: + +| Key | Type | Description | +|-----|------|-------------| +| `name` | string | Unique identifier | +| `gpio` | int | BCM pin number | +| `gesture` | string | Gesture emitted on press — see [gesture table](#gestures) | +| `pull_up` | bool | Optional per-button override of the group default | +| `description` | string | Optional human-readable note | + +Buttons can be mapped to *any* gesture, so custom layouts are just config. For example, +a four-button reading layout: + +```json +{ + "gpio_buttons": { + "enabled": true, + "buttons": [ + {"name": "next", "gpio": 27, "gesture": "swipe_left"}, + {"name": "prev", "gpio": 22, "gesture": "swipe_right"}, + {"name": "zoom_in", "gpio": 25, "gesture": "pinch_out"}, + {"name": "zoom_out", "gpio": 23, "gesture": "pinch_in"} + ] + } +} +``` + +Button handling lives in [dreader/gpio_buttons.py](../dreader/gpio_buttons.py). + +--- + +## 5. Running DReader + +### With a Config File (Recommended) + +```bash +python examples/run_on_hardware_config.py +``` + +### Direct Command Line + +```bash +source venv/bin/activate +python examples/run_on_hardware.py /path/to/books --vcom -2.06 +``` + +### Without Hardware (Virtual Display) + +Test the integration on a development machine — this opens a Tkinter window +simulating the e-ink panel: + +```bash +python examples/run_on_hardware.py /path/to/books \ + --virtual --no-orientation --no-rtc --no-power +``` + +### Common Options + +| Option | Effect | +|--------|--------| +| `--vcom ` | Display VCOM voltage (**check your label**) | +| `--virtual` | Tkinter virtual display instead of real hardware | +| `--no-orientation` | Disable the accelerometer | +| `--no-rtc` | Disable the real-time clock | +| `--no-power` | Disable the battery monitor | +| `--show-battery` | Print battery level periodically | +| `--battery-capacity ` | Set battery capacity (default 3000) | +| `--force-library` | Always start in library mode, ignoring saved state | +| `--verbose` | Verbose debug logging | + +Full reference: `python examples/run_on_hardware.py --help` + +--- + +## 6. Input Reference + +### Gestures + +Every input source — touch, buttons, accelerometer — is normalised into the same +gesture stream, so any source can trigger any action. + +| Gesture | Default Action | +|---------|----------------| +| `swipe_left` | Next page | +| `swipe_right` | Previous page | +| `swipe_up` (from bottom) | Open navigation/TOC overlay | +| `swipe_down` (from top) | Open settings overlay | +| `tap` | Select book, word, or link | +| `long_press` | Word definition / context menu | +| `pinch_in` | Decrease font size | +| `pinch_out` | Increase font size | +| `tilt_forward` | Next page — see [section 7](#7-accelerometer-page-flipping) | +| `tilt_backward` | Previous page — see [section 7](#7-accelerometer-page-flipping) | + +--- + +## 7. Accelerometer Page Flipping + +The BMA400 can detect device tilt and turn pages hands-free — useful while eating, +holding the device one-handed, when it is mounted on a stand, or for accessibility. + +> Tilt is **off by default** (`accelerometer.tilt_enabled: false`). Enable it in +> `hardware_config.json` after calibrating. + +### Calibration + +Calibration establishes which direction is "up" for your device: + +```bash +python examples/calibrate_accelerometer.py +``` + +1. The display shows an arrow pointing in the direction of gravity +2. Rotate the device until the arrow points up +3. Tap the screen to save +4. Calibration is written to `accelerometer_config.json` + +### Calibration File Format + +```json +{ + "up_vector": {"x": 0.0, "y": 9.8, "z": 0.0}, + "tilt_threshold": 0.3, + "debounce_time": 0.5 +} +``` + +| Key | Description | +|-----|-------------| +| `up_vector` | Gravity vector (m/s²) when the device is upright | +| `tilt_threshold` | Tilt angle threshold in radians | +| `debounce_time` | Minimum seconds between gestures | + +**Tuning `tilt_threshold`:** + +| Value | Behaviour | +|-------|-----------| +| 0.1 rad (~6°) | Very sensitive — small tilts turn pages | +| 0.3 rad (~17°) | Default | +| 0.5 rad (~29°) | Requires a deliberate, larger tilt | + +**Tuning `debounce_time`:** + +| Value | Behaviour | +|-------|-----------| +| 0.2s | Fast — flip several pages quickly | +| 0.5s | Default — prevents accidental double-flips | +| 1.0s | Slow — requires a pause between flips | + +### How Tilt Detection Works + +1. **Read** the accelerometer for (x, y, z) acceleration in m/s² +2. **Normalise** both the current gravity vector and the calibrated up vector +3. **Compute the tilt angle** by projecting gravity onto the plane perpendicular to + the up vector +4. **Compare** against `tilt_threshold` +5. **Determine direction** from the sign of the perpendicular y-component +6. **Debounce** to suppress repeat triggers + +Given a calibrated up vector `U = (ux, uy, uz)` and current gravity `G = (gx, gy, gz)`: + +```python +# Component of G along U +dot = gx*ux + gy*uy + gz*uz + +# Perpendicular component +perp = G - dot*U +perp_magnitude = |perp| + +# Tilt angle +angle = atan2(perp_magnitude, |dot|) + +# Direction +if perp_y > 0: + gesture = TILT_FORWARD # next page +else: + gesture = TILT_BACKWARD # previous page +``` + +The implementation lives in [dreader/hal_hardware.py](../dreader/hal_hardware.py) +(`get_tilt_gesture`), with the gesture types defined in +[dreader/gesture.py](../dreader/gesture.py) and handled in +[dreader/handlers/gestures.py](../dreader/handlers/gestures.py). + +### Using Tilt in Your Own Code + +```python +from dreader.hal_hardware import HardwareDisplayHAL + +hal = HardwareDisplayHAL( + width=1872, + height=1404, + enable_orientation=True, # required for tilt +) +await hal.initialize() + +if hal.load_accelerometer_calibration("accelerometer_config.json"): + print("Accelerometer calibrated!") +else: + print("No calibration found - tilt gestures disabled") +``` + +**Unified event loop (recommended)** — `get_event()` polls both touch and +accelerometer, prioritising touch: + +```python +while running: + event = await hal.get_event() + if event: + response = gesture_router.handle_touch(event) + await asyncio.sleep(0.01) +``` + +**Separate polling** — more control over each source: + +```python +while running: + touch_event = await hal.get_touch_event() + tilt_event = await hal.get_tilt_gesture() # None if uncalibrated + + if touch_event: + response = gesture_router.handle_touch(touch_event) + if tilt_event: + response = gesture_router.handle_touch(tilt_event) + + await asyncio.sleep(0.05) # ~20Hz +``` + +### Tilt API Reference + +| Method | Description | +|--------|-------------| +| `load_accelerometer_calibration(config_path="accelerometer_config.json") -> bool` | Load calibration from JSON. Returns `True` on success. | +| `async get_event() -> Optional[TouchEvent]` | **Recommended.** Next event from any input source; touch takes priority over tilt. `None` if nothing pending. | +| `async get_tilt_gesture() -> Optional[TouchEvent]` | Poll the accelerometer only. Returns a `TILT_FORWARD`/`TILT_BACKWARD` event, or `None` if there is no tilt, no calibration, or the debounce window is still open. | + +### Demos and Tests + +```bash +# Simple demo using the unified get_event() API +python examples/demo_accelerometer_simple.py ~/Books/mybook.epub + +# Full-featured demo with separate polling +python examples/demo_accelerometer_page_flip.py ~/Books/mybook.epub + +# Tests +python -m pytest tests/test_accelerometer_gestures.py -v +``` + +### Known Limitations + +1. **Orientation lock** — tilt detection assumes a fixed device orientation, so + auto-rotation can interfere with it. +2. **Movement** — walking may cause false positives. Raise the threshold or disable + tilt while moving. +3. **Calibration drift** — the accelerometer drifts over time; re-calibrate periodically. +4. **Simplified direction heuristic** — complex orientations may be misread. +5. **Single axis** — only tilt in one plane is detected; left/right tilts are not + distinguished. + +--- + +## 8. Verifying Hardware + +### Check I2C Devices + +```bash +i2cdetect -y 1 +``` + +Expected on a fully-populated device: + +``` + 0 1 2 3 4 5 6 7 8 9 a b c d e f +00: -- -- -- -- -- -- -- -- -- -- -- -- -- +10: -- -- -- -- 14 -- -- -- -- -- -- -- -- -- -- -- ← BMA400 +20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +30: -- -- -- -- -- -- -- -- 38 -- -- -- -- -- -- -- ← FT5316 +40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ← INA219 +50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- ← PCF8523 +70: -- -- -- -- -- -- -- -- +``` + +### Check SPI + +```bash +ls /dev/spi* +# Should show: /dev/spidev0.0 /dev/spidev0.1 +``` + +### Check Buttons + +```bash +sudo apt install gpiod + +gpioget gpiochip0 22 # Previous page — 1 idle, 0 when pressed +gpioget gpiochip0 27 # Next page — 1 idle, 0 when pressed +gpioget gpiochip0 21 # Power off — 0 idle, 1 when pressed (active high) +``` + +Or watch the application logs: + +```bash +python examples/run_on_hardware_config.py --verbose +``` + +Pressing a button should log something like: + +``` +Button pressed: next_page (GPIO 27) +Button event queued: next_page -> swipe_left +``` + +--- + +## 9. Troubleshooting + +### Display Not Working + +1. **Check VCOM voltage** — it must match the label on your display +2. **Check SPI is enabled** — `ls /dev/spi*` should list two devices +3. **Enable SPI if missing** — `sudo raspi-config` → Interface Options → SPI → Enable +4. **Check permissions** — `sudo usermod -a -G spi $USER`, then log out and back in +5. **Isolate the problem** — run with `--virtual` to test the software without hardware + +### Touch Not Working + +1. **Check I2C connections** — `i2cdetect -y 1` should show `0x38` +2. **Enable I2C if missing** — `sudo raspi-config` → Interface Options → I2C → Enable +3. **Confirm the module loaded** — `lsmod | grep i2c` should show `i2c_dev` and `i2c_bcm2835` +4. **Check permissions** — `sudo usermod -a -G i2c $USER`, then log out and back in +5. **Calibrate touch** — see the dreader-hal calibration docs + +### Buttons Not Working + +1. **Check the wiring polarity** — prev/next go to GND; the power button goes to 3.3V +2. **Verify the pin numbers** are BCM, not physical pin numbers +3. **Check permissions** — `sudo usermod -a -G gpio $USER`, then log out and back in +4. **Test the pin directly** with `gpioget` (see [section 8](#check-buttons)) +5. **Check for conflicts** — make sure no other program holds those GPIOs + +### Buttons Trigger Multiple Times + +1. Increase `bounce_time_ms` in the config (try 300–500ms) +2. Add a hardware debounce capacitor (0.1µF between GPIO and GND) +3. Check for loose connections + +### Button Performs the Wrong Action + +1. Check the `gesture` field for that button in `hardware_config.json` +2. Run with `--verbose` to see which gesture is actually emitted + +### Tilt Gestures Not Working + +1. **Calibrate first** — `python examples/calibrate_accelerometer.py` +2. **Enable the accelerometer** — `HardwareDisplayHAL(enable_orientation=True)` +3. **Verify the calibration loaded** — `hal.load_accelerometer_calibration()` returns `True` +4. **Confirm you are polling** — call `get_event()` or `get_tilt_gesture()` in your loop +5. **Adjust sensitivity** — tune `tilt_threshold` and `debounce_time` + +If tilting forward turns the page *backward*, the direction heuristic is inverted for +your device's mounting. Flip the comparison in `get_tilt_gesture()` in +[dreader/hal_hardware.py](../dreader/hal_hardware.py): + +```python +if perp_y < 0: # was: perp_y > 0 + gesture = AppGestureType.TILT_FORWARD +else: + gesture = AppGestureType.TILT_BACKWARD +``` + +### Import Errors + +If you see `ModuleNotFoundError` for the drivers, reinstall them all: + +```bash +cd external/dreader-hal/external +for dir in */; do pip install -e "$dir"; done +cd ../../.. +``` + +### Display Ghosting + +E-ink panels retain a faint previous image. The HAL performs a full refresh every 10 +page turns automatically to clear it. See the dreader-hal documentation for manual +refresh control. + +--- + +## 10. Performance Notes + +### E-ink Refresh Modes + +dreader-hal selects the refresh mode automatically: + +| Mode | Time | Used for | +|------|------|----------| +| **Fast** (DU) | ~200ms | Text updates | +| **Quality** (GC16) | ~1000ms | Images | +| **Full** (INIT) | ~1000ms | Every 10 pages, to clear ghosting | + +### Battery Life + +With default settings: + +- Active reading: ~10–20 hours +- Standby (display sleeping): ~1–2 weeks + +### Memory Usage + +- Base application: ~30–50MB +- Per book: ~10–30MB depending on size +- Minimum 512MB RAM recommended + +--- + +## Appendix: Programmatic Usage + +Driving the hardware HAL directly from your own script: + +```python +import asyncio +from dreader.hal_hardware import HardwareDisplayHAL +from dreader.main import DReaderApplication, AppConfig + +async def main(): + hal = HardwareDisplayHAL( + width=1872, + height=1404, + vcom=-2.06, # YOUR DISPLAY'S VCOM! + virtual_display=False, + enable_orientation=True, + enable_rtc=True, + enable_power_monitor=True, + ) + + config = AppConfig( + display_hal=hal, + library_path="/home/pi/Books", + page_size=(1872, 1404), + ) + + app = DReaderApplication(config) + + try: + await hal.initialize() + await app.start() + + while app.is_running(): + event = await hal.get_touch_event() + if event: + await app.handle_touch(event) + await asyncio.sleep(0.01) + + finally: + await app.shutdown() + await hal.cleanup() + +if __name__ == '__main__': + asyncio.run(main()) +``` + +--- + +## See Also + +- [README.md](../README.md) — application features and usage +- [ARCHITECTURE.md](ARCHITECTURE.md) — system architecture and design +- [HAL_IMPLEMENTATION_SPEC.md](HAL_IMPLEMENTATION_SPEC.md) — writing a HAL for other hardware +- [examples/](../examples/) — runnable examples +- [external/dreader-hal/README.md](../external/dreader-hal/README.md) — HAL library details + +### Support + +- Hardware issues: [dreader-hal issues](https://gitea.tourolle.paris/dtourolle/dreader-hal/issues) +- Application issues: [dreader-application issues](https://gitea.tourolle.paris/dtourolle/dreader-application/issues) diff --git a/REQUIREMENTS.md b/docs/REQUIREMENTS.md similarity index 100% rename from REQUIREMENTS.md rename to docs/REQUIREMENTS.md diff --git a/install_hardware_drivers.sh b/install_hardware_drivers.sh index 1101215..8261d8f 100755 --- a/install_hardware_drivers.sh +++ b/install_hardware_drivers.sh @@ -71,7 +71,7 @@ echo " ✓ PyPCF8523 (RTC)" echo " ✓ pi_ina219 (power monitor)" echo "" echo "Next steps:" -echo " 1. Wire up your hardware according to HARDWARE_SETUP.md" +echo " 1. Wire up your hardware according to docs/HARDWARE.md" echo " 2. Check your display's VCOM voltage (on label)" echo " 3. Run: python examples/run_on_hardware.py /path/to/books --vcom YOUR_VCOM" echo "" diff --git a/debug_overlay_links.py b/scripts/debug/debug_overlay_links.py similarity index 95% rename from debug_overlay_links.py rename to scripts/debug/debug_overlay_links.py index 8cd7a03..9fac8b9 100644 --- a/debug_overlay_links.py +++ b/scripts/debug/debug_overlay_links.py @@ -6,7 +6,7 @@ Shows where clickable links are located. import sys from pathlib import Path -sys.path.insert(0, str(Path(__file__).parent)) +sys.path.insert(0, str(Path(__file__).parents[2])) from dreader.application import EbookReader from dreader.overlays.settings import SettingsOverlay @@ -53,7 +53,7 @@ def visualize_settings_overlay(): reader = EbookReader(page_size=(800, 1200)) # Load a test book - test_book = Path(__file__).parent / "tests" / "data" / "library-epub" / "pg11-images-3.epub" + test_book = Path(__file__).parents[2] / "tests" / "data" / "library-epub" / "pg11-images-3.epub" if not test_book.exists(): print(f"Test book not found: {test_book}") return @@ -133,7 +133,7 @@ def visualize_navigation_overlay(): reader = EbookReader(page_size=(800, 1200)) # Load a test book - test_book = Path(__file__).parent / "tests" / "data" / "library-epub" / "pg11-images-3.epub" + test_book = Path(__file__).parents[2] / "tests" / "data" / "library-epub" / "pg11-images-3.epub" if not test_book.exists(): print(f"Test book not found: {test_book}") return diff --git a/debug_previous_page.py b/scripts/debug/debug_previous_page.py similarity index 98% rename from debug_previous_page.py rename to scripts/debug/debug_previous_page.py index a985651..b261598 100644 --- a/debug_previous_page.py +++ b/scripts/debug/debug_previous_page.py @@ -6,7 +6,7 @@ Debug previous_page issue. import sys from pathlib import Path -sys.path.insert(0, str(Path(__file__).parent)) +sys.path.insert(0, str(Path(__file__).parents[2])) from dreader.application import EbookReader diff --git a/test_pagination_visual.py b/scripts/debug/visualize_pagination.py similarity index 94% rename from test_pagination_visual.py rename to scripts/debug/visualize_pagination.py index 56de105..9b2b605 100644 --- a/test_pagination_visual.py +++ b/scripts/debug/visualize_pagination.py @@ -8,7 +8,7 @@ from dreader import LibraryManager def test_pagination(): """Test pagination with actual library""" - library_path = Path(__file__).parent / 'tests' / 'data' / 'library-epub' + library_path = Path(__file__).parents[2] / 'tests' / 'data' / 'library-epub' # Create library manager (default books_per_page=6) library = LibraryManager( diff --git a/scripts/update_pyweblayout.sh b/scripts/update_pyweblayout.sh new file mode 100755 index 0000000..36ff2b3 --- /dev/null +++ b/scripts/update_pyweblayout.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Update pyWebLayout to the latest version from git +# +# This script updates pyWebLayout to fix compatibility issues + +set -e # Exit on error + +echo "================================" +echo "pyWebLayout Update Script" +echo "================================" +echo "" + +# Check if we're in a virtual environment +if [ -z "$VIRTUAL_ENV" ]; then + echo "⚠️ Warning: No virtual environment detected!" + echo "It's recommended to activate your virtual environment first:" + echo " source venv/bin/activate" + echo "" + read -p "Continue anyway? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +echo "Updating pyWebLayout from git repository..." +echo "" + +# Upgrade pyWebLayout from the git repository +pip install --upgrade git+https://gitea.tourolle.paris/dtourolle/pyWebLayout@master + +echo "" +echo "================================" +echo "Update Complete!" +echo "================================" +echo "" +echo "pyWebLayout has been updated to the latest version." +echo "You can now run the application normally." +echo "" diff --git a/setup_rpi.py b/setup_rpi.py index c42f4ed..7db5d67 100755 --- a/setup_rpi.py +++ b/setup_rpi.py @@ -381,7 +381,7 @@ def main(): if not i2c_devices: print_warning("No I2C devices detected. Check your wiring.") - print("See HARDWARE_SETUP.md for wiring instructions.") + print("See docs/HARDWARE.md for wiring instructions.") # Step 3: Set up permissions print_step(3, "Setting Up User Permissions") diff --git a/tests/test_backward_nav_minimal.py b/tests/test_backward_nav_minimal.py index ae799f6..62c331c 100644 --- a/tests/test_backward_nav_minimal.py +++ b/tests/test_backward_nav_minimal.py @@ -1,9 +1,12 @@ """ -Minimal reproduction test for backward navigation bug. +Regression test for backward navigation onto the cover page. -BUG: Backward navigation cannot reach block_index=0 from block_index=1. +Going forward off the cover and then back again must return to block_index=0. -This is a pyWebLayout issue, not a dreader-application issue. +Previously it left current_position at the first content block (1) while flagging +the cover as displayed, so the cover had two different internal representations. +Since current_position is what gets persisted, saving while on the cover reopened +the book past it. Fixed in pyWebLayout's EreaderLayoutManager.previous_page(). """ import unittest @@ -31,14 +34,12 @@ class TestBackwardNavigationBug(unittest.TestCase): def test_minimal_backward_navigation_bug(self): """ - MINIMAL REPRODUCTION: + MINIMAL CASE: 1. Start at block_index=0 2. Go forward once (to block_index=1) 3. Go backward once - 4. BUG: Lands at block_index=1 instead of block_index=0 - - This proves backward navigation cannot reach the first block. + 4. Should land back at block_index=0 """ reader = EbookReader( page_size=(800, 1000), @@ -64,18 +65,13 @@ class TestBackwardNavigationBug(unittest.TestCase): pos_final = reader.manager.current_position.copy() print(f"3. After previous_page(): block_index={pos_final.block_index}") - # THE BUG: This assertion will fail print(f"\nEXPECTED: block_index=0") print(f"ACTUAL: block_index={pos_final.block_index}") - if pos_final.block_index != 0: - print("\n❌ BUG CONFIRMED: Cannot navigate backward to block_index=0") - print(" This is a pyWebLayout bug in the previous_page() method.") - self.assertEqual( pos_final.block_index, 0, - "BUG: Backward navigation from block 1 should return to block 0" + "Backward navigation from block 1 should return to block 0" ) reader.close() diff --git a/tests/test_backward_navigation_detailed.py b/tests/test_backward_navigation_detailed.py index 4fe6707..09ac519 100644 --- a/tests/test_backward_navigation_detailed.py +++ b/tests/test_backward_navigation_detailed.py @@ -1,12 +1,13 @@ """ -Detailed test for backward navigation issues. +Detailed regression tests for backward navigation. -This test explores the backward navigation behavior more thoroughly -to understand if the issue is: +These cover the ways backward navigation has broken before: 1. Complete failure (previous_page returns None) 2. Imprecise positioning (lands on wrong block) -3. Only occurs after resume -4. Occurs during continuous navigation +3. Failure only after resuming from a saved position +4. Failure during continuous navigation + +See test_backward_nav_minimal.py for the minimal cover-page case. """ import unittest