homebridge-eightsleepthermostat
Version:
Homebridge thermostat accessory for the Eight Sleep Pod smart bed.
237 lines • 10.4 kB
JavaScript
"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