UNPKG

@koush/ring-client-api

Version:

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

269 lines (268 loc) 14.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RingPlatform = exports.pluginName = exports.platformName = void 0; const api_1 = require("../api"); const hap_1 = require("./hap"); const security_panel_1 = require("./security-panel"); const base_station_1 = require("./base-station"); const chime_1 = require("./chime"); const keypad_1 = require("./keypad"); const contact_sensor_1 = require("./contact-sensor"); const motion_sensor_1 = require("./motion-sensor"); const lock_1 = require("./lock"); const smoke_alarm_1 = require("./smoke-alarm"); const co_alarm_1 = require("./co-alarm"); const smoke_co_listener_1 = require("./smoke-co-listener"); const config_1 = require("./config"); const beam_1 = require("./beam"); const multi_level_switch_1 = require("./multi-level-switch"); const fan_1 = require("./fan"); const outlet_1 = require("./outlet"); const switch_1 = require("./switch"); const camera_1 = require("./camera"); const panic_buttons_1 = require("./panic-buttons"); const util_1 = require("../api/util"); const flood_freeze_sensor_1 = require("./flood-freeze-sensor"); const freeze_sensor_1 = require("./freeze-sensor"); const temperature_sensor_1 = require("./temperature-sensor"); const water_sensor_1 = require("./water-sensor"); const location_mode_switch_1 = require("./location-mode-switch"); const thermostat_1 = require("./thermostat"); const unknown_zwave_switch_1 = require("./unknown-zwave-switch"); const util_2 = require("./util"); const debug = __filename.includes('release-homebridge'), unsupportedDeviceTypes = [api_1.RingDeviceType.BaseStation, api_1.RingDeviceType.Keypad], ignoreHiddenDeviceTypes = [ api_1.RingDeviceType.RingNetAdapter, api_1.RingDeviceType.ZigbeeAdapter, api_1.RingDeviceType.CodeVault, api_1.RingDeviceType.SecurityAccessCode, api_1.RingDeviceType.ZWaveAdapter, api_1.RingDeviceType.ZWaveExtender, api_1.RingDeviceType.BeamsDevice, api_1.RingDeviceType.PanicButton, ]; exports.platformName = 'Ring'; exports.pluginName = 'homebridge-ring'; process.env.RING_DEBUG = debug ? 'true' : ''; function getAccessoryClass(device) { const { deviceType } = device; if (device.data.status === 'disabled') { return null; } switch (deviceType) { case api_1.RingDeviceType.ContactSensor: case api_1.RingDeviceType.RetrofitZone: case api_1.RingDeviceType.TiltSensor: case api_1.RingDeviceType.GlassbreakSensor: return contact_sensor_1.ContactSensor; case api_1.RingDeviceType.MotionSensor: return motion_sensor_1.MotionSensor; case api_1.RingDeviceType.FloodFreezeSensor: return flood_freeze_sensor_1.FloodFreezeSensor; case api_1.RingDeviceType.FreezeSensor: return freeze_sensor_1.FreezeSensor; case api_1.RingDeviceType.SecurityPanel: return security_panel_1.SecurityPanel; case api_1.RingDeviceType.BaseStation: return base_station_1.BaseStation; case api_1.RingDeviceType.Keypad: return keypad_1.Keypad; case api_1.RingDeviceType.SmokeAlarm: return smoke_alarm_1.SmokeAlarm; case api_1.RingDeviceType.CoAlarm: return co_alarm_1.CoAlarm; case api_1.RingDeviceType.SmokeCoListener: return smoke_co_listener_1.SmokeCoListener; case api_1.RingDeviceType.BeamsMotionSensor: case api_1.RingDeviceType.BeamsSwitch: case api_1.RingDeviceType.BeamsMultiLevelSwitch: case api_1.RingDeviceType.BeamsTransformerSwitch: case api_1.RingDeviceType.BeamsLightGroupSwitch: return beam_1.Beam; case api_1.RingDeviceType.MultiLevelSwitch: return device instanceof api_1.RingDevice && device.categoryId === api_1.RingDeviceCategory.Fans ? fan_1.Fan : multi_level_switch_1.MultiLevelSwitch; case api_1.RingDeviceType.MultiLevelBulb: return multi_level_switch_1.MultiLevelSwitch; case api_1.RingDeviceType.Switch: return device instanceof api_1.RingDevice && device.categoryId === api_1.RingDeviceCategory.Outlets ? outlet_1.Outlet : switch_1.Switch; case api_1.RingDeviceType.TemperatureSensor: return temperature_sensor_1.TemperatureSensor; case api_1.RingDeviceType.WaterSensor: return water_sensor_1.WaterSensor; case api_1.RingDeviceType.Thermostat: return thermostat_1.Thermostat; case api_1.RingDeviceType.UnknownZWave: return unknown_zwave_switch_1.UnknownZWaveSwitchSwitch; } if (/^lock($|\.)/.test(deviceType)) { return lock_1.Lock; } if (deviceType === api_1.RingDeviceType.Sensor) { // Generic sensor that could be any type of sensor, but should at least have `faulted` if (device.name.toLowerCase().includes('motion')) { return motion_sensor_1.MotionSensor; } return contact_sensor_1.ContactSensor; } return null; } class RingPlatform { constructor(log, config, api) { var _a, _b, _c; this.log = log; this.config = config; this.api = api; this.homebridgeAccessories = {}; (0, util_1.useLogger)({ logInfo(message) { log.info(message); }, logError(message) { log.error(message); }, }); if (!config) { this.log.info('No configuration found for platform Ring'); return; } config.cameraStatusPollingSeconds = (_a = config.cameraStatusPollingSeconds) !== null && _a !== void 0 ? _a : 20; config.cameraDingsPollingSeconds = (_b = config.cameraDingsPollingSeconds) !== null && _b !== void 0 ? _b : 2; config.locationModePollingSeconds = (_c = config.locationModePollingSeconds) !== null && _c !== void 0 ? _c : 20; this.api.on('didFinishLaunching', () => { this.log.debug('didFinishLaunching'); if (config.refreshToken) { this.connectToApi().catch((e) => { this.log.error('Error connecting to API'); this.log.error(e); }); } else { this.log.warn('Plugin is not configured. Visit https://github.com/dgreif/ring/tree/master/homebridge#homebridge-configuration for more information.'); } }); this.homebridgeAccessories = {}; } configureAccessory(accessory) { this.log.info(`Configuring cached accessory ${accessory.UUID} ${accessory.displayName}`); this.log.debug('%j', accessory); this.homebridgeAccessories[accessory.UUID] = accessory; } connectToApi() { return __awaiter(this, void 0, void 0, function* () { const { api, config } = this, systemId = (0, config_1.getSystemId)(api), ringApi = new api_1.RingApi(Object.assign(Object.assign({ controlCenterDisplayName: 'homebridge-ring' }, config), { systemId })), locations = yield ringApi.getLocations(), cachedAccessoryIds = Object.keys(this.homebridgeAccessories), platformAccessories = [], activeAccessoryIds = []; this.log.info('Found the following locations:'); locations.forEach((location) => { this.log.info(` locationId: ${location.id} - ${location.name}`); }); yield Promise.all(locations.map((location) => __awaiter(this, void 0, void 0, function* () { var _a; const devices = yield location.getDevices(), cameras = location.cameras, chimes = location.chimes, allDevices = [...devices, ...cameras, ...chimes], securityPanel = devices.find((x) => x.deviceType === api_1.RingDeviceType.SecurityPanel), debugPrefix = debug ? 'TEST ' : '', hapDevices = allDevices.map((device) => { const isCamera = device instanceof api_1.RingCamera, cameraIdDifferentiator = isCamera ? 'camera' : '', // this forces bridged cameras from old version of the plugin to be seen as "stale" AccessoryClass = (device instanceof api_1.RingCamera ? camera_1.Camera : device instanceof api_1.RingChime ? chime_1.Chime : getAccessoryClass(device)); return { deviceType: device.deviceType, device: device, isCamera, id: device.id.toString() + cameraIdDifferentiator, name: device.name, AccessoryClass, }; }), hideDeviceIds = config.hideDeviceIds || [], onlyDeviceTypes = ((_a = config.onlyDeviceTypes) === null || _a === void 0 ? void 0 : _a.length) ? config.onlyDeviceTypes : undefined; if (config.showPanicButtons && securityPanel) { hapDevices.push({ deviceType: securityPanel.deviceType, device: securityPanel, isCamera: false, id: securityPanel.id.toString() + 'panic', name: 'Panic Buttons', AccessoryClass: panic_buttons_1.PanicButtons, }); } if (config.locationModePollingSeconds && (yield location.supportsLocationModeSwitching())) { hapDevices.push({ deviceType: 'location.mode', device: location, isCamera: false, id: location.id + 'mode', name: location.name + ' Mode', AccessoryClass: location_mode_switch_1.LocationModeSwitch, }); } this.log.info(`Configuring ${cameras.length} cameras and ${hapDevices.length} devices for location "${location.name}" - locationId: ${location.id}`); hapDevices.forEach(({ deviceType, device, isCamera, id, name, AccessoryClass }) => { const uuid = hap_1.hap.uuid.generate(debugPrefix + id), displayName = debugPrefix + name; if (!AccessoryClass || (config.hideLightGroups && deviceType === api_1.RingDeviceType.BeamsLightGroupSwitch) || (config.hideUnsupportedServices && unsupportedDeviceTypes.includes(deviceType)) || hideDeviceIds.includes(uuid) || (onlyDeviceTypes && !onlyDeviceTypes.includes(deviceType))) { if (!ignoreHiddenDeviceTypes.includes(deviceType)) { this.log.info(`Hidden accessory ${uuid} ${deviceType} ${displayName}`); } return; } const createHomebridgeAccessory = () => { const accessory = new api.platformAccessory(displayName, uuid, isCamera ? 17 /* CAMERA */ : 11 /* SECURITY_SYSTEM */); this.log.info(`Adding new accessory ${uuid} ${deviceType} ${displayName}`); platformAccessories.push(accessory); if (isCamera && typeof hap_1.hap.Accessory.cleanupAccessoryData === 'function') { // This is a one-time cleanup that will remove persist files for old external accessories from before camera bridging in version 8 hap_1.hap.Accessory.cleanupAccessoryData((0, util_2.generateMacAddress)(accessory.UUID)); } return accessory; }, homebridgeAccessory = this.homebridgeAccessories[uuid] || createHomebridgeAccessory(), accessory = new AccessoryClass(device, homebridgeAccessory, this.log, config); accessory.initBase(); this.homebridgeAccessories[uuid] = homebridgeAccessory; activeAccessoryIds.push(uuid); }); }))); if (platformAccessories.length) { api.registerPlatformAccessories(exports.pluginName, exports.platformName, platformAccessories); } const staleAccessories = cachedAccessoryIds .filter((cachedId) => !activeAccessoryIds.includes(cachedId)) .map((id) => this.homebridgeAccessories[id]); staleAccessories.forEach((staleAccessory) => { this.log.info(`Removing stale cached accessory ${staleAccessory.UUID} ${staleAccessory.displayName}`); }); if (staleAccessories.length) { this.api.unregisterPlatformAccessories(exports.pluginName, exports.platformName, staleAccessories); } ringApi.onRefreshTokenUpdated.subscribe(({ oldRefreshToken, newRefreshToken }) => { if (!oldRefreshToken) { return; } (0, config_1.updateHomebridgeConfig)(this.api, (configContents) => { return configContents.replace(oldRefreshToken, newRefreshToken); }); }); }); } } exports.RingPlatform = RingPlatform;