@koush/ring-client-api
Version:
Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting
269 lines (268 loc) • 14.3 kB
JavaScript
;
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;