diff --git a/app/fanout/mqtt_ha.py b/app/fanout/mqtt_ha.py index 093401d..eca696f 100644 --- a/app/fanout/mqtt_ha.py +++ b/app/fanout/mqtt_ha.py @@ -576,12 +576,32 @@ class MqttHaModule(FanoutModule): ) ) - # Tracked contacts — resolve names from DB best-effort + # Tracked contacts — resolve names and LPP sensors from DB best-effort for pub_key in self._tracked_contacts: cname = await self._resolve_contact_name(pub_key) configs.append( _contact_tracker_discovery_config(self._prefix, pub_key, cname, self._radio_key) ) + # LPP sensor entities for contacts with telemetry history + latest_ct = await self._resolve_latest_contact_telemetry(pub_key) + latest_ct_data = latest_ct.get("data", {}) if latest_ct else {} + ct_lpp_sensors = latest_ct_data.get("lpp_sensors", []) + if ct_lpp_sensors: + ct_nid = _node_id(pub_key) + ct_device = _device_payload( + pub_key, cname, "Node", via_device_key=self._radio_key + ) + ct_state_topic = f"{self._prefix}/{ct_nid}/telemetry" + configs.extend( + _lpp_discovery_configs( + self._prefix, pub_key, ct_device, ct_lpp_sensors, ct_state_topic + ) + ) + if latest_ct_data: + ct_payload = _repeater_telemetry_payload(latest_ct_data) + cached_repeater_states.append( + (f"{self._prefix}/{_node_id(pub_key)}/telemetry", ct_payload) + ) # Message event entity (namespaced to this radio) configs.append(_message_event_discovery_config(self._prefix, self._radio_key, radio_name)) @@ -644,6 +664,17 @@ class MqttHaModule(FanoutModule): pass return None + @staticmethod + async def _resolve_latest_contact_telemetry(pub_key: str) -> dict | None: + """Return the most recent contact telemetry row, or None.""" + try: + from app.repository.contact_telemetry import ContactTelemetryRepository + + return await ContactTelemetryRepository.get_latest(pub_key) + except Exception: + pass + return None + def _seed_radio_identity_from_runtime(self) -> None: """Best-effort bootstrap from the currently connected radio session.""" try: @@ -749,7 +780,7 @@ class MqttHaModule(FanoutModule): return pub_key = data.get("public_key", "") - if pub_key not in self._tracked_repeaters: + if pub_key not in self._tracked_repeaters and pub_key not in self._tracked_contacts: return nid = _node_id(pub_key) diff --git a/app/routers/contacts.py b/app/routers/contacts.py index 2f944ad..6ff1afb 100644 --- a/app/routers/contacts.py +++ b/app/routers/contacts.py @@ -663,6 +663,20 @@ async def request_contact_telemetry(public_key: str) -> ContactTelemetryResponse data=data, ) + # Dispatch to fanout modules (e.g. HA MQTT) + from app.fanout.manager import fanout_manager + + asyncio.create_task( + fanout_manager.broadcast_telemetry( + { + "public_key": contact.public_key, + "name": contact.name or contact.public_key[:12], + "timestamp": fetched_at, + **data, + } + ) + ) + # Fetch recent history (30 days) since = fetched_at - 30 * 86400 rows = await ContactTelemetryRepository.get_history(contact.public_key, since)