chore: tidy repository layout and consolidate hardware docs

Root had grown to 30 entries, most of it generated output and one-off
scripts. It now holds 12.

Docs:
- Merge HARDWARE_SETUP, HARDWARE_PINOUT, GPIO_BUTTONS and
  ACCELEROMETER_PAGE_FLIP into a single docs/HARDWARE.md
- Move ARCHITECTURE, REQUIREMENTS and HAL_IMPLEMENTATION_SPEC to docs/,
  leaving only README.md in the root
- Update cross-references in README, setup_rpi.py and
  install_hardware_drivers.sh, and re-base ARCHITECTURE's source links

Merging surfaced three errors, reconciled against hardware_config.json:
- The power button was documented as GPIO 21 to GND with a pull-up. It is
  active high (pull_up: false); wiring it as documented reads as
  permanently pressed.
- GPIO_BUTTONS used GPIO 23 for next-page; it is GPIO 27.
- The FT5316 INT pin was routed to GPIO 27, which collides with the
  next-page button. Now documented as a conflict.

Scripts:
- Move debug_overlay_links.py and debug_previous_page.py to scripts/debug/
- Rename test_pagination_visual.py to scripts/debug/visualize_pagination.py;
  it renders output and asserts nothing, so the test_ prefix was misleading
- Fix the __file__-relative paths these three relied on
- Track update_pyweblayout.sh under scripts/

Tests:
- Reword the backward-navigation tests, which described a pyWebLayout bug
  that is now fixed, as regression tests

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 22:27:53 +02:00
co-authored by Claude Opus 5
parent c62b8eff38
commit f7d59d025f
17 changed files with 942 additions and 1450 deletions
-410
View File
@@ -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.
-286
View File
@@ -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
-230
View File
@@ -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
-472
View File
@@ -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)
+6 -5
View File
@@ -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
+22 -22
View File
@@ -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
+853
View File
@@ -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):** ~12W
- **Total (sleep):** ~50100mW
---
## 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 711 | 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 <volts>` | 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 <mAh>` | 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 300500ms)
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: ~1020 hours
- Standby (display sleeping): ~12 weeks
### Memory Usage
- Base application: ~3050MB
- Per book: ~1030MB 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)
+1 -1
View File
@@ -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 ""
@@ -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
@@ -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
@@ -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(
+39
View File
@@ -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 ""
+1 -1
View File
@@ -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")
+9 -13
View File
@@ -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()
+6 -5
View File
@@ -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