homebridge-nest-cam-ft
Version:
Nest cam plugin for homebridge: https://homebridge.io/
305 lines • 13.8 kB
JavaScript
"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