import type { Logger } from "pino"; import type { Config } from "../../config.js"; import type { ZigbeeDevice, ZigbeeGroup } from "../../types/zigbee/bridge.js"; import type { MqttService } from "../mqtt/mqtt-service.js"; /** * Persistence options for the `DeviceRegistry`. * * When `persist` is `true`, both the device list and last-known device states * are saved to a JSON file on shutdown and restored on startup. Incoming MQTT * data always overwrites restored values on arrival, so persisted data is * never a source of truth — it is only a cold-start seed. */ export interface DeviceRegistryPersistenceOptions { /** * Whether to persist the device list and state to disk on shutdown * and restore them on startup. * * @default true */ persist?: boolean; /** * Path to the JSON file for device registry persistence. * Only used when `persist` is `true`. * * @default "./device-registry.json" */ filePath?: string; } export type DeviceStateChangeHandler = (state: Record, prev: Record | undefined) => void; export type DeviceAddedHandler = (device: ZigbeeDevice) => void; export type DeviceRemovedHandler = (device: ZigbeeDevice) => void; /** Fired whenever a `bridge/groups` message changes the tracked group set (task 1.5). */ export type GroupsChangedHandler = () => void; /** * Human-readable name mappings for Zigbee2MQTT devices. * * Resolution order in `DeviceRegistry.getNiceName()`: * 1. Explicit per-device entry in `devices` * 2. Result of `transform(friendlyName)` if provided * 3. Raw `friendly_name` as-is * * @example * ```ts * const engine = createEngine({ * automationsDir: "...", * deviceRegistry: { * names: { * devices: { * "kitchen_motion_0x1a2b": "Kitchen Motion Sensor", * "living_room_bulb": "Living Room Lamp", * }, * transform: (name) => name.replace(/_/g, " "), * }, * }, * }); * ``` */ export interface DeviceNiceNames { /** * Per-device overrides. Key is the Zigbee2MQTT `friendly_name`, * value is the human-readable display name. */ devices?: Record; /** * Global fallback transform applied when no per-device entry exists. * Receives the raw `friendly_name` and returns a display name. */ transform?: (friendlyName: string) => string; } /** * Discovers Zigbee2MQTT devices and tracks their state. * * When enabled, this service: * 1. Subscribes to `{prefix}/bridge/devices` to build a device list. * 2. Subscribes to `{prefix}/bridge/event` to react to join/leave events. * 3. Maintains per-device MQTT subscriptions to track live state. * * Device state updates are **merged** — a partial update (e.g. only `brightness`) * is applied on top of the previously-known state, mirroring Zigbee2MQTT behaviour. */ export declare class DeviceRegistry { private readonly mqtt; private readonly config; private readonly logger; private readonly niceNames; /** Keyed by `friendly_name`. */ private readonly devices; /** Last-known state per device, keyed by `friendly_name`. */ private readonly deviceStates; /** Keyed by numeric group id (design.md D2). */ private readonly groups; /** Handlers fired whenever a `bridge/groups` message changes the tracked set. */ private readonly groupsChangedHandlers; /** Per-device state change handlers. */ private readonly stateHandlers; /** Handlers fired when a device is added to the registry. */ private readonly deviceAddedHandlers; /** Handlers fired when a device is removed from the registry. */ private readonly deviceRemovedHandlers; /** * Active per-device MQTT handlers, keyed by `friendly_name`. * Stored so they can be passed to `mqtt.unsubscribe()` during cleanup. */ private readonly mqttDeviceHandlers; /** Handler for the `bridge/devices` topic — stored for unsubscribe. */ private bridgeDevicesHandler; /** Handler for the `bridge/groups` topic — stored for unsubscribe. */ private bridgeGroupsHandler; /** Handler for the `bridge/event` topic — stored for unsubscribe. */ private bridgeEventHandler; private readonly persist; private readonly filePath; constructor(mqtt: MqttService, config: Config, logger: Logger, niceNames?: DeviceNiceNames, persistenceOptions?: DeviceRegistryPersistenceOptions); /** * Restore persisted device list and state from disk (if persistence is enabled). * Must be called before `start()` so that the registry is seeded before MQTT * subscriptions fire. Incoming MQTT data always wins over persisted data. */ load(): Promise; /** * Persist the current device list and state to disk (if persistence is enabled). * Called by the engine before `stop()` on shutdown. */ save(): Promise; /** * Subscribe to bridge topics and begin tracking devices. * Must be called after the MQTT client is connected. */ start(): void; /** * Unsubscribe all topics and clear internal state. */ stop(): void; /** Return all tracked non-coordinator devices. */ getDevices(): ZigbeeDevice[]; /** Return a single device by `friendly_name`, or `undefined` if not found. */ getDevice(friendlyName: string): ZigbeeDevice | undefined; /** Return `true` if the named device is currently tracked. */ hasDevice(friendlyName: string): boolean; /** Return all tracked Zigbee groups. */ getGroups(): ZigbeeGroup[]; /** Return a single group by its numeric id, or `undefined` if not tracked. */ getGroup(id: number): ZigbeeGroup | undefined; /** * Register a handler that fires once per `bridge/groups` message that * changes the tracked set (task 1.5). */ onGroupsChanged(handler: GroupsChangedHandler): void; /** Remove a previously-registered groups-changed handler. */ offGroupsChanged(handler: GroupsChangedHandler): void; /** * Return a human-readable display name for the given `friendly_name`. * * Resolution order: * 1. Explicit per-device entry from `DeviceNiceNames.devices` * 2. Result of `DeviceNiceNames.transform(friendlyName)` * 3. Raw `friendly_name` as-is * * Works even before the device has been seen on the network. */ getNiceName(friendlyName: string): string; /** * Return the last-known merged state for a device, or `undefined` if no * state has been received yet. */ getDeviceState(friendlyName: string): Record | undefined; /** * Register a handler that fires whenever a device's state changes. * The handler receives the **full merged** new state and the previous state. */ onDeviceStateChange(friendlyName: string, handler: DeviceStateChangeHandler): void; /** Remove a previously-registered state change handler. */ offDeviceStateChange(friendlyName: string, handler: DeviceStateChangeHandler): void; /** Register a handler called whenever a new device is added to the registry. */ onDeviceAdded(handler: DeviceAddedHandler): void; /** Remove a previously-registered device-added handler. */ offDeviceAdded(handler: DeviceAddedHandler): void; /** Register a handler called whenever a device is removed from the registry. */ onDeviceRemoved(handler: DeviceRemovedHandler): void; /** Remove a previously-registered device-removed handler. */ offDeviceRemoved(handler: DeviceRemovedHandler): void; /** * Maps a device's `definition.exposes` from the raw Zigbee2MQTT shape into * the source-neutral capability vocabulary (design.md D22). Returns a new * device object; the incoming object (and its raw `exposes` payload as * published on the wire) is left untouched. */ private mapExposes; /** * Handle a payload from `{prefix}/bridge/devices`. * Diffs the incoming list against the current registry and subscribes / * unsubscribes per-device topics as needed. */ private handleBridgeDevices; /** * Handle a payload from `{prefix}/bridge/groups`. * Diffs the incoming list against the currently tracked groups: additions, * in-place updates (e.g. a rename or membership change), and removals * (task 1.3; specs/zigbee-groups "Group Discovery"). * * A malformed payload — not a list, or an entry missing a numeric `id` or * a string `friendly_name` — is skipped with a warning rather than * aborting the update (task 1.4). */ private handleBridgeGroups; /** * Handle a payload from `{prefix}/bridge/event`. * On join or leave events, re-request the full device list to keep the * registry consistent with the broker's authoritative state. */ private handleBridgeEvent; /** * Add a device to the registry: store metadata, subscribe to its topic, * and fire `onDeviceAdded` listeners. */ private addDevice; /** * Remove a device from the registry: clear its state, unsubscribe from its * topic, and fire `onDeviceRemoved` listeners. */ private removeDevice; /** * Merge an incoming device state payload into the current state and notify * any registered handlers. */ private handleDeviceState; } //# sourceMappingURL=device-registry.d.ts.map