UNPKG

homebridge

Version:
166 lines 6.05 kB
/** * Accessory Query * * Handles accessory lookup and UI display collection methods. */ import { Logger } from '../../logger.js'; const log = Logger.withPrefix('Matter/Server'); /** * Add each cluster's featureMap to the declared cluster state. * * The declared clusters are what the PLUGIN asked for, so on their own they * cannot tell a consumer which features the cluster ended up with - a * thermostat with and without AutoMode declares the same setpoints, and the * deadband is optional either way. The featureMap (named booleans, e.g. * `{ heating: true, cooling: true, autoMode: false }`) comes off the live * endpoint and settles that. * * Features never change after registration and the UI merges later state * updates into the cluster object rather than replacing it, so sending the map * once is enough. */ function withFeatureMaps(clusters, endpoint) { const endpointState = endpoint?.state; if (!endpointState) { // Not registered yet (or restored without a live endpoint) - serve the // declared state as-is and let the consumer fall back return clusters; } const enriched = {}; for (const [clusterName, state] of Object.entries(clusters)) { try { const featureMap = endpointState[clusterName]?.featureMap; enriched[clusterName] = featureMap !== undefined ? { featureMap, ...state } : state; } catch { // Reading a behavior's state can throw while the endpoint is still // initialising - the declared state alone is still correct enriched[clusterName] = state; } } return enriched; } export class AccessoryQuery { accessories; getAccessoryCache; constructor(accessories, getAccessoryCache) { this.accessories = accessories; this.getAccessoryCache = getAccessoryCache; } /** * Get all registered accessories (Plugin API) */ getAccessories() { return Array.from(this.accessories.values(), (acc) => { // eslint-disable-next-line unused-imports/no-unused-vars const { endpoint, registered, ...publicAccessory } = acc; return publicAccessory; }); } /** * Get a specific accessory by UUID (Plugin API) */ getAccessory(uuid) { const accessory = this.accessories.get(uuid); if (!accessory) { return undefined; } // eslint-disable-next-line unused-imports/no-unused-vars const { endpoint, registered, ...publicAccessory } = accessory; return publicAccessory; } /** * Get all cached accessories (Internal - for restore process) * @internal */ getAllCachedAccessories() { const cache = this.getAccessoryCache(); if (!cache) { log.debug('getAllCachedAccessories: No cache available'); return []; } const cached = cache.getAllCached(); log.debug(`getAllCachedAccessories: Returning ${cached.length} accessories`); return cached; } /** * Look up a cached accessory by UUID. O(1) Map lookup — prefer this over * scanning getAllCachedAccessories() when only one accessory is needed. * @internal */ getCachedAccessory(uuid) { return this.getAccessoryCache()?.getCached(uuid); } /** * Extract device type name from EndpointType */ getDeviceTypeName(deviceType) { return deviceType.name || 'Unknown'; } /** * Get current cluster state for an accessory or part, enriched with each * cluster's featureMap - see {@link withFeatureMaps} */ getCurrentState(uuid, partId) { const accessory = this.accessories.get(uuid); if (!accessory) { return {}; } if (partId) { // A part carries its own endpoint, so its clusters are enriched the same // way - a thermostat exposed as a part needs the features just as much const part = accessory._parts?.find(p => p.id === partId); return part ? withFeatureMaps(part.clusters || {}, part.endpoint) : {}; } return withFeatureMaps(accessory.clusters || {}, accessory.endpoint); } /** * Collect all accessories for UI display */ collectAccessories(bridgeUsername, bridgeType, bridgeName) { const accessories = []; for (const [uuid, accessory] of this.accessories.entries()) { const transformed = { uuid, displayName: accessory.displayName, deviceType: this.getDeviceTypeName(accessory.deviceType), clusters: this.getCurrentState(uuid), parts: accessory._parts?.map(part => ({ id: part.id, displayName: part.displayName, deviceType: this.getDeviceTypeName(part.deviceType), clusters: this.getCurrentState(uuid, part.id), })), bridge: { username: bridgeUsername, type: bridgeType, name: bridgeName, }, }; accessories.push(transformed); } return accessories; } /** * Get detailed info for a specific accessory */ getAccessoryInfo(uuid) { const accessory = this.accessories.get(uuid); if (!accessory) { return undefined; } return { uuid, displayName: accessory.displayName, deviceType: this.getDeviceTypeName(accessory.deviceType), clusters: this.getCurrentState(uuid), parts: accessory._parts?.map(part => ({ id: part.id, displayName: part.displayName, deviceType: this.getDeviceTypeName(part.deviceType), clusters: this.getCurrentState(uuid, part.id), })), }; } } //# sourceMappingURL=AccessoryQuery.js.map