mirror of
https://github.com/Matysh/houseplan-card
synced 2026-08-01 00:48:29 +00:00
Trail recorder: seed a run already in progress, and test the wiring
Only TrailBook was covered; the HA-facing half — subscription callback, attribute dialects, map-id resolution, run end on docking — had no test at all. It does now, against a stubbed hass, which is also where the missing behaviour showed up: recording started at the NEXT state change, so an HA restart (or finishing calibration) mid-cleanup dropped the opening seconds of the path. Sampling is factored out and runs once per source on setup and on every refresh.
This commit is contained in:
@@ -103,6 +103,11 @@ class TrailRecorder:
|
||||
self._unsub_track = async_track_state_change_event(
|
||||
self.hass, sorted(ents), self._on_state
|
||||
)
|
||||
# A run already in progress (HA restarted mid-cleanup, or the user just
|
||||
# finished calibrating) must start recording NOW, not at the next
|
||||
# state change — otherwise the first seconds of the path are lost.
|
||||
for src in self.pairs:
|
||||
self._sample(src, time.time())
|
||||
|
||||
def teardown(self) -> None:
|
||||
if self._unsub_track:
|
||||
@@ -123,36 +128,41 @@ class TrailRecorder:
|
||||
return e.entity_id
|
||||
return None
|
||||
|
||||
def _sample(self, src: str, now: float) -> bool:
|
||||
"""Record one point (or end the run) for a single source entity."""
|
||||
pair = self.pairs.get(src)
|
||||
if not pair:
|
||||
return False
|
||||
marker, vac = pair
|
||||
st_vac = self.hass.states.get(vac)
|
||||
if not st_vac or st_vac.state not in MOVING_STATES:
|
||||
return self.book.end_run(marker, now)
|
||||
st_src = self.hass.states.get(src)
|
||||
attrs = st_src.attributes if st_src else {}
|
||||
pos = attrs.get("vacuum_position") or attrs.get("robot_position")
|
||||
if not isinstance(pos, dict):
|
||||
return False
|
||||
try:
|
||||
x, y = float(pos["x"]), float(pos["y"])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return False
|
||||
map_id = str(
|
||||
attrs.get("map_name")
|
||||
or attrs.get("current_map")
|
||||
or attrs.get("map_index")
|
||||
or st_vac.attributes.get("selected_map")
|
||||
or "default"
|
||||
)
|
||||
return self.book.on_point(marker, map_id, x, y, now)
|
||||
|
||||
@callback
|
||||
def _on_state(self, event: Any) -> None:
|
||||
eid = event.data.get("entity_id")
|
||||
now = time.time()
|
||||
changed = False
|
||||
for src, (marker, vac) in self.pairs.items():
|
||||
if eid not in (src, vac):
|
||||
continue
|
||||
st_vac = self.hass.states.get(vac)
|
||||
moving = bool(st_vac and st_vac.state in MOVING_STATES)
|
||||
if not moving:
|
||||
changed |= self.book.end_run(marker, now)
|
||||
continue
|
||||
st_src = self.hass.states.get(src)
|
||||
attrs = st_src.attributes if st_src else {}
|
||||
pos = attrs.get("vacuum_position") or attrs.get("robot_position")
|
||||
if not isinstance(pos, dict):
|
||||
continue
|
||||
try:
|
||||
x, y = float(pos["x"]), float(pos["y"])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
continue
|
||||
map_id = str(
|
||||
attrs.get("map_name")
|
||||
or attrs.get("current_map")
|
||||
or attrs.get("map_index")
|
||||
or (st_vac.attributes.get("selected_map") if st_vac else None)
|
||||
or "default"
|
||||
)
|
||||
changed |= self.book.on_point(marker, map_id, x, y, now)
|
||||
for src, (_marker, vac) in self.pairs.items():
|
||||
if eid in (src, vac):
|
||||
changed |= self._sample(src, now)
|
||||
if changed:
|
||||
self._schedule_save()
|
||||
if now - self._last_fire >= FIRE_THROTTLE_S:
|
||||
|
||||
Reference in New Issue
Block a user