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>
854 lines
24 KiB
Markdown
854 lines
24 KiB
Markdown
# 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)
|