// home-assistant's events. The tool runtime imports this once the broker is bound. It watches the // entities whose state changing is a real signal — a door opening, a lock turning, a switch // flipping — and emits when one does. // // Emits (novox/hq ADR 0041/0042): // module.home-assistant.state.changed — a watched entity's state value changed // // Bounded on purpose. Home Assistant has hundreds of entities and many (temperature, humidity, // power draw) tick constantly; emitting every tick would be noise, not signal. So the watch is // limited to actuator/contact domains where a change is an event a human would care about, and // only the discrete `state` value is diffed — not the attribute bag. The set is overridable with // MESH_HOMEASSISTANT_WATCH (comma-separated entity ids) for a node that wants a specific few. import { emit } from "@novox/mesh-sdk/events"; import { HomeAssistantClient, type HAEntityState } from "./client.js"; const ha = HomeAssistantClient.fromEnv(); // Domains whose state changing is meaningful rather than a continuous reading. const WATCH_DOMAINS = new Set(["binary_sensor", "lock", "cover", "switch", "input_boolean", "light", "alarm_control_panel"]); // An explicit allowlist of entity ids, if the node set one; otherwise fall back to the domain filter. const watchList = (process.env.MESH_HOMEASSISTANT_WATCH ?? "") .split(",") .map((s) => s.trim()) .filter(Boolean); const watchSet = watchList.length ? new Set(watchList) : null; function isWatched(s: HAEntityState): boolean { if (watchSet) return watchSet.has(s.entity_id); return WATCH_DOMAINS.has(s.entity_id.split(".")[0] ?? ""); } const nameOf = (s: HAEntityState): string | undefined => typeof s.attributes.friendly_name === "string" ? s.attributes.friendly_name : undefined; // Last seen state per watched entity. Primed silently on the first poll so a restart does not // re-announce the current state of everything as a fresh change. const lastState = new Map(); let primed = false; async function pollStates(): Promise { const states = (await ha.getStates()).filter(isWatched); for (const s of states) { const prev = lastState.get(s.entity_id); if (primed && prev !== undefined && prev !== s.state) { await emit("state.changed", { entity: s.entity_id, name: nameOf(s), from: prev, to: s.state, }); } lastState.set(s.entity_id, s.state); } primed = true; } const tick = (fn: () => Promise, everyMs: number): void => { const run = (): void => void fn().catch((err) => console.error(`[home-assistant] ${err}`)); setInterval(run, everyMs); run(); }; tick(pollStates, 15_000); console.log("[home-assistant] watching entity states, emitting on change");