492bdb95e3
Receive real-time events from games (CS2, Dota 2, LoL, etc.) and drive LED effects through the existing color strip and value source pipelines. Core: - GameEventBus (thread-safe pub/sub) with standardized 23-type event vocabulary - GameAdapter ABC + AdapterRegistry + MappingAdapter (YAML-driven) - Built-in adapters: CS2 GSI, Dota 2 GSI, LoL Live Client, Generic Webhook - Community YAML adapters: Minecraft, Valorant, Rocket League - GameEventColorStripStream with 5 effects (flash/pulse/sweep/color_shift/breathing) - GameEventValueSource with EMA smoothing and timeout - 4 built-in effect presets (FPS Combat, MOBA Health, Racing, Generic Alert) - Auto-setup for Valve GSI games (Steam path detection, cfg file writing) - Demo capture engine exposed to non-demo mode Frontend: - Game tab in Streams tree navigation with integration cards - Game integration editor modal with adapter picker, config fields, event mappings - game_event source type in CSS and ValueSource editors - Setup instructions overlay (markdown rendered) - Live event monitor and connection test API: - Full CRUD for game integrations - Event ingestion endpoint (adapter-level auth) - Adapter metadata, presets, auto-setup, status/diagnostics endpoints
7.4 KiB
7.4 KiB
Phase 2: Storage & API — Game Integration Configs
Status: ✅ Complete Parent plan: PLAN.md Domain: backend
Objective
Create the persistence layer and REST API for managing game integration configurations. Users create/edit/delete game integration configs, and games POST events to an ingestion endpoint.
Tasks
- Task 1: Create
GameIntegrationConfigdataclass (storage/game_integration.py)- Fields: id (gi_<8hex>), name, adapter_type, enabled, adapter_config (dict), event_mappings (list of EventMapping dicts), created_at, updated_at, description, tags
EventMappingdataclass: event_type, effect, color ([R,G,B]), duration_ms, intensity, priority- Standard to_dict/from_dict/create_from_kwargs/apply_update methods
- Task 2: Create
GameIntegrationStore(storage/game_integration_store.py)- Extend BaseSqliteStore[GameIntegrationConfig]
- CRUD methods: create_integration, update_integration, delete (inherited)
- Name uniqueness validation
- get_references() for cascade prevention
- Task 3: Create Pydantic schemas (
api/schemas/game_integration.py)- GameIntegrationCreate, GameIntegrationUpdate, GameIntegrationResponse, GameIntegrationListResponse
- EventMappingSchema
- GameEventPayload (for ingestion endpoint)
- AdapterInfoResponse, AdapterListResponse (for adapter metadata endpoint)
- GameIntegrationStatusResponse (for diagnostics)
- Task 4: Create API routes (
api/routes/game_integration.py)- GET /api/v1/game-integrations — list all configs
- POST /api/v1/game-integrations — create config
- GET /api/v1/game-integrations/{id} — get config
- PUT /api/v1/game-integrations/{id} — update config
- DELETE /api/v1/game-integrations/{id} — delete config
- POST /api/v1/game-integrations/{id}/event — receive game event payload (ingestion)
- GET /api/v1/game-integrations/{id}/status — connection status, last event, event count
- GET /api/v1/game-integrations/{id}/events — recent events for debugging
- GET /api/v1/game-adapters — list available adapter types + supported events + config schema
- Task 5: Wire into main.py — create store, register in dependencies, include router
- Task 6: Add to STORE_MAP in api/routes/system.py for backup/restore
- N/A — backups are SQLite-level (full database snapshot). Added
game_integrationsto_ENTITY_TABLESindatabase.pywhich automatically includes it in backup/restore.
- N/A — backups are SQLite-level (full database snapshot). Added
- Task 7: Add game_integrations_file to StorageConfig in config.py (if JSON-based) or ensure DB table
- Added
"game_integrations"to_ENTITY_TABLESindatabase.py— table auto-created on startup.
- Added
- Task 8: Event ingestion logic — parse payload through adapter, publish to EventBus, track per-integration state (for diff detection)
- Task 9: Write tests for store (CRUD, validation, uniqueness)
- Task 10: Write tests for API routes (create, list, update, delete, event ingestion)
Files to Modify/Create
server/src/wled_controller/storage/game_integration.py— dataclass modelsserver/src/wled_controller/storage/game_integration_store.py— SQLite storeserver/src/wled_controller/api/schemas/game_integration.py— Pydantic schemasserver/src/wled_controller/api/routes/game_integration.py— REST endpointsserver/src/wled_controller/main.py— wire store + routerserver/src/wled_controller/api/dependencies.py— add get_game_integration_storeserver/src/wled_controller/api/routes/system.py— add to STORE_MAPserver/src/wled_controller/config.py— add storage config (if needed)server/tests/storage/test_game_integration_store.py— store testsserver/tests/api/test_game_integration_routes.py— route tests
Acceptance Criteria
- Full CRUD for game integration configs via REST API
- Event ingestion endpoint parses payload through the correct adapter and publishes to EventBus
- Adapter metadata endpoint returns available adapters with supported events and config schemas
- Status endpoint shows last event time and event counts
- Store validates name uniqueness
- Included in backup/restore via STORE_MAP
- All tests pass
Notes
- The ingestion endpoint must be low-latency — games send at tick rate (16-64 Hz)
- Store per-integration prev_state dict in memory (not persisted) for diff-based trigger detection
- Auth for ingestion endpoint: adapter-level auth (e.g. CS2 token) checked before standard API auth
Review Checklist
- All tasks completed
- Code follows project conventions
- No unintended side effects
- Tests pass
Handoff to Next Phase
Completed: All 10 tasks implemented and tested. 48 tests pass, 0 failures. Ruff clean.
What was built
storage/game_integration.py—EventMappingandGameIntegrationConfigdataclasses with fullto_dict()/from_dict()/create_from_kwargs()/apply_update()methods.apply_update()returns a new instance (immutable pattern).storage/game_integration_store.py—GameIntegrationStoreextendingBaseSqliteStore[GameIntegrationConfig]. CRUD with name uniqueness, write-through caching. Aliases:get_all_integrations,get_integration,delete_integration.api/schemas/game_integration.py— Full Pydantic schema suite:EventMappingSchema,GameIntegrationCreate/Update/Response/ListResponse,GameEventPayload,AdapterInfoResponse/AdapterListResponse,GameIntegrationStatusResponse,GameEventResponse/RecentEventsResponse.api/routes/game_integration.py— 9 endpoints: full CRUD + event ingestion + status + recent events + adapter listing. Ingestion endpoint skipsAuthRequired(uses adapter-level auth viavalidate_auth()). Per-integration runtime state tracked in-memory (_prev_states,_integration_stats) with thread-safe access.database.py— Added"game_integrations"to_ENTITY_TABLESso the table is auto-created and included in database backups.api/dependencies.py— Addedget_game_integration_store()andget_game_event_bus()getters +init_dependencies()params.api/__init__.py— Registeredgame_integration_router.main.py— CreatesGameIntegrationStore(db)andGameEventBus(), passes both toinit_dependencies().
Key design decisions
- Ingestion auth: The
/eventendpoint does NOT useAuthRequired. Instead, it callsadapter_cls.validate_auth()with request headers and adapter config. This allows game clients (CS2 GSI, etc.) to authenticate with their own token scheme without needing the app's API key. - Runtime state: Per-integration
prev_state(for diff detection) and event stats are stored in module-level dicts with a threading lock. Not persisted — resets on server restart. - Connected heuristic: An integration is "connected" if it received an event within the last 30 seconds (based on monotonic time).
- Immutable updates:
GameIntegrationConfig.apply_update()returns a new instance; the store replaces the cache entry.
What Phase 3+ needs
GameEventBussingleton is now available viaget_game_event_bus()dependency — Phase 4/5 streams can subscribe to it.GameIntegrationStoreis available viaget_game_integration_store()— Phase 6 frontend can call the CRUD API.- Built-in adapters (Phase 3) should call
AdapterRegistry.register(MyAdapter)at import time to appear inGET /api/v1/game-adapters. - The
adapter_configfield on each integration stores adapter-specific secrets (e.g. CS2 auth token). Phase 3 adapters define their config schema viaget_config_schema().