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:
@@ -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)
|
||||
Reference in New Issue
Block a user