import logging import threading from datetime import datetime logger = logging.getLogger(__name__) class HaPoller: """Background daemon thread that periodically polls Home Assistant for battery percentages and updates installed batteries in the DB. Lifecycle: poller = HaPoller(ha_client, session_factory, interval_seconds) poller.start() # called once from create_app poller.stop() # not normally needed; daemon thread exits with the process """ def __init__(self, ha_client, session_factory, interval: int): self._client = ha_client self._Session = session_factory self._interval = interval self._stop = threading.Event() self._thread = threading.Thread( target=self._run, name="ha-poller", daemon=True ) def start(self): self._thread.start() logger.info("HA poller started (interval=%ds)", self._interval) def stop(self): self._stop.set() def _run(self): # Wait first so startup DB activity settles before the first poll while not self._stop.wait(self._interval): self._poll_once() def _poll_once(self): from models import Battery, BatteryPctLog, Device # local import avoids circular-import risk session = self._Session() try: devices = ( session.query(Device) .filter(Device.ha_entity_id.isnot(None)) .all() ) for device in devices: pct = self._client.get_state(device.ha_entity_id) if pct is not None: for battery in device.batteries: if battery.status == "installed" and battery.battery_percentage != pct: battery.battery_percentage = pct session.add(BatteryPctLog( battery_id=battery.id, percentage=pct, recorded_at=datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), source="poll", )) session.commit() logger.debug("HA poll complete (%d devices checked)", len(devices)) except Exception as exc: session.rollback() logger.warning("HA poll failed: %s", exc) finally: session.close()