Skip to content

Commit

Permalink
unifi-protect: debounce motion sensors, attempt to stabilize nativeids
Browse files Browse the repository at this point in the history
  • Loading branch information
koush committed Mar 6, 2024
1 parent b4293e3 commit 2da94cd
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 77 deletions.
2 changes: 1 addition & 1 deletion plugins/unifi-protect/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"sourceMaps": true,
"localRoot": "${workspaceFolder}/out",
"remoteRoot": "/plugin/",
"type": "pwa-node"
"type": "node"
}
]
}
42 changes: 16 additions & 26 deletions plugins/unifi-protect/src/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { once } from "events";
import { Readable } from "stream";
import WS from 'ws';
import { UnifiProtect } from "./main";
import { MOTION_SENSOR_TIMEOUT, UnifiMotionDevice, debounceMotionDetected } from './motion';
import { FeatureFlagsShim } from "./shim";
import { ProtectCameraChannelConfig, ProtectCameraConfigInterface, ProtectCameraLcdMessagePayload } from "./unifi-protect";

Expand Down Expand Up @@ -39,19 +40,17 @@ export class UnifiPackageCamera extends ScryptedDeviceBase implements Camera, Vi
}
}

export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Intercom, Camera, VideoCamera, VideoCameraConfiguration, MotionSensor, Settings, ObjectDetector, DeviceProvider, OnOff, PanTiltZoom, Online {
export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Intercom, Camera, VideoCamera, VideoCameraConfiguration, MotionSensor, Settings, ObjectDetector, DeviceProvider, OnOff, PanTiltZoom, Online, UnifiMotionDevice {
motionTimeout: NodeJS.Timeout;
detectionTimeout: NodeJS.Timeout;
ringTimeout: NodeJS.Timeout;
lastMotion: number;
lastRing: number;
lastSeen: number;
intercomProcess?: ChildProcess;
packageCamera?: UnifiPackageCamera;

constructor(public protect: UnifiProtect, nativeId: string, protectCamera: Readonly<ProtectCameraConfigInterface>) {
super(nativeId);
this.lastMotion = protectCamera?.lastMotion;
this.lastRing = protectCamera?.lastRing;
this.lastSeen = protectCamera?.lastSeen;

Expand Down Expand Up @@ -226,14 +225,14 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
}

async getSettings(): Promise<Setting[]> {
const vsos = await this.getVideoStreamOptions();
// const vsos = await this.getVideoStreamOptions();
return [
{
title: 'Sensor Timeout',
key: 'sensorTimeout',
value: this.storage.getItem('sensorTimeout') || defaultSensorTimeout,
description: 'Time to wait in seconds before clearing the motion, doorbell button, or object detection state.',
}
// {
// title: 'Sensor Timeout',
// key: 'sensorTimeout',
// value: this.storage.getItem('sensorTimeout') || defaultSensorTimeout,
// description: 'Time to wait in seconds before clearing the motion, doorbell button, or object detection state.',
// }
];
}

Expand All @@ -242,17 +241,6 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
}

getSensorTimeout() {
return (parseInt(this.storage.getItem('sensorTimeout')) || 10) * 1000;
}

resetMotionTimeout() {
clearTimeout(this.motionTimeout);
this.motionTimeout = setTimeout(() => {
this.setMotionDetected(false);
}, this.getSensorTimeout());
}

resetDetectionTimeout() {
clearTimeout(this.detectionTimeout);
this.detectionTimeout = setTimeout(() => {
Expand All @@ -261,14 +249,14 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
detections: []
}
this.onDeviceEvent(ScryptedInterface.ObjectDetector, detect);
}, this.getSensorTimeout());
}, MOTION_SENSOR_TIMEOUT);
}

resetRingTimeout() {
clearTimeout(this.ringTimeout);
this.ringTimeout = setTimeout(() => {
this.binaryState = false;
}, this.getSensorTimeout());
}, MOTION_SENSOR_TIMEOUT);
}

