UNPKG

homebridge-eightsleepthermostat

Version:

Homebridge thermostat accessory for the Eight Sleep Pod smart bed.

237 lines 10.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AccessoryClientAdapter = exports.PlatformClientAdapter = void 0; const Client = __importStar(require("./clientRequest")); const clientRequest_1 = require("./clientRequest"); var DeviceMode; (function (DeviceMode) { DeviceMode["on"] = "smart"; DeviceMode["off"] = "off"; })(DeviceMode || (DeviceMode = {})); const stateFor = (newState) => { return { type: newState }; }; const resolveUsersUrl = (id) => `/users/${id}/temperature`; const resolveDevicesUrl = (id) => `/devices/${id}`; class PlatformClientAdapter { constructor(sharedDeviceId, log) { this.sharedDeviceId = sharedDeviceId; this.log = log; this.devicesEndpoint = resolveDevicesUrl(this.sharedDeviceId); this.sharedDeviceSettings = this.loadSharedDeviceState(); // Time of last CurrentTemp `GET` handler call by controller this.lastActive = Date.now(); this.refreshState = () => { this.sharedDeviceSettings = this.loadSharedDeviceState(); this.clearRefreshIfNotActive(); }; this.refreshInterval = this.startRefreshing(); } async loadSharedDeviceState() { try { const response = await Client.get((0, clientRequest_1.currentState)(this.devicesEndpoint), this.log); this.log.debug('Fetched current device status from API'); return response ? response.result : null; } catch (error) { this.log.error('Error fetching current device status from API:', error); return null; } } startRefreshing() { return setInterval(this.refreshState, 1000 * 10); } /** * When `GET` CurrentTemp handler fired, update last active timestamp. * Continuing refreshing state every 15 seconds while active, but once * handler hasn't been fired in more than 1.5 minutes, cancel the refresh * interval... i.e. we only continue hitting the client API while a home * controller is actively requesting an updated current temperature, * otherwise go into standby to prevent unnecessary requests */ clearRefreshIfNotActive() { if (this.refreshInterval && this.lastActive < Date.now() - 1000 * 90) { // Go into standby until next time there is controller activity global.clearInterval(this.refreshInterval); this.refreshInterval = null; } } setAsActive() { this.lastActive = Date.now(); if (!this.refreshInterval) { this.sharedDeviceSettings = this.loadSharedDeviceState(); this.refreshInterval = this.startRefreshing(); } } // Initiated by accessory get handler, triggers new active refresh interval async getCurrentLevel(side) { const currSettings = await this.sharedDeviceSettings; this.setAsActive(); if (currSettings) { return this.determineLevelFor(currSettings, side); } else { // Await result of new promise that was just initiated by `setAsActive()` const newSettings = await this.sharedDeviceSettings; return this.determineLevelFor(newSettings, side); } } // Fetches latest value w/o triggering new active refresh interval async loadMostRecentSettings(side) { const newSettings = await this.sharedDeviceSettings; return this.determineLevelFor(newSettings, side); } determineLevelFor(settings, side) { if (side === 'solo') { return this.getSoloLevel(settings); } else if (side === 'left') { return settings ? settings.leftHeatingLevel : 0; } else { return settings ? settings.rightHeatingLevel : 0; } } getSoloLevel(settings) { const leftLevel = settings === null || settings === void 0 ? void 0 : settings.leftHeatingLevel; const rightLevel = settings === null || settings === void 0 ? void 0 : settings.rightHeatingLevel; if (leftLevel && rightLevel) { const average = Math.round((leftLevel + rightLevel) / 2); return average; } else { this.log.debug('Something went wrong getting current level for solo user'); return 0; } } } exports.PlatformClientAdapter = PlatformClientAdapter; class AccessoryClientAdapter { constructor(accessoryUserId, log) { this.accessoryUserId = accessoryUserId; this.log = log; this.usersEndpoint = resolveUsersUrl(this.accessoryUserId); this.currentUserSettings = this.fetchCurrentSettings(); // Time of last `GET` handler call by controller to fetch on/off state OR target temp this.lastActive = Date.now(); /** * Refresh interval to continue updating state for accessory * while there is Homekit controller actvity */ this.refreshState = async () => { this.currentUserSettings = this.fetchCurrentSettings(); this.clearRefreshIfNotActive(); }; this.refreshInterval = this.startRefreshing(); } async fetchCurrentSettings() { try { // Returns `level` and `currentState`, i.e. mode `type: smart` or `type: off` const response = await Client.get((0, clientRequest_1.currentState)(this.usersEndpoint), this.log); this.log.debug('Fetched current user device settings from API'); return response; } catch (error) { this.log.error('Error fetching current user device settings from API'); return null; } } async getUserTargetLevel() { const settings = await this.currentUserSettings; this.setAccessoryAsActive(); // `currentLevel` represents temp at which bed is *currently set* to // i.e. not the measured temp of the bed, but target temp return settings ? settings.currentLevel : 0; } // Current Device On/Off Status & Updates async getAccessoryIsOn() { const settings = await this.currentUserSettings; this.setAccessoryAsActive(); return (settings === null || settings === void 0 ? void 0 : settings.currentState.type) !== DeviceMode.off; } // Get latest state without triggering active refresh interval async loadMostRecentSettings() { const settings = await this.currentUserSettings; if (settings) { const targetLevel = settings.currentLevel; const targetState = settings.currentState.type === DeviceMode.off ? 0 : 3; return [targetState, targetLevel]; } else { return [0, 0]; } } /** * `PUT` methods below to alter client device state after local changes. * - Each of these methods returns a full response object `UserSettings` * - Rather than making a `PUT` followed by a `GET`, we update the stored * `currentUserSettings` Promise with each client `PUT` response */ // Update Bed Temperature ('level') --> target temp locally == 'currentLevel' in client API async updateUserTargetLevel(newLevel) { const response = await Client.put((0, clientRequest_1.updateState)(this.usersEndpoint, 'currentLevel', newLevel)); this.updateCurrentSettingsFrom(response); this.log.debug('Updated bed temp (level):', response === null || response === void 0 ? void 0 : response.currentLevel); return response ? response.currentLevel : newLevel; } /** * Since client returns 'smart:bedtime', 'smart:initial', or 'smart:final' * depending on when the request is made, it makes checking if response * is === `BedState.on` complicated (`on` enum value is just 'smart'). * Easier to ensure not 'off' instead of checking if some 'smart:...' */ async turnOnAccessory() { const onState = stateFor(DeviceMode.on); const response = await Client.put((0, clientRequest_1.updateState)(this.usersEndpoint, 'currentState', onState)); this.updateCurrentSettingsFrom(response); return (response === null || response === void 0 ? void 0 : response.currentState.type) !== DeviceMode.off; } async turnOffAccessory() { const offState = stateFor(DeviceMode.off); const response = await Client.put((0, clientRequest_1.updateState)(this.usersEndpoint, 'currentState', offState)); this.updateCurrentSettingsFrom(response); return (response === null || response === void 0 ? void 0 : response.currentState.type) === DeviceMode.off; } updateCurrentSettingsFrom(response) { this.setAccessoryAsActive(); this.currentUserSettings = Promise.resolve(response); } setAccessoryAsActive() { this.lastActive = Date.now(); if (!this.refreshInterval) { this.currentUserSettings = this.fetchCurrentSettings(); this.refreshInterval = this.startRefreshing(); } } startRefreshing() { return setInterval(this.refreshState, 1000 * 10); } clearRefreshIfNotActive() { if (this.refreshInterval && this.lastActive < Date.now() - 1000 * 90) { // Go into standby until next time there is controller activity global.clearInterval(this.refreshInterval); this.refreshInterval = null; } } } exports.AccessoryClientAdapter = AccessoryClientAdapter; //# sourceMappingURL=clientAdapter.js.map