homebridge-nest-cam-ft
Version:
Nest cam plugin for homebridge: https://homebridge.io/
299 lines • 13.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const cam_1 = require("./nest/cam");
const connection_1 = require("./nest/connection");
const session_1 = require("./nest/session");
const accessory_1 = require("./accessory");
class Options {
constructor() {
this.motionDetection = true;
this.doorbellAlerts = true;
this.doorbellSwitch = true;
this.streamingSwitch = true;
this.chimeSwitch = true;
this.announcementsSwitch = true;
this.audioSwitch = true;
}
}
const ModelTypes = [
'Nest Camera',
'Nest Camera',
'Nest Camera',
'Nest Camera',
'Nest Camera',
'Nest Camera',
'Nest Camera',
'Nest Camera',
'Nest Cam Indoor',
'Nest Cam Outdoor',
'Nest Cam IQ Indoor',
'Nest Cam IQ Outdoor',
'Nest Hello',
];
let hap;
let Accessory;
const PLUGIN_NAME = 'homebridge-nest-cam';
const PLATFORM_NAME = 'Nest-cam';
class NestCamPlatform {
constructor(log, config, api) {
this.nestObjects = [];
this.structures = [];
this.cameras = [];
this.log = log;
this.api = api;
this.config = config;
this.options = new Options();
if (!config) {
return;
}
this.initDefaultOptions();
api.on("didFinishLaunching", this.didFinishLaunching.bind(this));
api.on("shutdown", this.isShuttingDown.bind(this));
}
initDefaultOptions() {
var _a, _b;
Object.keys(this.options).forEach((opt) => {
const key = opt;
if (this.config.options) {
const configVal = this.config.options[key];
if (typeof configVal === 'undefined') {
this.options[key] = true;
this.log.debug(`Defaulting ${key} to true`);
}
else {
this.options[key] = configVal;
this.log.debug(`Using ${key} from config: ${configVal}`);
}
}
});
const structures = (_a = this.config.options) === null || _a === void 0 ? void 0 : _a.structures;
if (typeof structures !== 'undefined') {
this.log.debug(`Using structures from config: ${structures}`);
this.structures = structures;
}
else {
this.log.debug('Defaulting structures to []');
}
const cameras = (_b = this.config.options) === null || _b === void 0 ? void 0 : _b.cameras;
if (typeof cameras !== 'undefined') {
this.log.debug(`Using cameras from config: ${cameras}`);
this.cameras = cameras;
}
else {
this.log.debug('Defaulting cameras to []');
}
}
configureAccessory(accessory) {
this.log.info(`Configuring accessory ${accessory.displayName}`);
accessory.on("identify", () => {
this.log.info(`${accessory.displayName} identified!`);
});
const cameraInfo = accessory.context.cameraInfo;
const camera = new cam_1.NestCam(this.config, cameraInfo, this.log);
const nestAccessory = new accessory_1.NestAccessory(accessory, camera, this.config, this.log, hap);
nestAccessory.configureController();
if (camera.info.capabilities.includes('audio.microphone')) {
nestAccessory.createService(hap.Service.Microphone);
nestAccessory.createService(hap.Service.Speaker);
this.log.debug(`Creating microphone for ${accessory.displayName}.`);
}
if (camera.info.capabilities.includes('indoor_chime') && this.options.doorbellAlerts) {
nestAccessory.createService(hap.Service.Doorbell, 'Doorbell');
this.log.debug(`Creating doorbell sensor for ${accessory.displayName}.`);
camera.startAlertChecks();
}
else {
nestAccessory.removeService(hap.Service.Doorbell, 'Doorbell');
}
if (camera.info.capabilities.includes('indoor_chime') &&
this.options.doorbellAlerts &&
this.options.doorbellSwitch) {
const service = nestAccessory.createService(hap.Service.StatelessProgrammableSwitch, 'DoorbellSwitch');
this.log.debug(`Creating doorbell switch for ${accessory.displayName}.`);
service.getCharacteristic(hap.Characteristic.ProgrammableSwitchEvent).setProps({
maxValue: hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
});
}
else {
nestAccessory.removeService(hap.Service.StatelessProgrammableSwitch, 'DoorbellSwitch');
}
if (camera.info.capabilities.includes('streaming.start-stop') && this.options.streamingSwitch) {
nestAccessory.createSwitchService('Streaming', hap.Service.Switch, 'streaming.enabled', async (value) => {
await nestAccessory.toggleActive(value);
});
}
else {
nestAccessory.removeService(hap.Service.Switch, 'Streaming');
}
if (camera.info.capabilities.includes('indoor_chime') && this.options.chimeSwitch) {
nestAccessory.createSwitchService('Chime', hap.Service.Switch, 'doorbell.indoor_chime.enabled', async (value) => {
await nestAccessory.toggleChime(value);
});
}
else {
nestAccessory.removeService(hap.Service.Switch, 'Chime');
}
if (camera.info.capabilities.includes('indoor_chime') && this.options.announcementsSwitch) {
nestAccessory.createSwitchService('Announcements', hap.Service.Switch, 'doorbell.chime_assist.enabled', async (value) => {
await nestAccessory.toggleAnnouncements(value);
});
}
else {
nestAccessory.removeService(hap.Service.Switch, 'Announcements');
}
if (camera.info.capabilities.includes('audio.microphone') && this.options.audioSwitch) {
nestAccessory.createSwitchService('Audio', hap.Service.Switch, 'audio.enabled', async (value) => {
await nestAccessory.toggleAudio(value);
});
}
else {
nestAccessory.removeService(hap.Service.Switch, 'Audio');
}
const accessoryInformation = accessory.getService(hap.Service.AccessoryInformation);
if (accessoryInformation) {
accessoryInformation.setCharacteristic(hap.Characteristic.FirmwareRevision, cameraInfo.combined_software_version);
}
this.nestObjects.push({ accessory: accessory, camera: camera });
}
async setupMotionServices() {
this.nestObjects.forEach(async (obj) => {
const camera = obj.camera;
const accessory = obj.accessory;
if (accessory) {
const nestAccessory = new accessory_1.NestAccessory(accessory, camera, this.config, this.log, hap);
if (this.options.motionDetection) {
const services = nestAccessory.getServicesByType(hap.Service.MotionSensor);
const alertTypes = await camera.getAlertTypes();
const invalidServices = services.filter((x) => !alertTypes.includes(x.displayName));
for (const service of invalidServices) {
accessory.removeService(service);
}
alertTypes.forEach((type) => {
if (camera.info.capabilities.includes('detectors.on_camera')) {
nestAccessory.createService(hap.Service.MotionSensor, type);
this.log.debug(`Creating motion sensor for ${accessory.displayName} ${type}.`);
camera.startAlertChecks();
}
});
}
else {
nestAccessory.removeAllServicesByType(hap.Service.MotionSensor);
}
}
});
}
cleanupAccessories() {
if (this.structures.length > 0) {
const oldObjects = this.nestObjects.filter((obj) => !this.structures.includes(obj.camera.info.nest_structure_id.replace('structure.', '')));
oldObjects.forEach((obj) => {
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [obj.accessory]);
const index = this.nestObjects.indexOf(obj);
if (index > -1) {
obj.camera.stopAlertChecks();
this.nestObjects.splice(index, 1);
}
});
}
if (this.cameras.length > 0) {
const oldObjects = this.nestObjects.filter((obj) => !this.cameras.includes(obj.camera.info.uuid));
oldObjects.forEach((obj) => {
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [obj.accessory]);
const index = this.nestObjects.indexOf(obj);
if (index > -1) {
obj.camera.stopAlertChecks();
this.nestObjects.splice(index, 1);
}
});
}
}
filterCameras(cameras) {
if (this.structures.length > 0) {
this.log.debug('Filtering cameras by structures config');
cameras = cameras.filter((info) => this.structures.includes(info.nest_structure_id.replace('structure.', '')));
}
if (this.cameras.length > 0) {
this.log.debug('Filtering cameras by cameras config');
cameras = cameras.filter((info) => this.cameras.includes(info.uuid));
}
return cameras;
}
async addCameras(cameras) {
const filteredCameras = await this.filterCameras(cameras);
filteredCameras.forEach(async (cameraInfo) => {
const uuid = hap.uuid.generate(cameraInfo.uuid);
const displayName = cameraInfo.name.replace('(', '').replace(')', '');
const accessory = new Accessory(displayName, uuid);
accessory.context.cameraInfo = cameraInfo;
const model = cameraInfo.type < ModelTypes.length ? ModelTypes[cameraInfo.type] : 'Nest Camera';
const accessoryInformation = accessory.getService(hap.Service.AccessoryInformation);
if (accessoryInformation) {
accessoryInformation.setCharacteristic(hap.Characteristic.Manufacturer, 'Nest');
accessoryInformation.setCharacteristic(hap.Characteristic.Model, model);
accessoryInformation.setCharacteristic(hap.Characteristic.SerialNumber, cameraInfo.serial_number);
accessoryInformation.setCharacteristic(hap.Characteristic.FirmwareRevision, cameraInfo.combined_software_version);
}
const obj = this.nestObjects.find((x) => x.accessory.UUID === uuid);
if (obj) {
await obj.camera.updateData(cameraInfo);
}
else {
this.log.debug(`New camera found: ${cameraInfo.name}`);
this.configureAccessory(accessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
}
});
}
async getAccessToken() {
var _a;
const { refreshToken, googleAuth, nest_token } = this.config;
if (refreshToken) {
return await (0, connection_1.auth)(refreshToken, (_a = this.config.options) === null || _a === void 0 ? void 0 : _a.fieldTest, this.log);
}
else if (googleAuth) {
const { apiKey, issueToken, cookies } = googleAuth;
if (!issueToken || !cookies) {
this.log.error('You must provide issueToken and cookies in config.json. Please see README.md for instructions');
return '';
}
return await (0, connection_1.old_auth)(issueToken, cookies, apiKey, this.log);
}
else if (nest_token) {
return await (0, connection_1.nest_auth)(nest_token, this.log);
}
else {
this.log.error('You must provide a refreshToken or googleAuth object in config.json. Please see README.md for instructions');
return '';
}
}
async didFinishLaunching() {
const self = this;
const accessToken = await this.getAccessToken();
if (accessToken) {
this.config.access_token = accessToken;
setInterval(async () => {
self.log.debug('Reauthenticating with config credentials');
this.config.access_token = await this.getAccessToken();
}, 3480000);
const cameras = await (0, connection_1.getCameras)(this.config, this.log);
await this.addCameras(cameras);
await this.setupMotionServices();
this.cleanupAccessories();
const session = new session_1.NestSession(this.config, this.log);
const cameraObjects = this.nestObjects.map((x) => x.camera);
await session.subscribe(cameraObjects);
}
else {
this.log.error('Unable to retrieve access token.');
}
}
isShuttingDown() {
const accessoryObjects = this.nestObjects.map((x) => x.accessory);
this.api.updatePlatformAccessories(accessoryObjects);
}
}
exports.default = (api) => {
hap = api.hap;
Accessory = api.platformAccessory;
api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, NestCamPlatform);
};
//# sourceMappingURL=index.js.map