Commit Graph
2 Commits
Author SHA1 Message Date
schmidt.florian c0bbaba99f Ingestion: route MQTT messages only to sources bound to the delivering broker
ci / build-test (push) Successful in 1m8s
MqttMessageRouter matched purely on topic with no endpoint predicate, and
RouteAsync was not even passed an endpoint id. Topic filters routinely overlap
between brokers — every Tasmota install publishes tele/+/SENSOR — so with two
brokers a message on A was ingested by a source bound to B. HA enforced the
binding on both workers; MQTT enforced it only at subscribe time.

Pass the endpoint id through: MQTTnet's event args carry the topic but not the
delivering connection, so CreateClient captures the id in the handler closure.
ResolveTopicsAsync drops its `|| EndpointId == null` clause to match, since an
unbound source is no longer routed and subscribing its topic everywhere would
only invite traffic nothing consumes.

That last part would silently kill unbound sources that work today, so a data
migration binds them to the single broker when exactly one exists — the case
where old and new behaviour coincide. Two or more brokers is left alone: the
old behaviour was already ambiguous and a guess could route a meter's data to
the wrong broker. HA sources are excluded; they have always required an
endpoint, so binding them would activate ingestion never previously running.

Claude-Session: https://claude.ai/code/session_01V6joyergfvVLFEizH1hJLd
2026-07-18 11:21:36 +02:00
schmidt.florian 4b0cad67df M3: live ingestion (MQTT/Tasmota + Home Assistant)
- PayloadExtractor: dot-path value/time extraction (Tasmota ENERGY.Total, bare scalars).
- MqttTopicMatcher: standard +/# wildcard matching.
- IngestionService: scale/offset, idempotent upsert on (meter_id, time), and a spurious-
  decrease guard for monotonic registers (allowed only with a reset/swap event) + source
  last-seen status.
- MqttMessageRouter + MqttIngestionWorker (MQTTnet 5): per-endpoint persistent connections,
  topic subscription, graceful degradation; secrets resolved by env-var reference.
- Home Assistant: HaStateClient (REST /api/states parse) + HomeAssistantWorker polling on
  each source's interval. HA-via-MQTT also works through the MQTT path.
- Ingestion workers gated by MeterVault:EnableLiveIngestion (off in tests).

85 tests green (53 Core + 32 integration): Tasmota payload → reading verified end to end.

Follow-up (polish): HA WebSocket push (state_changed) as an alternative to REST poll;
source-topic index caching in the router.

Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
2026-07-13 11:45:20 +02:00