Ship the portal FastAPI controller with presets, sequences, and live audio.
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>
This commit is contained in:
@@ -14,7 +14,13 @@ Reference: [photo of the physical portal](https://technical.kiwi/images/portal/I
|
||||
floor
|
||||
```
|
||||
|
||||
WS2812 control for Raspberry Pi 5 (orchestrator) and Pico panel adapters.
|
||||
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
|
||||
|
||||
@@ -22,7 +28,7 @@ Five Pico panels on `10.1.1.10`–`10.1.1.14`. Examples drive **all 5** by defau
|
||||
|
||||
## Panel sizes
|
||||
|
||||
Panels are **9 rows** tall; width varies by panel. Edit `PANEL_WIDTH_BY_INDEX` in `leds/array_config.py`:
|
||||
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 |
|
||||
|-------|------|-----|------|------|----------|
|
||||
@@ -32,99 +38,119 @@ Panels are **9 rows** tall; width varies by panel. Edit `PANEL_WIDTH_BY_INDEX` i
|
||||
| 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`).
|
||||
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 examples/panel_sync_test.py --test width --panel-index 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 `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.
|
||||
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 simulator
|
||||
### Portal web controller
|
||||
|
||||
Preview all five panels in the browser (3D interior view + flat layout map). Uses the same Python animations as the hardware.
|
||||
Drive all five Pico panels from the browser. Starts black until you select a preset.
|
||||
|
||||
```bash
|
||||
pipenv install fastapi "uvicorn[standard]"
|
||||
pipenv run python examples/portal_simulator.py
|
||||
pipenv run dev
|
||||
# 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`.
|
||||
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 simulator URL (`http://…:8765/`) — do not open `index.html` directly from the filesystem (`file://`), or module imports will fail.
|
||||
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 `web/js/`:
|
||||
Frontend uses **Web Components** and ES modules under `src/static/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 |
|
||||
| `<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 examples/panel_color_test.py --panel-index 0 --pins 28:405,27:351
|
||||
pipenv run python test/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 |
|
||||
| `pipenv run python examples/panel_color_test.py` | One-shot RGB + white test |
|
||||
| `pipenv run python examples/panel_test.py` | Layout tests (corners, rows, chase) |
|
||||
| `pipenv run python examples/panel_sync_test.py` | Positioning + sync across all panels |
|
||||
| `pipenv run python examples/panel_animations.py` | All animations on all panels |
|
||||
| `pipenv run python examples/panel_animations.py rolling` | One animation |
|
||||
| `pipenv run python examples/panel_text.py` | Show your name |
|
||||
| `pipenv run python examples/animations.py --panel` | Same as panel_animations |
|
||||
| `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 examples/panel_animations.py --panel-index 1 # 9×45 @ 10.1.1.11
|
||||
pipenv run python test/panel_animations.py --panel-index 1 # 9×45 @ 10.1.1.11
|
||||
```
|
||||
|
||||
### Pi SPI (bench / single matrix)
|
||||
### Pi 5 overlays (bench / local strips)
|
||||
|
||||
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:
|
||||
Optional local PIO tests on GPIO — not used for the five portal panels:
|
||||
|
||||
```bash
|
||||
pipenv run python examples/setup_spi_bufsiz.py --install # sudo
|
||||
pipenv run python test/setup_pi5_leds.py --install
|
||||
```
|
||||
|
||||
| Command | What it does |
|
||||
|---------|----------------|
|
||||
| `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 |
|
||||
|
||||
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.
|
||||
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
|
||||
# MicroPython on Pico — this is the reference:
|
||||
np.fill((255, 0, 0)); np.write() # red
|
||||
# Logical RGB — firmware converts to GRB on the wire:
|
||||
# (255, 0, 0) is red
|
||||
```
|
||||
|
||||
```bash
|
||||
pipenv run python examples/panel_rgb_cycle.py # same colors over UDP
|
||||
pipenv run python test/panel_rgb_cycle.py # same colors over UDP
|
||||
```
|
||||
|
||||
### Firmware
|
||||
|
||||
Reference in New Issue
Block a user