UNPKG

homebridge-loxone-proxy

Version:

Homebridge Dynamic Platform Plugin which exposes a Loxone System to Homekit.

179 lines 6.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CameraMotionSensor = void 0; const BaseService_1 = require("./BaseService"); class CameraMotionSensor extends BaseService_1.BaseService { constructor(platform, accessory, camera, doorbellService) { var _a, _b, _c; super(platform, accessory); this.camera = camera; this.doorbellService = doorbellService; this.intervalMs = 1000; this.minThreshold = 0.04; this.maxThreshold = 0.30; this.minDeltaBytes = 1500; this.cooldown = 8000; this.resetTimeout = 15000; this.historyLimit = 20; this.minimumHistory = 5; this.snapshotHistory = []; this.snapshotFailures = 0; this.lastTrigger = 0; this.polling = false; this.shuttingDown = false; this.state = { MotionDetected: false }; this.jpegHeaderSize = (_c = (_b = (_a = platform.config) === null || _a === void 0 ? void 0 : _a.Advanced) === null || _b === void 0 ? void 0 : _b.JpegHeaderSize) !== null && _c !== void 0 ? _c : 623; this.setupService(); this.startPolling(); platform.api.on("shutdown", () => { this.shuttingDown = true; if (this.loopTimer) { clearTimeout(this.loopTimer); this.loopTimer = undefined; } if (this.resetTimer) { clearTimeout(this.resetTimer); this.resetTimer = undefined; } this.platform.log.debug(`[${this.accessory.displayName}] Motion detection stopped (shutdown)`); }); } setupService() { this.service = this.accessory.getService(this.platform.Service.MotionSensor) || this.accessory.addService(this.platform.Service.MotionSensor); this.service .getCharacteristic(this.platform.Characteristic.MotionDetected) .onGet(() => this.state.MotionDetected); } startPolling() { this.scheduleNextPoll(0); } scheduleNextPoll(delayMs) { if (this.shuttingDown) { return; } if (this.loopTimer) { clearTimeout(this.loopTimer); } this.loopTimer = setTimeout(() => { void this.pollOnce(); }, delayMs); } async pollOnce() { if (this.shuttingDown || this.polling) { return; } this.polling = true; let nextDelay = this.intervalMs; try { const size = await this.readSnapshotSize(); if (size !== null) { this.snapshotFailures = 0; const now = Date.now(); if (this.evaluateMotion(size, now)) { this.triggerMotion(now); } } else { nextDelay = this.handleSnapshotFailure(); } } catch (error) { const message = error instanceof Error ? error.message : String(error); this.platform.log.debug(`[${this.accessory.displayName}] Motion polling error: ${message}`); nextDelay = this.handleSnapshotFailure(); } finally { this.polling = false; this.scheduleNextPoll(nextDelay); } } async readSnapshotSize() { const headerSize = this.jpegHeaderSize; const headerSizeResult = await this.camera.getSnapshotSize(); if (headerSizeResult !== null) { return Math.max(0, headerSizeResult - headerSize); } const snapshot = await this.camera.getSnapshot(false); if (!snapshot) { return null; } return Math.max(0, snapshot.length - headerSize); } handleSnapshotFailure() { if (this.snapshotFailures < 3) { this.platform.log.warn(`[${this.accessory.displayName}] Snapshot unavailable`); } this.snapshotFailures++; return Math.min(this.intervalMs * 2 ** this.snapshotFailures, 60000); } evaluateMotion(current, now) { this.snapshotHistory.push(current); if (this.snapshotHistory.length > this.historyLimit) { this.snapshotHistory.shift(); } if (this.snapshotHistory.length < this.minimumHistory) { return false; } const baseline = this.median(this.snapshotHistory.slice(0, -1)); if (baseline <= 0) { return false; } const deltaAbs = Math.abs(current - baseline); const deltaRel = deltaAbs / baseline; return (now - this.lastTrigger > this.cooldown && deltaRel > this.minThreshold && deltaRel < this.maxThreshold && deltaAbs > this.minDeltaBytes); } triggerMotion(now) { var _a; if (!this.state.MotionDetected) { this.platform.log.info(`[${this.accessory.displayName}] Motion detected`); this.state.MotionDetected = true; (_a = this.service) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.platform.Characteristic.MotionDetected, true); this.triggerDoorbellFromMotion(); } if (this.resetTimer) { clearTimeout(this.resetTimer); } this.resetTimer = setTimeout(() => this.resetMotion(), this.resetTimeout); this.lastTrigger = now; } triggerDoorbellFromMotion() { var _a, _b, _c; if (!((_b = (_a = this.platform.config) === null || _a === void 0 ? void 0 : _a.Advanced) === null || _b === void 0 ? void 0 : _b.MotionTriggersDoorbell)) { return; } try { (_c = this.doorbellService) === null || _c === void 0 ? void 0 : _c.triggerDoorbell(); } catch (error) { const message = error instanceof Error ? error.message : String(error); this.platform.log.warn(`[${this.accessory.displayName}] Failed to trigger doorbell from motion: ${message}`); } } resetMotion() { var _a; if (!this.state.MotionDetected) { return; } this.platform.log.info(`[${this.accessory.displayName}] Motion ended`); this.state.MotionDetected = false; (_a = this.service) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.platform.Characteristic.MotionDetected, false); this.resetTimer = undefined; } median(values) { if (!values.length) { return 0; } const sorted = values.slice().sort((a, b) => a - b); const mid = sorted.length >> 1; return sorted.length & 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; } } exports.CameraMotionSensor = CameraMotionSensor; //# sourceMappingURL=CameraMotionSensor.js.map