Files
room_glitch/tools/room_controller/README.md
T

142 lines
10 KiB
Markdown

# Arcade room controller and browser operator
This controller is independent of Odin. It uses Python's standard library, authenticated HTTP, and SQLite; no Odin services or dependencies are needed.
For supervised startup on Windows or the Pi, use the [stack launch scripts](../launch/README.md). The manual commands below remain available for troubleshooting.
## Available now
- A touch-friendly browser operator for the Mac mini and iPads, served by the controller itself.
- Room boundary, walls, cabinet positions, and reference device positions exported from the existing editor resources.
- Independent cabinet connections, game sessions, readiness, win/loss states, and command history.
- Scenario preparation across multiple cabinets. The entire assignment list is validated before commands are queued. Each cabinet acknowledges separately; this is not an atomic room-wide load.
- Compatible game lists and server-side and cabinet-side hardware checks. Games load from the cabinet's installed export; this does not transfer game binaries.
- Designer event mappings for `game_activated`, `game_started`, `game_won`, and `game_lost`. Other event mappings and nonstandard variants are rejected instead of silently ignored.
- Manual game assignments without scenario hardware rewards.
- Idempotent GPIO commands through the existing local bridge. Accepted commands do not prove a coin was delivered or a physical device changed.
External sensor inputs, detailed designer-defined puzzle steps, room timers/hints, and coordinated finale game variants are future work. The map's device dots are reference positions, not live sensor indicators. Runtime phases and hardware command acknowledgements are live.
## Deployment layout
```text
Mac mini / PC: Python controller :8766 + persistent SQLite database
^ browser operator (Mac / iPad, same controller URL)
^ managed Godot cabinet clients (one identity per Pi)
-> local GPIO bridge 127.0.0.1:8765 -> cabinet hardware
```
Use a trusted isolated room LAN. This HTTP configuration does not encrypt traffic; use TLS termination before using an untrusted network. Do not expose it directly to the internet. The GPIO bridge stays on each Pi's loopback address, not the controller address.
## 1. Export the designer catalog
The source of truth remains the existing Godot resources:
- `data/project_database.tres`: registered cabinets, games, and scenarios.
- `data/rooms/arcade_room.tres`: room layout and cabinet/device placements.
- `data/cabinets/*.tres`: cabinet IDs, capabilities, and hardware actions.
- `data/games/*.tres`: requirements and alternative control sets.
After saving designer changes, run this on the development computer (replace `godot` with your Godot executable):
```sh
godot --headless --path . --script tools/room_controller/export_catalog.gd
```
This writes `tools/room_controller/catalog.json`. Copy **server.py, operator.html, and catalog.json together** onto the controller machine. Restart the controller after updating the catalog. Re-export/redeploy Godot when game, cabinet, or hardware resources change; its own compatibility check uses those packaged resources.
Capabilities describe physical controls; they do not replace the per-device control mapping wizard. Configure both. Common requirements must all be present; when a game has control alternatives, one entire alternative must also be present. Tempest accepts horizontal joystick, spinner, or trackball plus two buttons and Start. Robotron accepts dual 8-way joysticks or one 8-way joystick plus four firing buttons, with Start. Missile Command requires trackball, three fire buttons, and Start.
The sample cabinet profiles are intentionally not expanded to claim hardware they may not have. Cabinet 01/02 currently list one action button; Cabinet 03 has no room placement. Update these resources to match the physical cabinets before expecting all seven games to appear in their compatible lists.
## 2. Start the controller on the Mac mini (or PC)
Python 3 is needed on the controller; Godot is not. Generate a different random token of at least 16 characters for the operator and for each cabinet. Keep tokens outside source control.
macOS / Linux:
```sh
export ARCADE_OPERATOR_TOKEN='your-generated-operator-token'
export ARCADE_CABINET_TOKENS='{"cabinet_01":"your-generated-cabinet-one-token","cabinet_02":"your-generated-cabinet-two-token"}'
python3 server.py --bind 0.0.0.0 --database /absolute/path/arcade_room.sqlite3
```
Windows PowerShell, from the project directory:
```powershell
$env:ARCADE_OPERATOR_TOKEN = 'your-generated-operator-token'
$env:ARCADE_CABINET_TOKENS = '{"cabinet_01":"your-generated-cabinet-one-token","cabinet_02":"your-generated-cabinet-two-token"}'
py -3 tools/room_controller/server.py --bind 0.0.0.0 --database arcade_room.sqlite3
```
The existing `ARCADE_CABINET_TOKEN` environment variable still provisions `cabinet_01` if that ID is not already in the JSON object. Cabinets without provisioned tokens cannot connect. Each cabinet token is bound to its ID and cannot control other cabinets or use the operator API.
Keep the database path stable. Allow inbound TCP 8766 on the room LAN. Existing single-cabinet state is migrated into cabinet 01's record; do not delete the database to upgrade. Back it up before deployment. Connectivity always starts offline until a cabinet polls again.
## 3. Start each Pi cabinet
Deploy the updated Godot export: the fleet controller requires protocol 2 (`arcade-cabinet-2`). Old cabinet exports are rejected so they cannot incorrectly load Pac-Man for a different assignment.
First run the normal launcher to map that Pi's physical controls. The managed client uses the saved mappings. Escape still quits.
Configure `user://arcade_network.cfg` on each Pi:
```ini
[network]
url="http://CONTROLLER_IP:8766"
cabinet_id="cabinet_01"
cabinet_token="your-generated-cabinet-one-token"
```
Godot CFG strings use double quotes. The file belongs in the app's user data folder, not beside the executable: normally `~/.local/share/godot/app_userdata/ArcadeEscape/` on Linux and `%APPDATA%/Godot/app_userdata/ArcadeEscape/` on Windows. URL/token environment variables override the file. `--cabinet=cabinet_02` overrides the cabinet ID.
Start the [GPIO bridge](../pi_gpio/README.md) on each Pi with that cabinet's controller ID and configured BCM pin allowlist. For the Cabinet 01 coin-release LED test:
```sh
GPIOZERO_PIN_FACTORY=lgpio python3 pi_gpio_bridge.py --controller cabinet_01_gpio --pins 17
./ArcadeEscape.arm64 -- --managed-cabinet --cabinet=cabinet_01 --gpio=real
```
Run those in separate terminals/services. Additional mapped actions require their actual pins in the bridge's allowlist. Do not use the cabinet 01 bridge identity on cabinet 02. Only run one managed game process per cabinet ID. The GPIO helper uses port **8765 locally**, while the room URL uses **8766**.
For desktop game/network testing, omit `--gpio=real`. Hardware actions report failure in simulation mode; they do not claim physical success.
## 4. Use the operator
Open `http://CONTROLLER_IP:8766/` in the Mac or iPad browser and enter the operator token. No iPad app or Godot installation is needed. The token stays in page memory and is cleared by Lock, reload, or closing the page.
1. Verify the expected cabinets show ONLINE.
2. Select a scenario and **Prepare assigned cabinets**. Each cabinet changes from PREPARING to READY only after its game loads. An offline or incompatible assignment prevents the entire dispatch.
3. Select a cabinet on the map or card, then **Start game**, or use the cabinet's mapped Start button.
4. Watch its runtime phase, hardware command statuses, and activity history. Win outputs follow that scenario's event mappings.
5. **Reset cabinet…** ends its current session. Reset active cabinets before preparing another scenario.
6. For an individual game override, select a compatible game and **Prepare game**. This deliberately removes scenario event mappings for that cabinet; no automatic coin reward is inherited.
The room map uses the saved designer positions. Cabinets without placements remain selectable through their cards. This is an operator view; continue editing layout/scenarios in Godot, then re-export the catalog.
The older exported `--operator` Godot window remains a Cabinet 01 quick-control view. Its Prepare Pac-Man button prepares the `proof_of_concept` scenario and keeps that scenario's reward. Use the browser for multiple cabinets and other game assignments.
## Reconnects and uncertain hardware results
- Cabinets become offline after roughly four seconds without a poll. The browser disables actions when the controller connection is unavailable.
- Commands expire after 15 seconds. Expired commands are not automatically reissued.
- Every command carries its cabinet session and unique ID. Old-session reports are ignored after Reset/Prepare.
- Duplicate command deliveries reuse the agent outcome; the GPIO bridge persists request IDs across helper restarts to prevent replayed pulses.
- A cabinet process restart requires preparation again. A reconnect by the same process can deliver its buffered reports.
- An accepted GPIO command confirms the bridge API call, not physical delivery. A timeout/failure is uncertain; inspect the hardware before trying again. There is no automatic reward retry.
- Keep the GPIO request ledger at its configured path, default `~/.local/state/arcade_gpio/requests.sqlite3`. Do not delete it to replay a command.
- Reset unloads the game and clears the controller session; it is not a physical hardware reset. Existing bridge pulse timers/watchdog still apply.
## Verification
```sh
python3 -m unittest discover -s tests/room_network -v
python3 -m unittest discover -s tests/gpio -v
python3 tests/room_network/check_integration.py /path/to/godot
godot --headless --path . --script tests/room_network/check_capabilities.gd
```
The integration test starts temporary localhost services on 8765/8766 and two actual headless Godot clients. It prepares Pac-Man via a scenario and Galaga via a manual assignment, forces wins only in the test harness, and verifies exactly one fake GPIO activation. Do not run while real services occupy those ports.
Deployment checklist: verify both cabinet IDs/tokens, test physical controls on each Pi, prepare a scenario and confirm both game names, win and check the intended LED/output only, reset, then unplug/reconnect one cabinet and confirm its offline/recovery behavior. Finally test the browser on the actual iPad; desktop browser checks do not replace device testing.