import { readFile } from "node:fs/promises"; import type Database from "better-sqlite3"; import { upsertDevices, recordSeenMacs, type DiscoveredDevice } from "../db/index.js"; import { notifyNewDevices, type HomeAssistantConfig, type NewDeviceInfo } from "./homeAssistant.js"; // mac-oui-lookup is CommonJS; see routes/devices.ts for why this needs the // default-import-and-destructure form instead of a named import. import macOuiLookup from "mac-oui-lookup"; const { getVendor } = macOuiLookup; interface RawEntry { ip: string; mac: string; state: string; mdnsHostname?: string; } // Reads the JSON file produced by scripts/discover-devices.sh (a systemd timer // on the deploy host, not this process) and labels each entry against the // known-device list from config/hosts.yaml. See docs/device-discovery.md. export async function refreshDevices( db: Database.Database, filePath: string, knownDevices: Map, homeAssistant: HomeAssistantConfig | undefined, onNotifyError: (err: unknown) => void ): Promise { let raw: RawEntry[]; try { raw = JSON.parse(await readFile(filePath, "utf8")); } catch (err) { // Missing file (timer hasn't run yet, or not deployed on this host) isn't // fatal — just means no device data this cycle. if ((err as NodeJS.ErrnoException).code === "ENOENT") return; throw err; } const devices: DiscoveredDevice[] = raw.map((entry) => ({ ip: entry.ip, mac: entry.mac, knownName: knownDevices.get(entry.ip) ?? null, mdnsHostname: entry.mdnsHostname ?? null, })); upsertDevices(db, devices); const newMacs = new Set(recordSeenMacs(db, raw.map((r) => r.mac))); if (newMacs.size > 0 && homeAssistant) { const newDevices: NewDeviceInfo[] = raw .filter((r) => newMacs.has(r.mac)) .map((r) => ({ ip: r.ip, mac: r.mac, vendor: getVendor(r.mac), name: knownDevices.get(r.ip) ?? r.mdnsHostname ?? null, })); try { await notifyNewDevices(homeAssistant, newDevices); } catch (err) { onNotifyError(err); } } }