mcp-server-pixelcade 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_server_pixelcade-0.1.0.dist-info/METADATA +321 -0
- mcp_server_pixelcade-0.1.0.dist-info/RECORD +24 -0
- mcp_server_pixelcade-0.1.0.dist-info/WHEEL +5 -0
- mcp_server_pixelcade-0.1.0.dist-info/entry_points.txt +2 -0
- mcp_server_pixelcade-0.1.0.dist-info/licenses/LICENSE +21 -0
- mcp_server_pixelcade-0.1.0.dist-info/top_level.txt +1 -0
- pixelcade_mcp/__init__.py +5 -0
- pixelcade_mcp/assumptions.py +230 -0
- pixelcade_mcp/cli.py +192 -0
- pixelcade_mcp/client.py +354 -0
- pixelcade_mcp/config.py +321 -0
- pixelcade_mcp/doctor.py +180 -0
- pixelcade_mcp/effects.py +178 -0
- pixelcade_mcp/limits.py +30 -0
- pixelcade_mcp/logsafe.py +309 -0
- pixelcade_mcp/lookup.py +60 -0
- pixelcade_mcp/ratelimit.py +226 -0
- pixelcade_mcp/registry.py +141 -0
- pixelcade_mcp/remote.py +516 -0
- pixelcade_mcp/server.py +950 -0
- pixelcade_mcp/sim/__init__.py +1 -0
- pixelcade_mcp/sim/fake_pixelweb.py +252 -0
- pixelcade_mcp/tasks.py +174 -0
- pixelcade_mcp/tools.py +29 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-server-pixelcade
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Pixelcade LED marquees and Pixelcade Pulse lighting, via the pixelweb REST API
|
|
5
|
+
Author: Nicholas E. Andrade
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SDNick484/mcp-server-pixelcade
|
|
8
|
+
Project-URL: Issues, https://github.com/SDNick484/mcp-server-pixelcade/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/SDNick484/mcp-server-pixelcade/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: mcp,mcp-server,pixelcade,arcade,batocera,led-marquee
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Framework :: AsyncIO
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Games/Entertainment :: Arcade
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: mcp<3,>=2.3
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: pyjwt[crypto]>=2.10
|
|
26
|
+
Requires-Dist: uvicorn>=0.31
|
|
27
|
+
Requires-Dist: typing_extensions>=4.7
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
30
|
+
Requires-Dist: anyio>=4; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# mcp-server-pixelcade
|
|
36
|
+
|
|
37
|
+
An [MCP](https://modelcontextprotocol.io) server for **[Pixelcade](https://pixelcade.org) LED marquees**
|
|
38
|
+
and **Pixelcade Pulse** lighting (WS2812B strips, speaker rings and matrices on a V2 board). It lets an
|
|
39
|
+
MCP client such as Claude show game and system marquees, scroll text, run arcade-themed clocks and other
|
|
40
|
+
widgets, and drive Pulse lighting effects.
|
|
41
|
+
|
|
42
|
+
> **Status: early, tested on hardware except Pulse.** On a real V2 board (pixelweb 8.0.17, Batocera 43)
|
|
43
|
+
> every marquee, text, widget and frontend tool has been checked, plus dry run; Pulse waits for strips. See
|
|
44
|
+
> [HARDWARE_VALIDATION.md](https://github.com/SDNick484/mcp-server-pixelcade/blob/main/HARDWARE_VALIDATION.md) and the [verification table](#verification-status).
|
|
45
|
+
|
|
46
|
+
This is not an official Pixelcade project.
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
The Pixelcade installer runs **pixelweb**, a small HTTP listener, on the arcade host (on Batocera:
|
|
51
|
+
`/userdata/system/pixelcade/pixelweb`, port 8080, started from `/userdata/system/custom.sh`). Pixelweb
|
|
52
|
+
drives the marquee over USB and exposes a [REST API](https://pixelcade.org/developers). This server is a
|
|
53
|
+
typed, allow-listed client for that API, so it can run anywhere that can reach the arcade on TCP 8080:
|
|
54
|
+
your desktop, a laptop, or a homelab VM. Clients reach it over stdio, or over Streamable HTTP as a
|
|
55
|
+
service behind Cloudflare Access ([Service and Cloudflare](#service-and-cloudflare)).
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
MCP client ──stdio──▶ mcp-server-pixelcade ──HTTP :8080──▶ pixelweb (Batocera) ──USB──▶ V2 board ─▶ LED panels
|
|
59
|
+
└──▶ Pulse strips
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
python -m venv ~/.venvs/pixelcade && . ~/.venvs/pixelcade/bin/activate
|
|
66
|
+
pip install mcp-server-pixelcade
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
From source instead (for the newest code, or to run the tests):
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
git clone https://github.com/SDNick484/mcp-server-pixelcade.git && cd mcp-server-pixelcade
|
|
73
|
+
python -m venv .venv && . .venv/bin/activate
|
|
74
|
+
pip install -e ".[dev]" && pytest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Requires Python 3.11+. Then check that pixelweb answers:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
PIXELCADE_URL=192.168.1.20 mcp-server-pixelcade doctor
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`doctor` checks each layer (TCP, `/info`, the frontend's current game, Pulse) and, when one fails, says
|
|
84
|
+
what to try and which [HARDWARE_VALIDATION.md](https://github.com/SDNick484/mcp-server-pixelcade/blob/main/HARDWARE_VALIDATION.md) step covers it. It changes nothing
|
|
85
|
+
on the marquee. It ends with the [assumptions](#verification-status) that hardware contradicted (listed; the code
|
|
86
|
+
no longer relies on them) and a count of those not yet confirmed; `doctor --verbose` lists those too, and
|
|
87
|
+
`doctor --json` always includes both lists.
|
|
88
|
+
|
|
89
|
+
### Trying it without the arcade
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
mcp-server-pixelcade simulate --write-config /tmp/pxsim # a fake pixelweb; prints every request it gets
|
|
93
|
+
export PIXELCADE_CONFIG_DIR=/tmp/pxsim # in another terminal
|
|
94
|
+
mcp-server-pixelcade doctor
|
|
95
|
+
mcp-server-pixelcade call show_text text="GAME NIGHT" color=cyan # one tool call, as the model makes it
|
|
96
|
+
mcp-server-pixelcade call --dry-run blank_display # reports the GET it would send
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### On Alpine (e.g. a Proxmox LXC)
|
|
100
|
+
|
|
101
|
+
`deploy/alpine/` installs it on an always-on Alpine machine on the same LAN as the arcade, in the
|
|
102
|
+
sibling servers' `deploy/alpine/` layout so they can share an LXC: a venv in `/opt/mcp-server-pixelcade`,
|
|
103
|
+
a `mcp-pixelcade` system user, every setting in `/etc/mcp-server-pixelcade/env`, an OpenRC service
|
|
104
|
+
(`/etc/init.d/mcp-server-pixelcade`), and `mcp-server-pixelcade` on `PATH`, which runs as that user with
|
|
105
|
+
those settings, for `doctor` and `call` from a shell.
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
git clone https://github.com/SDNick484/mcp-server-pixelcade.git && cd mcp-server-pixelcade
|
|
109
|
+
sh deploy/alpine/install.sh # as root; re-run to upgrade
|
|
110
|
+
vim /etc/mcp-server-pixelcade/env # PIXELCADE_URL=<the arcade's IP>
|
|
111
|
+
mcp-server-pixelcade doctor
|
|
112
|
+
mcp-server-pixelcade call show_text text="GAME NIGHT" color=cyan
|
|
113
|
+
rc-update add mcp-server-pixelcade default && rc-service mcp-server-pixelcade start
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A variable set in your shell beats the env file, so one-off overrides work as usual
|
|
117
|
+
(`PIXELCADE_DRY_RUN=1 mcp-server-pixelcade call ...`). mcp-server-batocera's
|
|
118
|
+
`deploy/alpine/setup-arcade.sh` installs both arcade servers in a Proxmox container in one go, and
|
|
119
|
+
enables and restarts both services. CI runs `deploy/alpine/smoke-test.sh` in a stock
|
|
120
|
+
`python:3.12-alpine` container: install, upgrade, the test suite on musl, `doctor` and `call` through the
|
|
121
|
+
command on `PATH` against the simulator, and the OpenRC service (as its user, an MCP call over HTTP,
|
|
122
|
+
Access settings from the env file).
|
|
123
|
+
|
|
124
|
+
### Service and Cloudflare
|
|
125
|
+
|
|
126
|
+
The service runs `/opt/mcp-server-pixelcade/run.sh serve --http` under `supervise-daemon` (restarted if
|
|
127
|
+
it exits) as `mcp-pixelcade`, logging to `/var/log/mcp-server-pixelcade/server.log`. The HTTP transport
|
|
128
|
+
is `remote.py`, shared byte for byte with the sibling servers through
|
|
129
|
+
[mcp-server-shared](https://github.com/SDNick484/mcp-server-shared). Its settings are the `MCP_HTTP_*`,
|
|
130
|
+
`MCP_PUBLIC_HOSTS` and `CF_ACCESS_*` lines in `/etc/mcp-server-pixelcade/env`:
|
|
131
|
+
|
|
132
|
+
| Flag | Variable | Default |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| `--bind` | `MCP_HTTP_BIND` | `127.0.0.1` |
|
|
135
|
+
| `--port` | `MCP_HTTP_PORT` | `8718` (the siblings use 8711 to 8716, Batocera 8717) |
|
|
136
|
+
| `--path` | `MCP_HTTP_PATH` | `/pixelcade/mcp` |
|
|
137
|
+
| `--public-host` | `MCP_PUBLIC_HOSTS` | none: only loopback Host headers pass |
|
|
138
|
+
| | `CF_ACCESS_TEAM_DOMAIN`, `CF_ACCESS_AUD` | none: serves only this machine |
|
|
139
|
+
| | `MCP_ALLOWED_EMAILS` | anyone Access lets in |
|
|
140
|
+
|
|
141
|
+
With Access configured, every request needs a valid Access assertion, checked here: signature (Access's
|
|
142
|
+
published keys), the application's audience tag, issuer, expiry, and the email allow-list if set; anything
|
|
143
|
+
else gets a plain 403. Without Access it serves only the machine it runs on: it won't listen on anything
|
|
144
|
+
but loopback, won't start with a `--public-host`, and answers 403 to anything a tunnel or proxy forwarded
|
|
145
|
+
(`--insecure-no-auth` turns that off for a short trusted-LAN test; don't run the service with it).
|
|
146
|
+
|
|
147
|
+
To use it as a connector, with cloudflared running on the same machine (keep the `127.0.0.1` bind):
|
|
148
|
+
|
|
149
|
+
1. **Tunnel route:** a public hostname, say `mcp.example.com`, path `pixelcade/*`, to
|
|
150
|
+
`http://localhost:8718`. Several servers can share the hostname, one path each.
|
|
151
|
+
2. **Access application** (self-hosted) covering that hostname and path, with a policy for who may use it
|
|
152
|
+
and Managed OAuth turned on, so Claude's connector can sign in through Access. Note its AUD tag.
|
|
153
|
+
3. **The env file:** `MCP_PUBLIC_HOSTS=mcp.example.com`, `CF_ACCESS_TEAM_DOMAIN=<team>.cloudflareaccess.com`,
|
|
154
|
+
`CF_ACCESS_AUD=<the AUD tag>`, then `rc-service mcp-server-pixelcade restart`.
|
|
155
|
+
4. **The connector:** `https://mcp.example.com/pixelcade/mcp`.
|
|
156
|
+
|
|
157
|
+
`GET /healthz` answers without auth, for monitoring. Details are in `src/pixelcade_mcp/remote.py`.
|
|
158
|
+
|
|
159
|
+
## Use it with an MCP client
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"mcpServers": {
|
|
164
|
+
"pixelcade": {
|
|
165
|
+
"command": "/path/to/mcp-server-pixelcade/.venv/bin/mcp-server-pixelcade",
|
|
166
|
+
"env": { "PIXELCADE_URL": "192.168.1.50" }
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
For Claude Code: `claude mcp add pixelcade -e PIXELCADE_URL=192.168.1.50 -- /path/to/.venv/bin/mcp-server-pixelcade`.
|
|
173
|
+
|
|
174
|
+
Running as a service behind Cloudflare Access instead, add `https://<your host>/pixelcade/mcp` as a
|
|
175
|
+
connector ([Service and Cloudflare](#service-and-cloudflare)).
|
|
176
|
+
|
|
177
|
+
## Configuration
|
|
178
|
+
|
|
179
|
+
| Setting | Default | What it does |
|
|
180
|
+
| -------------------------------- | ------------------------------------- | -------------------------------------------------------------- |
|
|
181
|
+
| `PIXELCADE_URL` | `http://batocera.local:8080` | Where pixelweb listens. A bare host or IP gets `http://` and `:8080` |
|
|
182
|
+
| `PIXELCADE_PULSE_MAX_BRIGHTNESS` | `128` | Highest Pulse brightness/intensity the model may set (0-255) |
|
|
183
|
+
| `PIXELCADE_TIMEOUT` | `10` | Seconds to wait for pixelweb (1-60) |
|
|
184
|
+
| `PIXELCADE_CONFIG_DIR` | `$XDG_CONFIG_HOME/mcp-server-pixelcade` | Where an optional `config.json` lives |
|
|
185
|
+
| `PIXELCADE_NAME` | pixelweb's URL | How results name the marquee |
|
|
186
|
+
| `PIXELCADE_DRY_RUN` | off | Read pixelweb, change nothing (`serve --dry-run`) |
|
|
187
|
+
| `PIXELCADE_LOG_UNREDACTED` | off | Show LAN addresses in logs (`--no-redact`); secrets stay hidden |
|
|
188
|
+
| `PIXELCADE_DEBUG` | off | Debug logging (`--debug`) |
|
|
189
|
+
|
|
190
|
+
`config.json` can hold the same settings as `url`, `pulse_max_brightness`, `timeout_s`, `name` and `dry_run`; environment
|
|
191
|
+
variables win. `.local` names need mDNS, which usually doesn't cross VLANs or work in WSL2 without
|
|
192
|
+
mirrored networking. A DHCP reservation plus the IP address is the dependable choice.
|
|
193
|
+
|
|
194
|
+
## Tools
|
|
195
|
+
|
|
196
|
+
All tools use the `pixelcade_` prefix to eliminate namespace collisions with sibling MCP servers in multi-server agent environments. The CLI supports shorthand names (`call show_text` resolves to `pixelcade_show_text`).
|
|
197
|
+
|
|
198
|
+
| Tool | What it does |
|
|
199
|
+
| -------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
200
|
+
| `pixelcade_get_status` | Reachability, hardware/firmware/software versions, the frontend's selected game, Pulse state |
|
|
201
|
+
| `pixelcade_list_artwork` | The marquees in one folder (a system, `console`, `animations`, `user`): names, still/animated |
|
|
202
|
+
| `pixelcade_list_pulse_effects` | Pulse effect names by category, per-game/per-system name patterns, and zone names |
|
|
203
|
+
| `pixelcade_show_game_marquee` | A game's marquee by system + ROM name; still/animated choice, optional queueing and fallback text |
|
|
204
|
+
| `pixelcade_show_console_marquee` | A system marquee, e.g. `snes` |
|
|
205
|
+
| `pixelcade_show_current_game` | Put back the marquee for the game selected in EmulationStation |
|
|
206
|
+
| `pixelcade_show_default_marquee` | Pixelcade's default marquee (needs pixelweb 8.0.14+) |
|
|
207
|
+
| `pixelcade_show_text` | Scrolling or fixed text: color, speed, loops, 1/2/4 lines, blink, left-justify |
|
|
208
|
+
| `pixelcade_play_animation` | One of Pixelcade's built-in animations |
|
|
209
|
+
| `pixelcade_show_widget` | Clock (19 arcade themes), weather, stocks, sports scores, or the attract-mode playlist |
|
|
210
|
+
| `pixelcade_stop_widgets` | Stop widgets/attract mode and return to arcade mode |
|
|
211
|
+
| `pixelcade_blank_display` | Black screen |
|
|
212
|
+
| `pixelcade_pulse_effect` | Run an effect on a Pulse zone (`rainbow`, `fire`, `mame_pacman`, …) with colors, speed, loops |
|
|
213
|
+
| `pixelcade_pulse_fill` | Solid color on a zone |
|
|
214
|
+
| `pixelcade_pulse_clear` | Turn off one zone, or all LEDs |
|
|
215
|
+
| `pixelcade_set_pulse_brightness` | Overall Pulse brightness, up to the server's cap |
|
|
216
|
+
|
|
217
|
+
**The frontend drives the marquee too.** EmulationStation's Pixelcade hooks call pixelweb whenever you
|
|
218
|
+
scroll to a system or game, and every game launch calls `/attract/stop`. So whatever the model shows
|
|
219
|
+
lasts until the next frontend event. `show_current_game` restores the frontend's marquee, and
|
|
220
|
+
`show_default_marquee` shows the default one.
|
|
221
|
+
|
|
222
|
+
**Locks.** `show_widget(lock=true)` sends `?nointerrupt`, which Pixelcade documents as preventing
|
|
223
|
+
interruption by *other API calls*, this server's included, until `stop_widgets` or a game launch.
|
|
224
|
+
Pixelcade's Batocera screensaver hooks do the same: the screensaver starts attract mode with
|
|
225
|
+
`?nointerrupt` and ends it with `/attract/stop`. So while the screensaver runs, marquee calls succeed
|
|
226
|
+
but change nothing (PX-ATTRACT-LOCK); `stop_widgets` releases the lock.
|
|
227
|
+
|
|
228
|
+
**Pixelcade 8.x.** Pixelweb 8 is mostly about Sidekick companion displays; the API this server uses is
|
|
229
|
+
unchanged. It adds `/defaultmarquee` (8.0.14, PX-DEFAULT-MARQUEE), and a companion-app setting to show
|
|
230
|
+
static PNG marquees only, which may override `show_game_marquee(animation="animated")`. `doctor` shows
|
|
231
|
+
the pixelweb version and notes when it's too old for `show_default_marquee`.
|
|
232
|
+
|
|
233
|
+
**Names:** `system` is the Batocera ROM folder name (`mame`, `fbneo`, `snes`, …) and `rom` is the file
|
|
234
|
+
name without its extension (`pacman`, `Super Mario World (USA)`), the same values Batocera's own hook
|
|
235
|
+
scripts send.
|
|
236
|
+
|
|
237
|
+
Every tool that changes the display returns the same structured result as the sibling servers:
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{ "pixelcade": "Arcade marquee", "outcome": "done", "detail": "Showing the text",
|
|
241
|
+
"sent": ["GET /text?t=GAME%20NIGHT&c=cyan"], "warnings": [] }
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`outcome` is `done` or `dry_run`; `sent` is the exact GET (or, in a dry run, the one it would send).
|
|
245
|
+
|
|
246
|
+
## Safety design
|
|
247
|
+
|
|
248
|
+
- **Namespace isolation.** Every tool is prefixed with `pixelcade_` to eliminate collision with other MCP servers in multi-server configurations.
|
|
249
|
+
- **Hardware Rate Limiting.** Commands are regulated via TokenBucket rate limiters:
|
|
250
|
+
- USB serial queue protection (burst 6, refill 2/s) prevents buffer overruns on the V2 board.
|
|
251
|
+
- High-draw Pulse LED protection (burst 3, refill 0.5/s) prevents thermal and current overload.
|
|
252
|
+
- **Network topology & PII masking.** Network URLs and IP addresses are masked (e.g. `http://x.x.x.50:8080`) in tool outputs, logs, and error messages via `mcp_shared.logsafe`.
|
|
253
|
+
- **A fixed set of endpoints.** Paths are built in `server.py` from constants plus validated arguments.
|
|
254
|
+
Pixelweb also serves `/quit`, `/reboot`, `/shutdown`, `/arcade/write` (writes the marquee's SD card),
|
|
255
|
+
`/upload`, `/settings`, `/update/*` and `/ledstrip/numpixels`; none are reachable through this server.
|
|
256
|
+
- **Arguments can't become other URLs.** Each model-supplied path value must be a single segment (no
|
|
257
|
+
`/`, `\`, `.`/`..` or control characters) and is then percent-encoded, `/` included. Zones, colors,
|
|
258
|
+
tickers and team codes are validated; effect names must match a strict pattern.
|
|
259
|
+
- **A Pulse brightness cap**, 128 by default. Each WS2812B LED can draw 60 mA at full white, and Pixelcade
|
|
260
|
+
warns that powering more than about 40 LEDs from the V2 board without an external 5V supply (with a
|
|
261
|
+
common ground) can overload it. Raise `PIXELCADE_PULSE_MAX_BRIGHTNESS` once your strips have their own
|
|
262
|
+
supply. The firmware's own thermal throttling (55/60/65 °C) is a backstop, not a plan.
|
|
263
|
+
- **Pixelweb has no authentication**, so anything on your LAN can already call every endpoint. Keep port
|
|
264
|
+
8080 off the internet. Over HTTP, this server is the gate: Access assertions are checked on every
|
|
265
|
+
request, and without Access it serves only its own machine ([Service and Cloudflare](#service-and-cloudflare)).
|
|
266
|
+
- Tools carry titles and MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`,
|
|
267
|
+
`openWorldHint`). Nothing is marked destructive: every display change is undone by the next one.
|
|
268
|
+
- Structured XML docstrings (`<use_case>` and `<important_notes>`) guide models on tool preconditions, display lifetimes, and routing.
|
|
269
|
+
- Errors are actionable ("pixelweb isn't answering at …", "Can't resolve batocera.local; set
|
|
270
|
+
PIXELCADE_URL to the IP"), and logs go to stderr because stdout belongs to the MCP transport.
|
|
271
|
+
|
|
272
|
+
## Verification status
|
|
273
|
+
|
|
274
|
+
Every pixelweb behavior the code relies on is listed in `src/pixelcade_mcp/assumptions.py`, with its
|
|
275
|
+
source and how sure we are; the code cites each id where it depends on it, and
|
|
276
|
+
[HARDWARE_VALIDATION.md](https://github.com/SDNick484/mcp-server-pixelcade/blob/main/HARDWARE_VALIDATION.md) has the step that confirms it. *high* means documented
|
|
277
|
+
and used the same way by Pixelcade's own scripts; *medium* means documented only; *low* means our own
|
|
278
|
+
guess. Hardware results so far are from pixelweb 8.0.17 on a V2 board (2026-10-22).
|
|
279
|
+
|
|
280
|
+
| Assumption | Confidence | Status |
|
|
281
|
+
|---|---|---|
|
|
282
|
+
| `PX-GET-API` | high | hardware-verified |
|
|
283
|
+
| `PX-INFO` | medium | hardware-verified |
|
|
284
|
+
| `PX-CURRENTGAME` | medium | hardware-verified |
|
|
285
|
+
| `PX-LISTS` | low | hardware-contradicted |
|
|
286
|
+
| `PX-IMAGES-LIST` | high | hardware-verified |
|
|
287
|
+
| `PX-REPLIES` | low | hardware-verified |
|
|
288
|
+
| `PX-TEXT` | medium | hardware-verified |
|
|
289
|
+
| `PX-QUEUE` | medium | hardware-verified |
|
|
290
|
+
| `PX-HOLD` | medium | hardware-verified |
|
|
291
|
+
| `PX-FRONTEND` | high | hardware-verified |
|
|
292
|
+
| `PX-WIDGETS` | medium | hardware-verified |
|
|
293
|
+
| `PX-ATTRACT-LOCK` | medium | hardware-verified |
|
|
294
|
+
| `PX-DEFAULT-MARQUEE` | low | hardware-verified |
|
|
295
|
+
| `PX-ZONES` | medium | simulator-only |
|
|
296
|
+
| `PX-BRIGHTNESS` | medium | simulator-only |
|
|
297
|
+
| `PX-POWER` | high | simulator-only |
|
|
298
|
+
|
|
299
|
+
## Not yet included
|
|
300
|
+
|
|
301
|
+
- Pulse **batch zone control**, **MAX7219** 7-segment/dot-matrix displays, and **mini LCD** (`/ministats`)
|
|
302
|
+
endpoints, which Pixelcade documents but this server doesn't wrap yet.
|
|
303
|
+
- Editing the attract-mode playlist, and OAuth-backed widgets (Spotify, Plex, YouTube, Google Calendar).
|
|
304
|
+
- Uploading custom artwork (`/upload`): useful, but it writes files on the arcade, so it should be opt-in.
|
|
305
|
+
|
|
306
|
+
## Development
|
|
307
|
+
|
|
308
|
+
```sh
|
|
309
|
+
pip install -e ".[dev]"
|
|
310
|
+
pytest # validators, HTTP client, tools via an in-process MCP client, dry run, faults, CLI, stdio, HTTP + Access
|
|
311
|
+
ruff check . && ruff format --check .
|
|
312
|
+
mypy # strict
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Tests run against the wire-level fake pixelweb (`sim/fake_pixelweb.py`), which records each request's raw
|
|
316
|
+
path, so the exact percent-encoding on the wire is asserted. To poke at the tools interactively:
|
|
317
|
+
`npx @modelcontextprotocol/inspector mcp-server-pixelcade`.
|
|
318
|
+
|
|
319
|
+
## License
|
|
320
|
+
|
|
321
|
+
MIT. See [LICENSE](https://github.com/SDNick484/mcp-server-pixelcade/blob/main/LICENSE).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
mcp_server_pixelcade-0.1.0.dist-info/licenses/LICENSE,sha256=UaFe7iKAtdvNfrhH4lJ-v0InFSbjZGMZ4dLXeVf7o8g,1076
|
|
2
|
+
pixelcade_mcp/__init__.py,sha256=sM7Bai9XZ5pYv9JptQHjckyOIviVkdaN2LkOhmAU6Ng,318
|
|
3
|
+
pixelcade_mcp/assumptions.py,sha256=b5HuqWkxZUqOqyfgmO2Bramh8SLowvZpV7zs7n7hq2A,13001
|
|
4
|
+
pixelcade_mcp/cli.py,sha256=310N-OfdlzEXGj4JuP8S3Dw6D1QaIEq8Ri3a_s7u7TI,8886
|
|
5
|
+
pixelcade_mcp/client.py,sha256=tScf43_hi4tSPlAqEmc7zgEJ-KWC3zHyRRaWS13cGEU,15045
|
|
6
|
+
pixelcade_mcp/config.py,sha256=T3Mig3bMKXBkGs0XRmqTjC7inebRcVfbcx0alXnYYPc,10164
|
|
7
|
+
pixelcade_mcp/doctor.py,sha256=tDxvDr39gqF9w27fjoRiCkyUE2lp3YEX1aU41JW66aE,7755
|
|
8
|
+
pixelcade_mcp/effects.py,sha256=7XirNdfQwKgm9avoCfoJ33hEY0C_aKgAicEG1nZIBG0,4941
|
|
9
|
+
pixelcade_mcp/limits.py,sha256=Gf0t_qp2B31Ch_Lrm9Lnx_BPxfn5L8bZfhL-g8gm5qE,1445
|
|
10
|
+
pixelcade_mcp/logsafe.py,sha256=QuCitdXqckrLQSzb-V0R2pxFhLyqMhPSKQq7s3R97mI,15892
|
|
11
|
+
pixelcade_mcp/lookup.py,sha256=TmP8Abg5drIKTzHZ928YJ8Sa1qSDmvymEhwnSQpun6s,2235
|
|
12
|
+
pixelcade_mcp/ratelimit.py,sha256=aIhy8CAPMxJdEdlMvFCjUT8jElFKvb0QcrR8zxAB6Ps,9840
|
|
13
|
+
pixelcade_mcp/registry.py,sha256=9JIwD7hYao7m6PWJXZDAiGQfd8dfGWIfruuAJYNFsUw,7027
|
|
14
|
+
pixelcade_mcp/remote.py,sha256=v7d4C9Phz11hc9XsORMJ7VY-GO2GMrdfaa_gEU8b7s8,23958
|
|
15
|
+
pixelcade_mcp/server.py,sha256=__2GRoSNayYdcbBPytvK8tYMFCyVYZZ1CJuPEh3hiR0,38556
|
|
16
|
+
pixelcade_mcp/tasks.py,sha256=FttB8XI_evXjIJUyWz7rtjENQVhgTS6ZcbEB5v-crkc,7815
|
|
17
|
+
pixelcade_mcp/tools.py,sha256=TlMObSXFMZst3ssGBrSwtS3WpjbAV0ngKfU5mnajWNU,1363
|
|
18
|
+
pixelcade_mcp/sim/__init__.py,sha256=rtOXDNhwRl-DCbt4Lsf5qtLLJiqeNzty6tOrXZFP-vI,73
|
|
19
|
+
pixelcade_mcp/sim/fake_pixelweb.py,sha256=nBo0JKWUN-dURNnLuXpTMULupY2GBHFXF4h7Biv83zQ,11285
|
|
20
|
+
mcp_server_pixelcade-0.1.0.dist-info/METADATA,sha256=Mj2_pg3bY4LzjIA80X3MrYVsyg_6IH74vZwrzu59duI,19939
|
|
21
|
+
mcp_server_pixelcade-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
22
|
+
mcp_server_pixelcade-0.1.0.dist-info/entry_points.txt,sha256=JEzBzxrdGTjJqTtd7LPVuDAi6mXCYUNvU2Z02ANg8cQ,64
|
|
23
|
+
mcp_server_pixelcade-0.1.0.dist-info/top_level.txt,sha256=SOemg7JeEEDlB5WlfRr3YAYDmPk96CNPrLLKYPvGEZI,14
|
|
24
|
+
mcp_server_pixelcade-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nicholas E. Andrade
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pixelcade_mcp
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""MCP server for Pixelcade LED marquees (and Pixelcade Pulse lighting) via the pixelweb REST API."""
|
|
2
|
+
|
|
3
|
+
# The one place the version lives: pyproject.toml reads it from here. Bump it for every release, since pip
|
|
4
|
+
# keeps an installed copy whose version matches, even when the code under it changed.
|
|
5
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Every pixelweb behavior this server relies on, and whether hardware has confirmed it.
|
|
2
|
+
|
|
3
|
+
The same scheme as the sibling servers: each claim has an id that the code cites
|
|
4
|
+
where it depends on it (``# ASSUMPTION PX-QUEUE``), that a test cites, that
|
|
5
|
+
HARDWARE_VALIDATION.md cites in the step confirming it, that the README's
|
|
6
|
+
verification table lists, and that ``mcp-server-pixelcade doctor`` prints while
|
|
7
|
+
it's unconfirmed. tests/test_assumptions.py fails if they drift apart.
|
|
8
|
+
|
|
9
|
+
Sources, read 2026-10: Pixelcade's API reference (pixelcade.org/developers), the
|
|
10
|
+
Pulse page (pixelcade.org/pulse), the release notes (pixelcade.org/release-notes,
|
|
11
|
+
through pixelweb 8.0.17), and Pixelcade's own Batocera hook scripts
|
|
12
|
+
(github.com/alinke/pixelcade-linux, batocera/scripts/). Nothing has been seen on
|
|
13
|
+
the owner's marquee yet. To record a result, set ``status`` to
|
|
14
|
+
"hardware-verified" (or "hardware-contradicted"), say what you saw in ``note``.
|
|
15
|
+
|
|
16
|
+
Confidence is about the *claim*, judged from its source:
|
|
17
|
+
high - documented, and used the same way by Pixelcade's own scripts
|
|
18
|
+
medium - documented, but not used by anything we can read
|
|
19
|
+
low - our own guess; the simulator implements it but nothing confirms it
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from .registry import Assumption, Confidence, Status, by_id
|
|
25
|
+
from .registry import contradicted as _contradicted
|
|
26
|
+
from .registry import unverified as _unverified
|
|
27
|
+
|
|
28
|
+
# Confidence and Status are re-exported (no_implicit_reexport): callers may need the literal types.
|
|
29
|
+
__all__ = ["ASSUMPTIONS", "BY_ID", "Assumption", "Confidence", "Status", "contradicted", "unverified"]
|
|
30
|
+
|
|
31
|
+
DOCS = "pixelcade.org/developers"
|
|
32
|
+
SCRIPTS = "pixelcade-linux batocera/scripts"
|
|
33
|
+
RELEASES = "pixelcade.org/release-notes"
|
|
34
|
+
SEEN = "Seen 2026-10-22 on a V2 LED (firmware PIXR0023), pixelweb 8.0.17 on Batocera 43, from a Proxmox LXC: "
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
ASSUMPTIONS: tuple[Assumption, ...] = (
|
|
38
|
+
Assumption(
|
|
39
|
+
"PX-GET-API",
|
|
40
|
+
"Every control is an HTTP GET on port 8080; options are query parameters, some of them bare flags "
|
|
41
|
+
"(?nogif, ?nointerrupt), and spaces in ROM names are sent as %20.",
|
|
42
|
+
f"{DOCS}; {SCRIPTS} (rawurlencode, curl)",
|
|
43
|
+
"high",
|
|
44
|
+
status="hardware-verified",
|
|
45
|
+
note=SEEN + "the marquee for 'Super Mario World 2 - Yoshi's Island (USA)' showed, sent as ...%20Yoshi%27s"
|
|
46
|
+
"%20Island%20%28USA%29; flags like ?nogif and scroll=false work. pixelweb matches artwork by exact name: "
|
|
47
|
+
"the ROM's '(USA) (Rev 1)' found nothing, so the default marquee showed.",
|
|
48
|
+
),
|
|
49
|
+
Assumption(
|
|
50
|
+
"PX-INFO",
|
|
51
|
+
"/info returns JSON with type, resolution, v2board, firmwareVersion, softwareVersion, hasLEDStripSupport, "
|
|
52
|
+
"ledStrip, ledStripBrightness and ledStripNumPixels.",
|
|
53
|
+
f"{DOCS} (Device Information example)",
|
|
54
|
+
"medium",
|
|
55
|
+
status="hardware-verified",
|
|
56
|
+
note=SEEN + "every field present, plus many more: platform, arcadeFrontend 'Batocera', ledActive, "
|
|
57
|
+
"noAnimatedMarquees (the 8.x 'static PNG marquees only' setting, now in get_status), sidekick, "
|
|
58
|
+
"sidekick-ids, sidekick-models, sidekickOnly, centerMatrixResolution, isMiSTer, vestaboard*.",
|
|
59
|
+
),
|
|
60
|
+
Assumption(
|
|
61
|
+
"PX-CURRENTGAME",
|
|
62
|
+
"/currentgame returns the last game or system marquee shown, by anyone (the frontend's hooks, the "
|
|
63
|
+
"screensaver, this server), as 'system%rom', or 'system%' after a system marquee; text and animations "
|
|
64
|
+
"don't change it. (The game-selected hook's comment says 'system,rom'.)",
|
|
65
|
+
f"{SCRIPTS} game-selected; {DOCS}; hardware",
|
|
66
|
+
"medium",
|
|
67
|
+
status="hardware-verified",
|
|
68
|
+
note=SEEN + "'neogeo%eightman' ('%', not a comma). It followed every marquee: scrolling gave "
|
|
69
|
+
"genesis%Old-Towers; show_text and play_animation left it; show_console_marquee snes made it 'snes%'; "
|
|
70
|
+
"show_game_marquee galaga made it mame%galaga. The claim used to say it was the frontend's selection; "
|
|
71
|
+
"it isn't, so the server remembers the frontend's itself (server._Marquees); in one session, Galaga "
|
|
72
|
+
"then show_current_game restored the frontend's gba/SpaceTwins. During the random-video "
|
|
73
|
+
"screensaver it changed with each video (mystwaru, eightman, circusc), so "
|
|
74
|
+
"the screensaver's game-selected events update it even while attract mode holds the marquee: after the "
|
|
75
|
+
"screensaver, it names the last video's game, not the user's selection.",
|
|
76
|
+
),
|
|
77
|
+
Assumption(
|
|
78
|
+
"PX-LISTS",
|
|
79
|
+
"/arcade/list, /still/list and /animations/list return a list of names.",
|
|
80
|
+
DOCS,
|
|
81
|
+
"low",
|
|
82
|
+
status="hardware-contradicted",
|
|
83
|
+
note=SEEN + "all three answer 404 (so do the documented /list/image/<system>, /list/image/all and "
|
|
84
|
+
"/list/video, which may be LCD-only). list_artwork uses PX-IMAGES-LIST instead.",
|
|
85
|
+
),
|
|
86
|
+
Assumption(
|
|
87
|
+
"PX-IMAGES-LIST",
|
|
88
|
+
'POST /api/images/list with the JSON body {"name": "<folder>"} lists /userdata/system/pixelcade/<folder> '
|
|
89
|
+
"as [{name, system, urlPath, imagePath}], name being a file name; a missing folder answers 400 (plain text "
|
|
90
|
+
"'open <path>: no such file or directory'), and an empty name answers null.",
|
|
91
|
+
"pixelweb's own web page (its artwork browser's script); checked on hardware",
|
|
92
|
+
"high",
|
|
93
|
+
status="hardware-verified",
|
|
94
|
+
note=SEEN + "mame: 12,415 files (10,291 PNG, 2,124 GIF; 2 MB of JSON in 0.07 s); animations: 702 PNG; "
|
|
95
|
+
"console, user and system folders too; 'still' has no folder. The one POST this server sends: a read. "
|
|
96
|
+
"Undocumented, so a pixelweb update could change it.",
|
|
97
|
+
),
|
|
98
|
+
Assumption(
|
|
99
|
+
"PX-REPLIES",
|
|
100
|
+
'Controls answer 200 with an empty body (/defaultmarquee: {"status": "ok"}), even when the '
|
|
101
|
+
"artwork is missing; an endpoint this pixelweb doesn't have answers 404 '404 page not found'.",
|
|
102
|
+
"pixelweb is a Go net/http server; hardware",
|
|
103
|
+
"low",
|
|
104
|
+
status="hardware-verified",
|
|
105
|
+
note=SEEN + "every control call answered 200 with nothing in the body; /arcade/stream/mame/nosuchgame "
|
|
106
|
+
"answered 200 too, so a reply can't say whether artwork exists (list_artwork can). The claim used to "
|
|
107
|
+
"say 'a short body'.",
|
|
108
|
+
),
|
|
109
|
+
Assumption(
|
|
110
|
+
"PX-TEXT",
|
|
111
|
+
"/text takes the message in t=, with line breaks as the literal two characters \\n, plus c=, s=, l=, "
|
|
112
|
+
"scroll=, lines=, blink= and left=.",
|
|
113
|
+
DOCS,
|
|
114
|
+
"medium",
|
|
115
|
+
status="hardware-verified",
|
|
116
|
+
note=SEEN + "color, two fixed lines from a literal \\n (sent %5Cn), blink on 'PLAYER 1', and four "
|
|
117
|
+
"left-justified lines all showed as documented. blink's 9-character limit is from the docs, not tried.",
|
|
118
|
+
),
|
|
119
|
+
Assumption(
|
|
120
|
+
"PX-QUEUE",
|
|
121
|
+
"A call with l=N (N >= 1) joins a queue; a call without l interrupts and clears it. Queued calls from "
|
|
122
|
+
"scripts carry event=FEScroll.",
|
|
123
|
+
DOCS,
|
|
124
|
+
"medium",
|
|
125
|
+
status="hardware-verified",
|
|
126
|
+
note=SEEN + "a marquee queued with l=5&event=FEScroll played for about 5 s, then text queued with l=1 "
|
|
127
|
+
"scrolled once; blank_display (no l) interrupted.",
|
|
128
|
+
),
|
|
129
|
+
Assumption(
|
|
130
|
+
"PX-HOLD",
|
|
131
|
+
"l=-1, documented as 'infinite until next command', shows text once and then the marquee goes blank; "
|
|
132
|
+
"l=99999 holds it until interrupted, and a plain call followed by the same call with l=99999 shows it "
|
|
133
|
+
"at once (the plain call interrupts and clears the queue) and holds it.",
|
|
134
|
+
f"{DOCS} (Looping & Queue System); hardware",
|
|
135
|
+
"medium",
|
|
136
|
+
status="hardware-verified",
|
|
137
|
+
note=SEEN + "text?t=HELLO&l=-1 showed once, then blank; HOLD ONE with l=99999 replaced Galaga and stayed; "
|
|
138
|
+
"the plain-then-l=99999 pair showed at once and stayed, and a long message sent the same way kept "
|
|
139
|
+
"scrolling for 30 s; scroll=false text stayed. Short text doesn't scroll (it fits). Held text stayed "
|
|
140
|
+
"until scrolling in the frontend replaced it. Not yet seen: the pair while the frontend's own l=99999 "
|
|
141
|
+
"game marquee holds the queue.",
|
|
142
|
+
),
|
|
143
|
+
Assumption(
|
|
144
|
+
"PX-FRONTEND",
|
|
145
|
+
"EmulationStation's hooks call /console/stream/<system>/?event=FEScroll and "
|
|
146
|
+
"/arcade/stream/<system>/<rom>?event=FEScroll as you scroll (game-selected skips the call when the game "
|
|
147
|
+
"matches /currentgame), and /attract/stop then a queued marquee (l=99999&event=GameStart) on every game "
|
|
148
|
+
"launch, so whatever this server shows lasts until the next frontend event.",
|
|
149
|
+
f"{SCRIPTS} system-selected, game-selected, game-start",
|
|
150
|
+
"high",
|
|
151
|
+
status="hardware-verified",
|
|
152
|
+
note=SEEN + "scrolling in the frontend replaced held text; launching a game (through mcp-server-batocera) "
|
|
153
|
+
"ended a locked clock and showed the game's marquee. Hook scripts from pixelcade-linux (2026-06).",
|
|
154
|
+
),
|
|
155
|
+
Assumption(
|
|
156
|
+
"PX-WIDGETS",
|
|
157
|
+
"/clock (clockType=, 12h=), /weather (location=), /stocks (tickers=), /sports[/<league>] (teams=) and "
|
|
158
|
+
"/attract show widgets; ?nointerrupt locks one in place (PX-ATTRACT-LOCK); /attract/stop ends them.",
|
|
159
|
+
DOCS,
|
|
160
|
+
"medium",
|
|
161
|
+
status="hardware-verified",
|
|
162
|
+
note=SEEN + "Galaga and Pac-Man clocks (12h=false too), weather (saved location and a ZIP), a stock ticker "
|
|
163
|
+
"and NFL scores all rendered, with no settings beyond the install. ?nointerrupt and /attract: step 5.",
|
|
164
|
+
),
|
|
165
|
+
Assumption(
|
|
166
|
+
"PX-ATTRACT-LOCK",
|
|
167
|
+
"?nointerrupt locks the marquee: other API calls, this server's included, don't change it until "
|
|
168
|
+
"/attract/stop. Pixelcade's Batocera screensaver-start hook calls /attract?nointerrupt and "
|
|
169
|
+
"screensaver-stop calls /attract/stop, so while the cabinet's screensaver runs, the marquee shows the "
|
|
170
|
+
"attract playlist and ignores both the screensaver's game-selected events and this server.",
|
|
171
|
+
f"{DOCS} (attract mode: 'prevents interruption by other API calls'); {SCRIPTS} screensaver-start, "
|
|
172
|
+
"screensaver-stop",
|
|
173
|
+
"medium",
|
|
174
|
+
status="hardware-verified",
|
|
175
|
+
note=SEEN + "a clock shown with lock=true stayed through frontend scrolling, and show_text over it "
|
|
176
|
+
"answered 200 but didn't show. During the random-video screensaver the marquee stayed in attract mode, "
|
|
177
|
+
"didn't follow the videos and ignored show_text; after stop_widgets it followed the videos. Unknown: "
|
|
178
|
+
"whether queued (l=N) calls wait for the lock, and whether Pulse calls are locked too.",
|
|
179
|
+
),
|
|
180
|
+
Assumption(
|
|
181
|
+
"PX-DEFAULT-MARQUEE",
|
|
182
|
+
"pixelweb 8.0.14 and later have GET /defaultmarquee, which shows the default marquee "
|
|
183
|
+
"(console/default-marquee.png) on the LED and LCD when called with no parameters; older versions answer 404.",
|
|
184
|
+
f"{RELEASES} (8.0.14); {DOCS} (default marquee path)",
|
|
185
|
+
"low",
|
|
186
|
+
status="hardware-verified",
|
|
187
|
+
note=SEEN + 'GET /defaultmarquee answered 200 {"status": "ok"} and showed the Batocera marquee (this '
|
|
188
|
+
"install's default). Only in the release notes, not the API reference; how it interacts with the queue "
|
|
189
|
+
"and the attract lock isn't known.",
|
|
190
|
+
),
|
|
191
|
+
Assumption(
|
|
192
|
+
"PX-ZONES",
|
|
193
|
+
"Pulse zones are named (left_strip, right_strip, ...) or numbered 0-35; effects are "
|
|
194
|
+
"/ledstrip/zone/<z>/effect/<name>, fills /ledstrip/zone/<z>/flood, but clearing one zone is "
|
|
195
|
+
"/ledstrip/<z>/clear (no zone/ segment).",
|
|
196
|
+
f"{DOCS} (Pulse API)",
|
|
197
|
+
"medium",
|
|
198
|
+
note="The clear path's missing zone/ segment may be a documentation typo; check it first.",
|
|
199
|
+
),
|
|
200
|
+
Assumption(
|
|
201
|
+
"PX-BRIGHTNESS",
|
|
202
|
+
"/ledstrip/brightness/<0-255> sets the strips' brightness and /ledstrip/getbrightness reads it back "
|
|
203
|
+
"from the board.",
|
|
204
|
+
f"{DOCS} (Pulse API)",
|
|
205
|
+
"medium",
|
|
206
|
+
note="Documented; needs strips connected to check. Seen 2026-10-22 with strips configured in pixelweb "
|
|
207
|
+
"(ledStrip true, 50 LEDs) but none connected: /ledstrip/getbrightness never answered (30 s), so get_status "
|
|
208
|
+
"and doctor wait only PULSE_STATUS_TIMEOUT_S (2 s) and report pulse.responding false.",
|
|
209
|
+
),
|
|
210
|
+
Assumption(
|
|
211
|
+
"PX-POWER",
|
|
212
|
+
"Each WS2812B LED draws up to 60 mA at full white, and the V2 board can power only about 40 LEDs "
|
|
213
|
+
"itself; more needs an external 5 V supply with a common ground. Hence the default brightness cap of 128.",
|
|
214
|
+
"pixelcade.org/pulse (Powering and Safety)",
|
|
215
|
+
"high",
|
|
216
|
+
note="Pixelcade's own guidance. The cap is a safety margin, not a measured limit.",
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
BY_ID = by_id(ASSUMPTIONS)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def unverified() -> list[Assumption]:
|
|
224
|
+
"""Not checked on hardware yet: the code relies on them on the strength of docs or source alone."""
|
|
225
|
+
return _unverified(ASSUMPTIONS)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def contradicted() -> list[Assumption]:
|
|
229
|
+
"""Checked on hardware and found wrong: the code no longer relies on them (see each ``note``)."""
|
|
230
|
+
return _contradicted(ASSUMPTIONS)
|