UNPKG

@homebridge-plugins/homebridge-lutron-caseta-leap

Version:
104 lines 4.81 kB
import util from 'node:util'; import debug from 'debug'; const logDebug = debug('leap:bridge'); export class OccupancySensorRouter { static instance; // map (bridgeID, occupancygroup Hrefs) to their callbacks cbMap; // map that tracks bridge subscriptions subMap; // map to track state information stateMap; constructor() { this.cbMap = new Map(); this.subMap = new Map(); this.stateMap = new Map(); } makeKey(bridgeID, ocg) { return `${bridgeID}_${ocg.href}`; } updateState(bridgeID, update) { logDebug('update state'); logDebug(util.inspect(update, { depth: null })); for (const grpStat of update.OccupancyGroupStatuses) { const key = this.makeKey(bridgeID, grpStat.OccupancyGroup); logDebug(`handling update for ${util.inspect(key, { depth: null })} to ${grpStat.OccupancyStatus}`); this.stateMap.set(key, grpStat.OccupancyStatus); } } callRegistered(bridgeID, update) { for (const grpStat of update.OccupancyGroupStatuses) { const key = this.makeKey(bridgeID, grpStat.OccupancyGroup); logDebug(`calling cb for ${util.inspect(key, { depth: null })} to ${grpStat.OccupancyStatus}`); const cb = this.cbMap.get(key); if (cb) { cb(grpStat.OccupancyStatus); } } } static getInstance() { if (!OccupancySensorRouter.instance) { OccupancySensorRouter.instance = new OccupancySensorRouter(); } return OccupancySensorRouter.instance; } async subscribeToBridge(bridge) { // Subscribe to occupancy updates for the provided bridge. this.subMap.set(bridge.bridgeID, new Promise((resolve, reject) => { // update state and call all registered callbacks when we get an update const _handleOccupancyUpdate = (r) => { logDebug('subscription cb called'); this.updateState(bridge.bridgeID, r.Body); this.callRegistered(bridge.bridgeID, r.Body); }; // we get a complete listing of occupancy groups and their // statuses when we subscribe, so update our internal state // while we've got the info handy const _handleGlobalUpdate = (initial) => { logDebug(`response from subscription call recd: ${util.inspect(initial, { depth: null })}`); this.updateState(bridge.bridgeID, initial); }; // subscribe to occupancy updates for this bridge, and... bridge .subscribeToOccupancy(_handleOccupancyUpdate.bind(this)) .then(_handleGlobalUpdate.bind(this)) // resolve the promise that we'll subscribe to the bridge .then(() => resolve()) .catch(e => reject(e)); // when the bridge is disconnected, subscriptions are lost. re-establish them. bridge.on('disconnected', () => { bridge.subscribeToOccupancy(_handleOccupancyUpdate.bind(this)).then(_handleGlobalUpdate.bind(this)); // WARNING: uncaught throw here will crash the program, but // there's nothing to be done if re-subscribing fails. // better to just blow it all up. }); })); } async register(bridge, occupancyGroup, cb) { // Register the specified bridge's occupancy group to have cb called // when there's an update. This function will subscribe to the bridge if // it hasn't already been done. // Create the key used for looking into the three state maps we're about to use const key = this.makeKey(bridge.bridgeID, occupancyGroup); // If we're not already subscribed to this bridge's updates, let's do that. if (!this.subMap.has(bridge.bridgeID)) { logDebug(`bridge ${bridge.bridgeID} is a new bridge`); this.subscribeToBridge(bridge); } await this.subMap.get(bridge.bridgeID); // Store this registration's callback this.cbMap.set(key, cb); // get stored state information for the occupancygroup that's // registering itself, and immediately call its callback to update it. // n.b. that this may not be this bridge's first registration request, // so the above nonsense about initial status might have happened on a // prior registration, and is async anyway. that's why we look in the // cache. const state = this.stateMap.get(key); if (state) { logDebug(`calling callback for ${key}`); cb(state); } } } //# sourceMappingURL=OccupancySensorRouter.js.map