Move the app under src/, add ring-spanning patterns with shared fonts and beat-driven motion, and wire global audio settings into the web UI. Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
5.8 KiB
Markdown
164 lines
5.8 KiB
Markdown
# portal
|
||
|
||
Hexagonal LED portal: five WS2812 matrix panels on Pico UDP adapters, plus a bare floor.
|
||
|
||
Reference: [photo of the physical portal](https://technical.kiwi/images/portal/IMG_20241029_220222.jpg) — upright flat-top hex arch, dark frame, red LED grids on each face.
|
||
|
||
```
|
||
___[2]___
|
||
/ \
|
||
[1] [3]
|
||
| |
|
||
[0] [4]
|
||
\___________/
|
||
floor
|
||
```
|
||
|
||
WS2812 control for Raspberry Pi 5 (orchestrator) and Pico panel firmware.
|
||
|
||
| Path | What |
|
||
|------|------|
|
||
| [`src/`](src/) | Python app, web UI, patterns |
|
||
| [`test/`](test/) | Unit tests and hardware panel scripts |
|
||
| [`firmware/`](firmware/) | Pico SDK panel firmware |
|
||
|
||
## Panel IPs
|
||
|
||
Five Pico panels on `10.1.1.10`–`10.1.1.14`. Examples drive **all 5** by default.
|
||
|
||
## Panel sizes
|
||
|
||
Panels are **9 rows** tall; width varies by panel. Edit `PANEL_WIDTH_BY_INDEX` in [`src/leds/array_config.py`](src/leds/array_config.py):
|
||
|
||
| Panel | Face | IP | Size | LEDs | Firmware |
|
||
|-------|------|-----|------|------|----------|
|
||
| 0 | bottom-left | 10.1.1.10 | 9×39 | 351 | `make deploy PANEL_ID=0` |
|
||
| 1 | top-left | 10.1.1.11 | 9×45 | 405 | `make deploy PANEL_ID=1` |
|
||
| 2 | **top** | 10.1.1.12 | 9×45 | 405 | `make deploy PANEL_ID=2` |
|
||
| 3 | top-right | 10.1.1.13 | 9×45 | 405 | `make deploy PANEL_ID=3` |
|
||
| 4 | bottom-right | 10.1.1.14 | 9×39? | 351 | `make deploy PANEL_ID=4` |
|
||
|
||
Panels **0→1→2→3→4** run clockwise from bottom-left (`PORTAL_PANEL_ORDER_CLOCKWISE` in `src/leds/array_config.py`).
|
||
|
||
Panel 4 may be **38 or 39** wide — find the exact width:
|
||
|
||
```bash
|
||
make deploy PANEL_ID=4
|
||
pipenv run python test/panel_sync_test.py --test width --panel-index 4
|
||
```
|
||
|
||
Last column that lights → set `PANEL_WIDTH_BY_INDEX[4]` in `src/leds/array_config.py` to **column + 1** (342 LEDs for 38, 351 for 39). Firmware takes pixel count from each UDP frame — no re-flash for width changes.
|
||
|
||
Animations render at each panel's own width automatically.
|
||
|
||
## Patterns
|
||
|
||
Each visual lives in its own file under [`src/patterns/`](src/patterns/). Drop a new `.py` file there and the web UI picks it up without restarting (hot-load). Every file is also a standalone CLI:
|
||
|
||
```bash
|
||
pipenv run python src/patterns/scanner.py
|
||
pipenv run python src/patterns/scanner.py --brightness 0.3 --speed 2 --beam ff0000
|
||
pipenv run python src/patterns/rainbow.py --panel-index 1
|
||
```
|
||
|
||
A pattern subclass declares optional named colours (shown as colour pickers in the UI) plus brightness and speed (time scale, independent of the 15 fps UDP cap):
|
||
|
||
```python
|
||
from leds.pattern import Color, Pattern, run_cli
|
||
|
||
class Scanner(Pattern):
|
||
id = "scanner"
|
||
label = "Scanner"
|
||
fps = 35
|
||
colors = {"beam": Color("#ff0000")}
|
||
|
||
def draw(self, surface, t, params):
|
||
...
|
||
|
||
if __name__ == "__main__":
|
||
run_cli(Scanner)
|
||
```
|
||
|
||
`t` is elapsed seconds × speed. Hardware brightness is applied when frames are sent, not inside `draw`.
|
||
|
||
## Examples
|
||
|
||
### Portal web controller
|
||
|
||
Drive all five Pico panels from the browser. Starts black until you select a preset.
|
||
|
||
```bash
|
||
pipenv install fastapi "uvicorn[standard]"
|
||
pipenv run dev
|
||
# open http://localhost:8765/
|
||
```
|
||
|
||
Play by tapping **preset buttons**. Switch to **Edit mode** to add or edit presets — each preset is based on a pattern (colours, brightness, speed). **Try** previews without saving; **Save** persists to `db/presets.json`. Saving a file in `src/patterns/` updates the pattern list without a full page reload.
|
||
|
||
`pipenv run dev` enables **uvicorn reload** for `src/leds/` + `src/static/` and **browser auto-reload** when `src/static/` files change. Pattern files are hot-loaded in-process (not via uvicorn). Disable with `pipenv run python src/main.py --no-reload` or `--no-browser-reload`.
|
||
|
||
Options: `--host 0.0.0.0 --port 8765` to view from another device on the network.
|
||
|
||
Open via the server URL (`http://…:8765/`) — do not open `index.html` directly from the filesystem (`file://`), or module imports will fail.
|
||
|
||
Frontend uses **Web Components** and ES modules under `src/static/js/`:
|
||
|
||
| Module | Role |
|
||
|--------|------|
|
||
| `<portal-app>` | Root layout, status, hot-load events |
|
||
| `<portal-controls>` | Preset buttons, Run/Edit mode |
|
||
| `<portal-preset-editor>` | Create/edit preset from a pattern |
|
||
|
||
### Pico panel (UDP)
|
||
|
||
WS2812 defaults: **GP28** (strip 0) and **GP27** (strip 1). Override over UDP:
|
||
|
||
```bash
|
||
pipenv run python test/panel_color_test.py --panel-index 0 --pins 28:405,27:351
|
||
```
|
||
|
||
| Command | What it does |
|
||
|---------|----------------|
|
||
| `pipenv run python test/panel_rgb_cycle.py` | Red / green / blue on all 5 panels |
|
||
| `pipenv run python test/panel_color_test.py` | One-shot RGB + white test |
|
||
| `pipenv run python test/panel_test.py` | Layout tests (corners, rows, chase) |
|
||
| `pipenv run python test/panel_sync_test.py` | Positioning + sync across all panels |
|
||
| `pipenv run python test/panel_animations.py` | All patterns on all panels |
|
||
| `pipenv run python test/panel_animations.py rolling` | One pattern (playlist runner) |
|
||
| `pipenv run python src/patterns/rolling.py` | One pattern, standalone |
|
||
| `pipenv run python test/panel_text.py` | Show your name |
|
||
|
||
Single panel only:
|
||
|
||
```bash
|
||
pipenv run python test/panel_animations.py --panel-index 1 # 9×45 @ 10.1.1.11
|
||
```
|
||
|
||
### Pi 5 overlays (bench / local strips)
|
||
|
||
Optional local PIO tests on GPIO — not used for the five portal panels:
|
||
|
||
```bash
|
||
pipenv run python test/setup_pi5_leds.py --install
|
||
```
|
||
|
||
Colors use logical **RGB** everywhere (`(255,0,0)` = red). Pico firmware remaps to **GRB** on the wire; Pi SPI uses the same swap when needed. The web preview stays logical RGB.
|
||
|
||
```python
|
||
# Logical RGB — firmware converts to GRB on the wire:
|
||
# (255, 0, 0) is red
|
||
```
|
||
|
||
```bash
|
||
pipenv run python test/panel_rgb_cycle.py # same colors over UDP
|
||
```
|
||
|
||
### Firmware
|
||
|
||
Pico SDK sources live under `firmware/pico/` (see that README for the UDP protocol).
|
||
|
||
```bash
|
||
make deploy PANEL_ID=0
|
||
make reset && make monitor
|
||
```
|