async getSnapshot(options?: PictureOptions, suffix?: string): Promise<Buffer> {
Expand All @@ -287,7 +275,7 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
catch (e) {

}
const url = `https://${this.protect.getSetting('ip')}/proxy/protect/api/cameras/${this.nativeId}/${suffix}?ts=${Date.now()}${size}`
const url = `https://${this.protect.getSetting('ip')}/proxy/protect/api/cameras/${this.findCamera().id}/${suffix}?ts=${Date.now()}${size}`

const abort = new AbortController();
const timeout = setTimeout(() => abort.abort('Unifi Protect Snapshot timed out after 10 seconds. Aborted.'), 10000);
Expand All @@ -307,7 +295,8 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
return this.createMediaObject(buffer, 'image/jpeg');
}
findCamera() {
return this.protect.api.cameras.find(camera => camera.id === this.nativeId);
const id = this.protect.findId(this.nativeId);
return this.protect.api.cameras.find(camera => camera.id === id);
}
async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
const camera = this.findCamera();
Expand Down Expand Up @@ -424,7 +413,8 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
return;
this.on = !!camera.ledSettings?.isEnabled;
this.online = !!camera.isConnected;
this.setMotionDetected(!!camera.isMotionDetected);
if (!!camera.isMotionDetected)
debounceMotionDetected(this);

if (!!camera.featureFlags.canOpticalZoom) {
this.ptzCapabilities = { pan: false, tilt: false, zoom: true };
Expand Down
13 changes: 9 additions & 4 deletions plugins/unifi-protect/src/light.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ScryptedDeviceBase, MotionSensor, TemperatureUnit, OnOff, Brightness } from "@scrypted/sdk";
import { Brightness, MotionSensor, OnOff, ScryptedDeviceBase, TemperatureUnit } from "@scrypted/sdk";
import { UnifiProtect } from "./main";
import { UnifiMotionDevice, debounceMotionDetected } from "./motion";
import { ProtectLightConfig } from "./unifi-protect";

export class UnifiLight extends ScryptedDeviceBase implements OnOff, Brightness, MotionSensor {
export class UnifiLight extends ScryptedDeviceBase implements OnOff, Brightness, MotionSensor, UnifiMotionDevice {
motionTimeout: NodeJS.Timeout;

constructor(public protect: UnifiProtect, nativeId: string, protectLight: Readonly<ProtectLightConfig>) {
super(nativeId);
this.temperatureUnit = TemperatureUnit.C;
Expand All @@ -26,7 +29,8 @@ export class UnifiLight extends ScryptedDeviceBase implements OnOff, Brightness,
}

findLight() {
return this.protect.api.lights.find(light => light.id === this.nativeId);
const id = this.protect.findId(this.nativeId);
return this.protect.api.lights.find(light => light.id === id);
}

updateState(light?: Readonly<ProtectLightConfig>) {
Expand All @@ -36,7 +40,8 @@ export class UnifiLight extends ScryptedDeviceBase implements OnOff, Brightness,
this.on = !!light.isLightOn;
// The Protect ledLevel settings goes from 1 - 6. HomeKit expects percentages, so we convert it like so.
this.brightness = (light.lightDeviceSettings.ledLevel - 1) * 20;
this.setMotionDetected(!!light.isPirMotionDetected);
if (!!light.isPirMotionDetected)
debounceMotionDetected(this);
}

setMotionDetected(motionDetected: boolean) {
Expand Down
9 changes: 5 additions & 4 deletions plugins/unifi-protect/src/lock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ScryptedDeviceBase, Lock, LockState } from "@scrypted/sdk";
import { Lock, LockState, ScryptedDeviceBase } from "@scrypted/sdk";
import { UnifiProtect } from "./main";
import { ProtectDoorLockConfig } from "./unifi-protect";

Expand All @@ -11,19 +11,20 @@ export class UnifiLock extends ScryptedDeviceBase implements Lock {
}

async lock(): Promise<void> {
await this.protect.loginFetch(this.protect.api.doorlocksUrl() + `/${this.nativeId}/close`, {
await this.protect.loginFetch(this.protect.api.doorlocksUrl() + `/${this.findLock().id}/close`, {
method: 'POST',
});
}

async unlock(): Promise<void> {
await this.protect.loginFetch(this.protect.api.doorlocksUrl() + `/${this.nativeId}/open`, {
await this.protect.loginFetch(this.protect.api.doorlocksUrl() + `/${this.findLock().id}/open`, {
method: 'POST',
});
}

findLock() {
return this.protect.api.doorlocks.find(doorlock => doorlock.id === this.nativeId);
const id = this.protect.findId(this.nativeId);
return this.protect.api.doorlocks.find(doorlock => doorlock.id === id);
}

updateState(lock?: Readonly<ProtectDoorLockConfig>) {
Expand Down
Loading

0 comments on commit 2da94cd

Please sign in to comment.