UNPKG

@koush/ring-client-api

Version:

Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting

180 lines (179 loc) 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Thermostat = void 0; const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const ring_types_1 = require("../api/ring-types"); const base_device_accessory_1 = require("./base-device-accessory"); const hap_1 = require("./hap"); class Thermostat extends base_device_accessory_1.BaseDeviceAccessory { constructor(device, accessory, logger, config) { super(); this.device = device; this.accessory = accessory; this.logger = logger; this.config = config; const { Characteristic, Service } = hap_1.hap; // Component Device (Temperature Sensor) this.onTemperature = this.device.onComponentDevices.pipe((0, operators_1.switchMap)((devices) => { const temperatureSensor = devices.find(({ deviceType }) => deviceType === ring_types_1.RingDeviceType.TemperatureSensor); if (!temperatureSensor) { return []; } this.logger.debug(`Discovered a component temperature sensor for ${this.device.name}`); return temperatureSensor.onData.pipe((0, operators_1.map)(({ celsius: temperature }) => { this.logger.debug(`Component temperature sensor for ${this.device.name} reported ${temperature} degrees`); return temperature; }), (0, operators_1.distinctUntilChanged)()); })); // Required Characteristics this.registerObservableCharacteristic({ characteristicType: Characteristic.CurrentHeatingCoolingState, serviceType: Service.Thermostat, onValue: (0, rxjs_1.combineLatest)([this.onTemperature, this.device.onData]).pipe((0, operators_1.map)(([temperature, { setPoint, mode }]) => { if (mode === 'off') { // The thermostat is set to 'off', so the thermostat is neither heating nor cooling return Characteristic.CurrentHeatingCoolingState.OFF; } if (!temperature || !setPoint) { this.logger.error(`Could not determine 'CurrentHeatingCoolingState' for ${this.device.name} given temperature: ${temperature}, set point: ${setPoint} and mode: ${mode}. Reporting 'off' state as a fallback.`); return Characteristic.CurrentHeatingCoolingState.OFF; } // Checking with a threshold to avoid floating point weirdness const currentTemperatureEqualsTarget = Math.abs(temperature - setPoint) < 0.1, currentTemperatureIsHigherThanTarget = temperature - setPoint >= 0.1, currentTemperatureIsLowerThanTarget = temperature - setPoint <= -0.1; if (currentTemperatureEqualsTarget) { // The target temperature has been reached, so the thermostat is neither heating nor cooling return Characteristic.CurrentHeatingCoolingState.OFF; } if (currentTemperatureIsHigherThanTarget && mode === 'cool') { // The current temperature is higher than the target temperature, // and the thermostat is set to 'cool', so the thermostat is cooling return Characteristic.CurrentHeatingCoolingState.COOL; } if (currentTemperatureIsLowerThanTarget && (mode === 'heat' || mode === 'aux')) { // The current temperature is lower than the target temperature, // and the thermostat is set to 'heat' or 'aux' (emergency heat), so the thermostat is heating return Characteristic.CurrentHeatingCoolingState.HEAT; } // The current temperature is either higher or lower than the target temperature, // but the current thermostat mode would only increase the difference, // so the thermostat is neither heating nor cooling return Characteristic.CurrentHeatingCoolingState.OFF; })), }); this.registerCharacteristic({ characteristicType: Characteristic.TargetHeatingCoolingState, serviceType: Service.Thermostat, getValue: ({ mode }) => { switch (mode) { case 'off': return Characteristic.TargetHeatingCoolingState.OFF; case 'heat': case 'aux': return Characteristic.TargetHeatingCoolingState.HEAT; case 'cool': return Characteristic.TargetHeatingCoolingState.COOL; } }, setValue: (targetHeatingCoolingState) => { const mode = (() => { switch (targetHeatingCoolingState) { case Characteristic.TargetHeatingCoolingState.OFF: return 'off'; case Characteristic.TargetHeatingCoolingState.HEAT: return 'heat'; case Characteristic.TargetHeatingCoolingState.COOL: return 'cool'; default: return; } })(); if (!mode) { this.logger.error(`Couldn’t match ${targetHeatingCoolingState} to a recognized mode string.`); return; } this.logger.info(`Setting ${this.device.name} mode to ${mode}`); return this.device.setInfo({ device: { v1: { mode } } }); }, }); // Only allow 'TargetHeatingCoolingState's which can be mapped to Ring modes // Specifically, this omits .AUTO this.getService(Service.Thermostat) .getCharacteristic(Characteristic.TargetHeatingCoolingState) .setProps({ validValues: [ Characteristic.TargetHeatingCoolingState.OFF, Characteristic.TargetHeatingCoolingState.HEAT, Characteristic.TargetHeatingCoolingState.COOL, ], }); this.registerObservableCharacteristic({ characteristicType: Characteristic.CurrentTemperature, serviceType: Service.Thermostat, onValue: this.onTemperature.pipe((0, operators_1.map)((temperature) => { if (!temperature) { this.logger.error(`Could not determine 'CurrentTemperature' for ${this.device.name} given temperature: ${temperature}. Returning 22 degrees celsius as a fallback.`); return 22; } // Documentation: https://developers.homebridge.io/#/characteristic/CurrentTemperature // 'Characteristic.CurrentTemperature' supports 0.1 increments return Number(Number(temperature).toFixed(1)); })), }); this.registerCharacteristic({ characteristicType: Characteristic.TargetTemperature, serviceType: Service.Thermostat, getValue: ({ setPoint }) => { return setPoint; }, setValue: (setPoint) => { this.logger.info(`Setting ${this.device.name} target temperature to ${setPoint}`); // Documentation: https://developers.homebridge.io/#/characteristic/TargetTemperature // 'Characteristic.TargetTemperature' has a valid range from 10 to 38 degrees celsius, // but devices may support a different range. When limits differ, accept the more strict. const setPointMin = Math.max(this.device.data.setPointMin || 10, 10), setPointMax = Math.min(this.device.data.setPointMax || 38, 38); if (setPoint < setPointMin || setPoint > setPointMax) { this.logger.error(`Ignoring request to set ${this.device.name} target temperature to ${setPoint}. Target temperature must be between ${setPointMin} and ${setPointMax}.`); return; } return this.device.setInfo({ device: { v1: { setPoint } } }); }, }); if (this.device.data.setPointMin || this.device.data.setPointMax) { // Documentation: https://developers.homebridge.io/#/characteristic/TargetTemperature // 'Characteristic.TargetTemperature' has a valid range from 10 to 38 degrees celsius, // but devices may support a different range. When limits differ, accept the more strict. const setPointMin = Math.max(this.device.data.setPointMin || 10, 10), setPointMax = Math.min(this.device.data.setPointMax || 38, 38); this.logger.debug(`Setting ${this.device.name} target temperature range to ${setPointMin}–${setPointMax}`); this.getService(Service.Thermostat) .getCharacteristic(Characteristic.TargetTemperature) .setProps({ minValue: setPointMin, maxValue: setPointMax, validValueRanges: [setPointMin, setPointMax], }); } this.registerCharacteristic({ characteristicType: Characteristic.TemperatureDisplayUnits, serviceType: Service.Thermostat, getValue: () => { // Neither thermostats nor their component devices (e.g. temperature sensors) // appear to include the unit preference. Hardcoding Fahrenheit as the default. return Characteristic.TemperatureDisplayUnits.FAHRENHEIT; }, setValue: () => { // noop // Setting display unit is unsupported }, }); // Setting 'TemperatureDisplayUnits' is unsupported by the Ring API. // We’ve defaulted to Fahrenheit above, so only allowing .FAHRENHEIT. this.getService(Service.Thermostat) .getCharacteristic(Characteristic.TemperatureDisplayUnits) .setProps({ validValues: [Characteristic.TemperatureDisplayUnits.FAHRENHEIT], }); } } exports.Thermostat = Thermostat;