UNPKG

homebridge-nest-cam-ft

Version:

Nest cam plugin for homebridge: https://homebridge.io/

305 lines 13.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NestCam = void 0; const endpoints_1 = require("./endpoints"); const structure_1 = require("./structure"); const querystring_1 = __importDefault(require("querystring")); const events_1 = require("events"); class NestCam extends events_1.EventEmitter { constructor(config, info, log) { var _a, _b, _c, _d, _e; super(); this.zones = []; this.motionDetected = false; this.doorbellRang = false; this.importantOnly = true; this.alertTypes = ['Motion', 'Sound', 'Person', 'Package Delivered', 'Package Retrieved', 'Face', 'Zone']; this.alertCooldown = 180000; this.alertInterval = 10000; this.alertFailures = 0; this.alertsSend = true; this.lastCuepoint = ''; this.lastAlertTypes = []; this.log = log; this.config = config; this.info = info; this.lastUpdatedTime = new Date(); this.alertCooldown = (((_a = config.options) === null || _a === void 0 ? void 0 : _a.alertCooldownRate) || 180) * 1000; if (this.alertCooldown > 300000) { this.alertCooldown = 300000; } this.alertInterval = (((_b = config.options) === null || _b === void 0 ? void 0 : _b.alertCheckRate) || 10) * 1000; if (this.alertInterval > 60000) { this.alertInterval = 60000; } this.endpoints = new endpoints_1.NestEndpoints((_c = config.options) === null || _c === void 0 ? void 0 : _c.fieldTest); const alertTypes = (_d = config.options) === null || _d === void 0 ? void 0 : _d.alertTypes; if (typeof alertTypes !== 'undefined') { log === null || log === void 0 ? void 0 : log.debug(`Using alertTypes from config: ${alertTypes}`); this.alertTypes = alertTypes.slice(); } const importantOnly = (_e = config.options) === null || _e === void 0 ? void 0 : _e.importantOnly; if (typeof importantOnly !== 'undefined') { log === null || log === void 0 ? void 0 : log.debug(`Using importantOnly from config: ${importantOnly}`); this.importantOnly = importantOnly; } } async setBooleanProperty(key, value) { var _a; const query = querystring_1.default.stringify({ [key]: value, uuid: this.info.uuid, }); const response = await this.endpoints.sendRequest(this.config.access_token, this.endpoints.CAMERA_API_HOSTNAME, '/api/dropcams.set_properties', 'POST', 'json', 'application/x-www-form-urlencoded', true, query); try { if (response.status !== 0) { (_a = this.log) === null || _a === void 0 ? void 0 : _a.error(`Unable to set property '${key}' for ${this.info.name} to ${value}`); return false; } this.info.properties[key] = value; return true; } catch (error) { (0, endpoints_1.handleError)(this.log, error, `Error setting property for ${this.info.name}`); } return false; } async getAlertTypes() { var _a; const useZones = this.alertTypes.includes('Zone'); const index = this.alertTypes.indexOf('Zone'); if (index > -1) { this.alertTypes.splice(index, 1); } if (useZones) { const zones = await this.getZones(); zones.forEach((zone) => { var _a; (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(`Found zone ${zone.label} for ${this.info.name}`); this.alertTypes.push(`Zone - ${zone.label}`); }); } if (this.info.capabilities.includes('stranger_detection')) { (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(`${this.info.name} has stranger_detection`); const useFaces = this.alertTypes.includes('Face'); const index = this.alertTypes.indexOf('Face'); if (index > -1) { this.alertTypes.splice(index, 1); } if (useFaces) { const structureId = this.info.nest_structure_id.replace('structure.', ''); const structure = new structure_1.NestStructure(this.info, this.config, this.log); const faces = await structure.getFaces(); if (faces) { faces.forEach((face) => { var _a; if (face.name) { (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(`Found face ${face.name} for ${structureId}`); this.alertTypes.push(`Face - ${face.name}`); } this.alertTypes.push('Face - Unknown'); }); } } return this.alertTypes; } else { const remove = ['Package Delivered', 'Package Retrieved', 'Face']; return this.alertTypes.filter((x) => !remove.includes(x)); } } startAlertChecks() { if (!this.alertTimeout) { const self = this; this.alertTimeout = global.setInterval(async () => { await self.checkAlerts(); }, this.alertInterval); } } stopAlertChecks() { if (this.alertTimeout) { clearInterval(this.alertTimeout); this.alertTimeout = undefined; this.emit("motion-detected", false, this.alertTypes); } } async checkAlerts() { var _a; if (!this.alertsSend) { return; } if (!this.info.properties['streaming.enabled']) { this.emit("motion-detected", false, this.alertTypes); return; } (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(`Checking for alerts on ${this.info.name}`); try { const currDate = new Date(); currDate.setMinutes(currDate.getMinutes() - 1); const epoch = Math.round(currDate.getTime() / 1000); const query = querystring_1.default.stringify({ start_time: epoch, }); const response = await this.endpoints.sendRequest(this.config.access_token, `https://${this.info.nexus_api_nest_domain_host}`, `/cuepoint/${this.info.uuid}/2?${query}`, 'GET'); this.alertFailures = 0; if (response.length > 0) { response.forEach((trigger) => { var _a; const { id, face_name, types, zone_ids, is_important } = trigger; this.lastCuepoint = id; if (face_name) { (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(`Found face for ${face_name || 'Unknown'} in event`); types === null || types === void 0 ? void 0 : types.push(`Face - ${face_name || 'Unknown'}`); if (!(types === null || types === void 0 ? void 0 : types.includes('person'))) { types === null || types === void 0 ? void 0 : types.push('person'); } } if (zone_ids.length > 0) { zone_ids.forEach((zone_id) => { var _a; const zone = this.zones.find((x) => x.id === zone_id); if (zone) { (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(`Found zone for ${zone.label} in event`); types.push(`Zone - ${zone.label}`); } }); } let important = true; if (this.importantOnly) { important = is_important; } if (important && types.includes('doorbell') && !this.doorbellRang) { this.triggerDoorbell(); } if (important && !this.motionDetected) { if (types && types.length > 0) { const lastSubset = this.lastAlertTypes.filter((x) => !types.includes(x)); this.emit("motion-detected", false, lastSubset); const currSubset = types.filter((x) => !this.lastAlertTypes.includes(x)); this.triggerMotion(currSubset); this.lastAlertTypes = types; } } }); setTimeout(() => { this.lastCuepoint = ''; }, 5000); } else { this.emit("motion-detected", false, this.alertTypes); this.lastAlertTypes = []; } } catch (error) { (0, endpoints_1.handleError)(this.log, error, 'Error checking alerts'); if (this.alertFailures < 10) { this.alertFailures++; } this.alertsSend = false; setTimeout(() => { this.alertsSend = true; }, this.alertInterval * Math.pow(this.alertFailures, 2)); } } async getZones() { try { const response = await this.endpoints.sendRequest(this.config.access_token, `https://${this.info.nexus_api_nest_domain_host}`, `/cuepoint_category/${this.info.uuid}`, 'GET'); const validZones = []; response.forEach((zone) => { if (zone.label && !zone.hidden && zone.type === 'region') { validZones.push(zone); } }); this.zones = validZones; return validZones; } catch (error) { (0, endpoints_1.handleError)(this.log, error, `Error getting zones for ${this.info.name} camera`); } return []; } async getSnapshot(height) { const query = querystring_1.default.stringify({ uuid: this.info.uuid, }); if (this.lastCuepoint) { return await this.getEventSnapshot(height); } return await this.endpoints.sendRequest(this.config.access_token, `https://${this.info.nexus_api_nest_domain_host}`, `/get_image?${query}`, 'GET', 'arraybuffer'); } async getEventSnapshot(height) { const query = querystring_1.default.stringify({ uuid: this.info.uuid, cuepoint_id: this.lastCuepoint, num_frames: 1, height: height, format: 'sprite', }); this.lastCuepoint = ''; return await this.endpoints.sendRequest(this.config.access_token, `https://${this.info.nexus_api_nest_domain_host}`, `/get_event_clip?${query}`, 'GET', 'arraybuffer'); } async updateData(info) { if (!info) { const checkTime = new Date(this.lastUpdatedTime); checkTime.setSeconds(checkTime.getSeconds() + 1); if (new Date().getTime() < checkTime.getTime()) { return this.info; } const query = querystring_1.default.stringify({ uuid: this.info.uuid, }); try { const response = await this.endpoints.sendRequest(this.config.access_token, this.endpoints.CAMERA_API_HOSTNAME, `/api/cameras.get_with_properties?${query}`, 'GET'); info = response.items[0]; } catch (error) { (0, endpoints_1.handleError)(this.log, error, `Error updating ${this.info.name} camera`); } } if (info) { const curr_streaming = this.info.properties['streaming.enabled']; const curr_chime = this.info.properties['doorbell.indoor_chime.enabled']; const curr_assist = this.info.properties['doorbell.chime_assist.enabled']; const curr_audio = this.info.properties['audio.enabled']; this.info = info; this.lastUpdatedTime = new Date(); const newProps = info.properties; if (curr_streaming !== newProps['streaming.enabled']) { this.emit("camera-change", newProps['streaming.enabled']); } if (curr_chime !== newProps['doorbell.indoor_chime.enabled']) { this.emit("chime-change", newProps['doorbell.indoor_chime.enabled']); } if (curr_assist !== newProps['doorbell.chime_assist.enabled']) { this.emit("chime-assist-change", newProps['doorbell.chime_assist.enabled']); } if (curr_audio !== newProps['audio.enabled']) { this.emit("audio-change", newProps['audio.enabled']); } } return this.info; } triggerMotion(types) { const self = this; this.emit("motion-detected", true, types); this.motionDetected = true; setTimeout(async () => { var _a; self.motionDetected = false; (_a = self.log) === null || _a === void 0 ? void 0 : _a.debug('Cooldown has ended'); }, this.alertCooldown); } triggerDoorbell() { const self = this; this.emit("doorbell-rang"); this.doorbellRang = true; setTimeout(() => { self.doorbellRang = false; }, this.alertCooldown); } } exports.NestCam = NestCam; //# sourceMappingURL=cam.js.map