Files
ledgrab/plans/game-integration/phase-2-storage-api.md
T
alexei.dolgolyov 492bdb95e3 feat: game integration system
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
2026-03-31 13:17:52 +03:00

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 GameIntegrationConfig dataclass (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
    • EventMapping dataclass: 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_integrations to _ENTITY_TABLES in database.py which automatically includes it in backup/restore.
  • Task 7: Add game_integrations_file to StorageConfig in config.py (if JSON-based) or ensure DB table
    • Added "game_integrations" to _ENTITY_TABLES in database.py — table auto-created on startup.
  • 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 models
  • server/src/wled_controller/storage/game_integration_store.py — SQLite store
  • server/src/wled_controller/api/schemas/game_integration.py — Pydantic schemas
  • server/src/wled_controller/api/routes/game_integration.py — REST endpoints
  • server/src/wled_controller/main.py — wire store + router
  • server/src/wled_controller/api/dependencies.py — add get_game_integration_store
  • server/src/wled_controller/api/routes/system.py — add to STORE_MAP
  • server/src/wled_controller/config.py — add storage config (if needed)
  • server/tests/storage/test_game_integration_store.py — store tests
  • server/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.pyEventMapping and GameIntegrationConfig dataclasses with full to_dict()/from_dict()/create_from_kwargs()/apply_update() methods. apply_update() returns a new instance (immutable pattern).
  • storage/game_integration_store.pyGameIntegrationStore extending BaseSqliteStore[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 skips AuthRequired (uses adapter-level auth via validate_auth()). Per-integration runtime state tracked in-memory (_prev_states, _integration_stats) with thread-safe access.
  • database.py — Added "game_integrations" to _ENTITY_TABLES so the table is auto-created and included in database backups.
  • api/dependencies.py — Added get_game_integration_store() and get_game_event_bus() getters + init_dependencies() params.
  • api/__init__.py — Registered game_integration_router.
  • main.py — Creates GameIntegrationStore(db) and GameEventBus(), passes both to init_dependencies().

Key design decisions

  • Ingestion auth: The /event endpoint does NOT use AuthRequired. Instead, it calls adapter_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

  • GameEventBus singleton is now available via get_game_event_bus() dependency — Phase 4/5 streams can subscribe to it.
  • GameIntegrationStore is available via get_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 in GET /api/v1/game-adapters.
  • The adapter_config field on each integration stores adapter-specific secrets (e.g. CS2 auth token). Phase 3 adapters define their config schema via get_config_schema().