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