Add portal web simulator, SPI bridges, and Pico firmware updates.

Bring the five-panel hex portal online with a browser 3D/schematic preview, Pi SPI backends, and renamed multi-panel Pico UDP firmware.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-30 14:54:51 +12:00
parent d5cab2efdf
commit 5094c7bcee
78 changed files with 62251 additions and 832 deletions

103
README.md
View File

@@ -1,38 +1,86 @@
# portal
WS2812 LED control for Raspberry Pi 5 and Pico panel adapters.
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 adapters.
## Panel IPs
Five panels on `10.1.1.10``10.1.1.14` (panel 0 → `.10`, panel 4 → `.14`). Examples drive **all 5** by default.
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 `leds/array_config.py`:
| Panel | IP | Size | LEDs | Flash |
|-------|-----|------|------|-------|
| 0 | 10.1.1.10 | 9×39 | 351 | `make deploy PANEL_ID=0 NUM_LEDS=351` |
| 1 | 10.1.1.11 | 9×45 | 405 | `make deploy PANEL_ID=1 NUM_LEDS=405` |
| 2 | 10.1.1.12 | 9×45 | 405 | `make deploy PANEL_ID=2 NUM_LEDS=405` |
| 3 | 10.1.1.13 | 9×45 | 405 | `make deploy PANEL_ID=3 NUM_LEDS=405` |
| 4 | 10.1.1.14 | 9×39? | 351 | `make deploy PANEL_ID=4 NUM_LEDS=351` |
| 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 `leds/array_config.py`).
Panel 4 may be **38 or 39** wide — find the exact width:
```bash
make deploy PANEL_ID=4 NUM_LEDS=387 # 43 cols max for probe
make deploy PANEL_ID=4
pipenv run python examples/panel_sync_test.py --test width --panel-index 4
```
Last column that lights → set `PANEL_WIDTH_BY_INDEX[4]` in `leds/array_config.py` to **column + 1**, then re-flash with `NUM_LEDS=width×9` (342 for 38, 351 for 39).
Last column that lights → set `PANEL_WIDTH_BY_INDEX[4]` in `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.
## Examples
### Portal web simulator
Preview all five panels in the browser (3D interior view + flat layout map). Uses the same Python animations as the hardware.
```bash
pipenv install fastapi "uvicorn[standard]"
pipenv run python examples/portal_simulator.py
# open http://localhost:8765/
```
Dev mode (default): **uvicorn reload** for Python changes, **browser auto-reload** when `web/` files change. Disable with `--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 simulator 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 `web/js/`:
| Module | Role |
|--------|------|
| `<portal-app>` | Root layout, frame streaming |
| `<portal-controls>` | Sidebar settings |
| `<portal-viewport>` | Three.js 3D scene |
| `<portal-schematic>` | Flat layout map |
| `<portal-panel>` | Single LED matrix canvas |
### Pico panel (UDP)
WS2812 defaults: **GP28** (strip 0) and **GP27** (strip 1). Override over UDP:
```bash
pipenv run python examples/panel_color_test.py --panel-index 0 --pins 28:405,27:351
```
| Command | What it does |
|---------|----------------|
| `pipenv run python examples/panel_rgb_cycle.py` | Red / green / blue on all 5 panels |
@@ -50,17 +98,40 @@ Single panel only:
pipenv run python examples/panel_animations.py --panel-index 1 # 9×45 @ 10.1.1.11
```
### Pi direct (SPI on GPIO 10)
### Pi SPI (bench / single matrix)
Optional local SPI tests via [`rpi5-ws2812`](https://github.com/niklasr22/rpi5-ws2812) on GPIO 10 — not used for the five portal panels.
405 LEDs need a ~10 KiB SPI frame. Default `spidev` bufsiz (4096) only updates ~169 LEDs — run once:
```bash
pipenv run python examples/setup_spi_bufsiz.py --install # sudo
```
| Command | What it does |
|---------|----------------|
| `pipenv run python examples/animations.py` | All animations on matrix |
| `pipenv run python examples/spi_rgb_test.py` | Red / green / blue over SPI |
| `pipenv run python examples/spi_panel_test.py` | Corners, rows, chase — verify all LEDs |
| `pipenv run python examples/animations.py` | All animations on local matrix |
| `pipenv run python examples/matrix_demo.py` | Rainbow only |
| `pipenv run python examples/led_demo.py` | Dual-strip chase + rainbow |
### Firmware
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 simulator stays logical RGB.
```python
# MicroPython on Pico — this is the reference:
np.fill((255, 0, 0)); np.write() # red
```
```bash
make deploy PANEL_ID=0 NUM_LEDS=351
make monitor
pipenv run python examples/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
```