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:
@@ -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
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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)
|
|
||||||
@@ -506,7 +506,7 @@ The repository includes a pre-configured **[hardware_config.json](hardware_confi
|
|||||||
- **Display**: 1872×1404 IT8951 e-ink
|
- **Display**: 1872×1404 IT8951 e-ink
|
||||||
- **I2C Bus**: GPIO 2/3 (touch, sensors, RTC, power)
|
- **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
|
### HAL Architecture
|
||||||
|
|
||||||
@@ -524,14 +524,15 @@ app = DReaderApplication(config)
|
|||||||
- **HardwareDisplayHAL** - Real e-ink hardware (IT8951 + dreader-hal)
|
- **HardwareDisplayHAL** - Real e-ink hardware (IT8951 + dreader-hal)
|
||||||
- **PygameDisplayHAL** - Desktop testing with pygame window
|
- **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
|
## Documentation
|
||||||
|
|
||||||
- [README.md](README.md) - This file, main project documentation
|
- [README.md](README.md) - This file, main project documentation
|
||||||
- [REQUIREMENTS.md](REQUIREMENTS.md) - Application requirements specification
|
- [docs/HARDWARE.md](docs/HARDWARE.md) - Wiring, configuration, and running on real hardware
|
||||||
- [ARCHITECTURE.md](ARCHITECTURE.md) - System architecture and design details
|
- [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md) - Application requirements specification
|
||||||
- [HAL_IMPLEMENTATION_SPEC.md](HAL_IMPLEMENTATION_SPEC.md) - Hardware integration guide
|
- [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
|
## Performance
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Think of it as:
|
|||||||
|
|
||||||
### 1. EbookReader (Main Coordinator)
|
### 1. EbookReader (Main Coordinator)
|
||||||
|
|
||||||
**Location**: [application.py](dreader/application.py)
|
**Location**: [application.py](../dreader/application.py)
|
||||||
|
|
||||||
The central orchestrator that coordinates all subsystems:
|
The central orchestrator that coordinates all subsystems:
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ class EbookReader:
|
|||||||
|
|
||||||
### 2. Document Manager
|
### 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.
|
Handles document loading and metadata extraction.
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ Handles document loading and metadata extraction.
|
|||||||
|
|
||||||
### 3. Settings Manager
|
### 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.
|
Manages all display settings with persistence.
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ Manages all display settings with persistence.
|
|||||||
|
|
||||||
### 4. Gesture Router
|
### 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.
|
Routes touch events to appropriate handlers based on application state.
|
||||||
|
|
||||||
@@ -128,13 +128,13 @@ Touch Event → GestureRouter
|
|||||||
|
|
||||||
### 5. Overlay System
|
### 5. Overlay System
|
||||||
|
|
||||||
**Location**: [overlays/](dreader/overlays/)
|
**Location**: [overlays/](../dreader/overlays/)
|
||||||
|
|
||||||
The overlay system provides modal UI panels over the reading content.
|
The overlay system provides modal UI panels over the reading content.
|
||||||
|
|
||||||
#### Overlay Manager
|
#### Overlay Manager
|
||||||
|
|
||||||
**Location**: [overlays/base.py](dreader/overlays/base.py)
|
**Location**: [overlays/base.py](../dreader/overlays/base.py)
|
||||||
|
|
||||||
Core overlay rendering and compositing infrastructure.
|
Core overlay rendering and compositing infrastructure.
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ Core overlay rendering and compositing infrastructure.
|
|||||||
|
|
||||||
#### Navigation Overlay
|
#### Navigation Overlay
|
||||||
|
|
||||||
**Location**: [overlays/navigation.py](dreader/overlays/navigation.py)
|
**Location**: [overlays/navigation.py](../dreader/overlays/navigation.py)
|
||||||
|
|
||||||
Unified overlay with tabbed interface for:
|
Unified overlay with tabbed interface for:
|
||||||
- **Contents Tab**: Chapter navigation (TOC)
|
- **Contents Tab**: Chapter navigation (TOC)
|
||||||
@@ -161,7 +161,7 @@ Unified overlay with tabbed interface for:
|
|||||||
|
|
||||||
#### Settings Overlay
|
#### 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.
|
Interactive settings panel with real-time preview.
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ Interactive settings panel with real-time preview.
|
|||||||
|
|
||||||
### 6. Library Manager
|
### 6. Library Manager
|
||||||
|
|
||||||
**Location**: [library.py](dreader/library.py)
|
**Location**: [library.py](../dreader/library.py)
|
||||||
|
|
||||||
Manages the library browsing experience.
|
Manages the library browsing experience.
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ Manages the library browsing experience.
|
|||||||
|
|
||||||
### 7. State Manager
|
### 7. State Manager
|
||||||
|
|
||||||
**Location**: [state.py](dreader/state.py)
|
**Location**: [state.py](../dreader/state.py)
|
||||||
|
|
||||||
Persistent application state across sessions.
|
Persistent application state across sessions.
|
||||||
|
|
||||||
@@ -426,18 +426,18 @@ class ActionType(Enum):
|
|||||||
|
|
||||||
To add a new overlay type:
|
To add a new overlay type:
|
||||||
|
|
||||||
1. Define new `OverlayState` enum value in [state.py](dreader/state.py#L27-L33)
|
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)
|
2. Create HTML generator in [html_generator.py](../dreader/html_generator.py)
|
||||||
3. Add overlay class in `overlays/` directory
|
3. Add overlay class in `overlays/` directory
|
||||||
4. Implement open/close methods in [overlay manager](dreader/overlays/base.py)
|
4. Implement open/close methods in [overlay manager](../dreader/overlays/base.py)
|
||||||
5. Add gesture handling in [application.py](dreader/application.py)
|
5. Add gesture handling in [application.py](../dreader/application.py)
|
||||||
|
|
||||||
### Custom Gesture Handlers
|
### Custom Gesture Handlers
|
||||||
|
|
||||||
To add custom gestures:
|
To add custom gestures:
|
||||||
|
|
||||||
1. Define gesture type in [gesture.py](dreader/gesture.py)
|
1. Define gesture type in [gesture.py](../dreader/gesture.py)
|
||||||
2. Add handler in [gestures.py](dreader/handlers/gestures.py)
|
2. Add handler in [gestures.py](../dreader/handlers/gestures.py)
|
||||||
3. Define action type for response
|
3. Define action type for response
|
||||||
4. Update gesture router logic
|
4. Update gesture router logic
|
||||||
|
|
||||||
@@ -486,10 +486,10 @@ Examples:
|
|||||||
### Example-Based Testing
|
### Example-Based Testing
|
||||||
|
|
||||||
Working examples demonstrate full integration:
|
Working examples demonstrate full integration:
|
||||||
- [simple_ereader_example.py](examples/simple_ereader_example.py)
|
- [simple_ereader_example.py](../examples/simple_ereader_example.py)
|
||||||
- [library_reading_integration.py](examples/library_reading_integration.py)
|
- [library_reading_integration.py](../examples/library_reading_integration.py)
|
||||||
- [navigation_overlay_example.py](examples/navigation_overlay_example.py)
|
- [navigation_overlay_example.py](../examples/navigation_overlay_example.py)
|
||||||
- [demo_settings_overlay.py](examples/demo_settings_overlay.py)
|
- [demo_settings_overlay.py](../examples/demo_settings_overlay.py)
|
||||||
|
|
||||||
## Design Patterns
|
## Design Patterns
|
||||||
|
|
||||||
@@ -548,5 +548,5 @@ Enable third-party extensions:
|
|||||||
|
|
||||||
- [pyWebLayout Documentation](https://gitea.tourolle.paris/dtourolle/pyWebLayout)
|
- [pyWebLayout Documentation](https://gitea.tourolle.paris/dtourolle/pyWebLayout)
|
||||||
- [REQUIREMENTS.md](REQUIREMENTS.md) - Detailed feature specifications
|
- [REQUIREMENTS.md](REQUIREMENTS.md) - Detailed feature specifications
|
||||||
- [README.md](README.md) - User-facing documentation
|
- [README.md](../README.md) - User-facing documentation
|
||||||
- [examples/](examples/) - Working code examples
|
- [examples/](../examples/) - Working code examples
|
||||||
@@ -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 <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 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)
|
||||||
@@ -71,7 +71,7 @@ echo " ✓ PyPCF8523 (RTC)"
|
|||||||
echo " ✓ pi_ina219 (power monitor)"
|
echo " ✓ pi_ina219 (power monitor)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next steps:"
|
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 " 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 " 3. Run: python examples/run_on_hardware.py /path/to/books --vcom YOUR_VCOM"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Shows where clickable links are located.
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
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.application import EbookReader
|
||||||
from dreader.overlays.settings import SettingsOverlay
|
from dreader.overlays.settings import SettingsOverlay
|
||||||
@@ -53,7 +53,7 @@ def visualize_settings_overlay():
|
|||||||
reader = EbookReader(page_size=(800, 1200))
|
reader = EbookReader(page_size=(800, 1200))
|
||||||
|
|
||||||
# Load a test book
|
# 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():
|
if not test_book.exists():
|
||||||
print(f"Test book not found: {test_book}")
|
print(f"Test book not found: {test_book}")
|
||||||
return
|
return
|
||||||
@@ -133,7 +133,7 @@ def visualize_navigation_overlay():
|
|||||||
reader = EbookReader(page_size=(800, 1200))
|
reader = EbookReader(page_size=(800, 1200))
|
||||||
|
|
||||||
# Load a test book
|
# 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():
|
if not test_book.exists():
|
||||||
print(f"Test book not found: {test_book}")
|
print(f"Test book not found: {test_book}")
|
||||||
return
|
return
|
||||||
@@ -6,7 +6,7 @@ Debug previous_page issue.
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
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.application import EbookReader
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ from dreader import LibraryManager
|
|||||||
|
|
||||||
def test_pagination():
|
def test_pagination():
|
||||||
"""Test pagination with actual library"""
|
"""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)
|
# Create library manager (default books_per_page=6)
|
||||||
library = LibraryManager(
|
library = LibraryManager(
|
||||||
Executable
+39
@@ -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
@@ -381,7 +381,7 @@ def main():
|
|||||||
|
|
||||||
if not i2c_devices:
|
if not i2c_devices:
|
||||||
print_warning("No I2C devices detected. Check your wiring.")
|
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
|
# Step 3: Set up permissions
|
||||||
print_step(3, "Setting Up User Permissions")
|
print_step(3, "Setting Up User Permissions")
|
||||||
|
|||||||
@@ -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
|
import unittest
|
||||||
@@ -31,14 +34,12 @@ class TestBackwardNavigationBug(unittest.TestCase):
|
|||||||
|
|
||||||
def test_minimal_backward_navigation_bug(self):
|
def test_minimal_backward_navigation_bug(self):
|
||||||
"""
|
"""
|
||||||
MINIMAL REPRODUCTION:
|
MINIMAL CASE:
|
||||||
|
|
||||||
1. Start at block_index=0
|
1. Start at block_index=0
|
||||||
2. Go forward once (to block_index=1)
|
2. Go forward once (to block_index=1)
|
||||||
3. Go backward once
|
3. Go backward once
|
||||||
4. BUG: Lands at block_index=1 instead of block_index=0
|
4. Should land back at block_index=0
|
||||||
|
|
||||||
This proves backward navigation cannot reach the first block.
|
|
||||||
"""
|
"""
|
||||||
reader = EbookReader(
|
reader = EbookReader(
|
||||||
page_size=(800, 1000),
|
page_size=(800, 1000),
|
||||||
@@ -64,18 +65,13 @@ class TestBackwardNavigationBug(unittest.TestCase):
|
|||||||
pos_final = reader.manager.current_position.copy()
|
pos_final = reader.manager.current_position.copy()
|
||||||
print(f"3. After previous_page(): block_index={pos_final.block_index}")
|
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"\nEXPECTED: block_index=0")
|
||||||
print(f"ACTUAL: block_index={pos_final.block_index}")
|
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(
|
self.assertEqual(
|
||||||
pos_final.block_index,
|
pos_final.block_index,
|
||||||
0,
|
0,
|
||||||
"BUG: Backward navigation from block 1 should return to block 0"
|
"Backward navigation from block 1 should return to block 0"
|
||||||
)
|
)
|
||||||
|
|
||||||
reader.close()
|
reader.close()
|
||||||
|
|||||||
@@ -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
|
These cover the ways backward navigation has broken before:
|
||||||
to understand if the issue is:
|
|
||||||
1. Complete failure (previous_page returns None)
|
1. Complete failure (previous_page returns None)
|
||||||
2. Imprecise positioning (lands on wrong block)
|
2. Imprecise positioning (lands on wrong block)
|
||||||
3. Only occurs after resume
|
3. Failure only after resuming from a saved position
|
||||||
4. Occurs during continuous navigation
|
4. Failure during continuous navigation
|
||||||
|
|
||||||
|
See test_backward_nav_minimal.py for the minimal cover-page case.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|||||||
Reference in New Issue
Block a user