Ingestion: Home Assistant WebSocket push path
ci / build-test (push) Successful in 1m16s

HomeAssistantWebSocketWorker holds a persistent state_changed subscription per HA endpoint that opts in via the connector's WebSocket toggle (HaEndpointConfig.UseWebSocket): auth handshake, subscribe, ingest in real time, capped-backoff reconnect. The REST poll worker skips WS endpoints so each is served once. HaWebSocketProtocol holds the pure handshake/parse logic. Verified by 11 protocol unit tests + a live integration test against an in-process fake HA server. CLAUDE.md updated.

Claude-Session: https://claude.ai/code/session_01Kib2MniVFbD95fkgLgBBnB
This commit is contained in:
2026-07-17 11:05:01 +02:00
parent 14930bc3d8
commit 8550ed8d9e
10 changed files with 695 additions and 8 deletions
@@ -54,9 +54,14 @@ public sealed class HomeAssistantWorker(
var ingestion = scope.ServiceProvider.GetRequiredService<IngestionService>();
var client = new HaStateClient(_httpClientFactory.CreateClient());
var endpoints = await db.IngestionEndpoints
// Endpoints using the WebSocket push path are served by HomeAssistantWebSocketWorker; skip
// them here so a source is never both polled and pushed.
var enabled = await db.IngestionEndpoints
.Where(e => e.IsEnabled && e.Type == EndpointType.HomeAssistant)
.ToDictionaryAsync(e => e.Id, cancellationToken).ConfigureAwait(false);
.ToListAsync(cancellationToken).ConfigureAwait(false);
var endpoints = enabled
.Where(e => !HaEndpointConfig.Parse(e.Config).UseWebSocket)
.ToDictionary(e => e.Id);
if (endpoints.Count == 0)
{
return;