From cb45a00c25ee84169aa506a8a1803488815748db Mon Sep 17 00:00:00 2001 From: apocaliss92 Date: Mon, 3 Feb 2025 17:51:45 +0100 Subject: [PATCH 1/2] reolink: Battery cams api fixes (#1719) * Battery cams api fixes * Update with new Sleep class --------- Co-authored-by: Gianluca Ruocco --- plugins/reolink/src/main.ts | 35 +++++++++++++++++++++--------- plugins/reolink/src/reolink-api.ts | 30 ++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/plugins/reolink/src/main.ts b/plugins/reolink/src/main.ts index 858f7f218..c4d9e4073 100644 --- a/plugins/reolink/src/main.ts +++ b/plugins/reolink/src/main.ts @@ -1,5 +1,5 @@ import { sleep } from '@scrypted/common/src/sleep'; -import sdk, { Brightness, Camera, Device, DeviceCreatorSettings, DeviceInformation, DeviceProvider, Intercom, MediaObject, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, OnOff, PanTiltZoom, PanTiltZoomCommand, Reboot, RequestPictureOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk"; +import sdk, { Sleep, Brightness, Camera, Device, DeviceCreatorSettings, DeviceInformation, DeviceProvider, Intercom, MediaObject, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, OnOff, PanTiltZoom, PanTiltZoomCommand, Reboot, RequestPictureOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk"; import { StorageSettings } from '@scrypted/sdk/storage-settings'; import { EventEmitter } from "stream"; import { createRtspMediaStreamOptions, Destroyable, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp"; @@ -78,7 +78,7 @@ class ReolinkCameraFloodlight extends ScryptedDeviceBase implements OnOff, Brigh } } -class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, Reboot, Intercom, ObjectDetector, PanTiltZoom { +class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, Reboot, Intercom, ObjectDetector, PanTiltZoom, Sleep { client: ReolinkCameraClient; clientWithToken: ReolinkCameraClient; onvifClient: OnvifCameraAPI; @@ -362,7 +362,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R if (this.hasSiren() || this.hasFloodlight()) interfaces.push(ScryptedInterface.DeviceProvider); if (this.hasBattery()) { - interfaces.push(ScryptedInterface.Battery, ScryptedInterface.Online); + interfaces.push(ScryptedInterface.Battery, ScryptedInterface.Sleep); this.startBatteryCheckInterval(); } @@ -378,14 +378,20 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R const api = this.getClientWithToken(); try { - const { batteryPercent, sleep } = await api.getBatteryInfo(); + const { batteryPercent, sleeping } = await api.getBatteryInfo(); this.batteryLevel = batteryPercent; - this.online = !sleep; + + if (sleeping !== this.sleeping) { + this.sleeping = sleeping; + } + if (batteryPercent !== this.batteryLevel) { + this.batteryLevel = batteryPercent; + } } catch (e) { this.console.log('Error in getting battery info', e); } - }, 1000 * 60 * 30); + }, 1000 * 10); } async reboot() { @@ -557,10 +563,19 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R (async () => { while (!killed) { try { - const { value, data } = await client.getMotionState(); - if (value) - triggerMotion(); - ret.emit('data', JSON.stringify(data)); + // Battey cameras do not have AI state, they just send events in case of PIR sensor triggered + // which equals a motion detected + if (this.hasBattery()) { + const { value, data } = await client.getPidActive(); + if (value) + triggerMotion(); + ret.emit('data', JSON.stringify(data)); + } else { + const { value, data } = await client.getMotionState(); + if (value) + triggerMotion(); + ret.emit('data', JSON.stringify(data)); + } } catch (e) { ret.emit('error', e); diff --git a/plugins/reolink/src/reolink-api.ts b/plugins/reolink/src/reolink-api.ts index 7b614c319..2bdf7c805 100644 --- a/plugins/reolink/src/reolink-api.ts +++ b/plugins/reolink/src/reolink-api.ts @@ -492,7 +492,35 @@ export class ReolinkCameraClient { return { batteryPercent: batteryInfoEntry?.batteryPercent, - sleep: channelStatusEntry?.sleep === 1, + sleeping: channelStatusEntry?.sleep === 1, } } + + async getPidActive() { + const url = new URL(`http://${this.host}/api.cgi`); + + const body = [ + { + cmd: "GetEvents", + action: 0, + param: { channel: this.channelId } + }, + ]; + + const response = await this.requestWithLogin({ + url, + responseType: 'json', + method: 'POST', + }, this.createReadable(body)); + + const error = response.body?.find(elem => elem.error)?.error; + if (error) { + this.console.error('error during call to getEvents', error); + } + + return { + value: !!response.body?.[0]?.value?.ai?.other?.alarm_state, + data: response.body, + }; + } } From 1fb1334a00e9ea710f2c6f161442949ee4f74464 Mon Sep 17 00:00:00 2001 From: apocaliss92 Date: Mon, 3 Feb 2025 19:55:58 +0100 Subject: [PATCH 2/2] snapshot: Sleeping cameras should not wake for periodic snapshots (#1718) * Preserve battery on snapshots * Don't force snapshot below 1 min * Online interface changes * Pr comments fix * Interval removed * Debounce restored * Branching fixes * Fix isBattery leftover * Remove prebuffer check * Remove comment * Remove unused import * Use Sleep interface * Disable default prebuffer for Sleep devices * Rollback default changes * Unused import removed --------- Co-authored-by: Gianluca Ruocco --- plugins/snapshot/src/main.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/snapshot/src/main.ts b/plugins/snapshot/src/main.ts index c5f59efa4..34042d437 100644 --- a/plugins/snapshot/src/main.ts +++ b/plugins/snapshot/src/main.ts @@ -2,7 +2,7 @@ import { AutoenableMixinProvider } from "@scrypted/common/src/autoenable-mixin-p import { AuthFetchCredentialState, authHttpFetch } from '@scrypted/common/src/http-auth-fetch'; import { RefreshPromise, TimeoutError, createMapPromiseDebouncer, singletonPromise, timeoutPromise } from "@scrypted/common/src/promise-utils"; import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin"; -import sdk, { BufferConverter, Camera, DeviceManifest, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, MediaObject, MediaObjectOptions, MixinProvider, Online, RequestMediaStreamOptions, RequestPictureOptions, ResponsePictureOptions, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, SettingValue, Settings, VideoCamera, WritableDeviceState } from "@scrypted/sdk"; +import sdk, { BufferConverter, Camera, DeviceManifest, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, MediaObject, MediaObjectOptions, MixinProvider, RequestMediaStreamOptions, RequestPictureOptions, ResponsePictureOptions, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, SettingValue, Settings, Sleep, VideoCamera, WritableDeviceState } from "@scrypted/sdk"; import { StorageSettings } from "@scrypted/sdk/storage-settings"; import https from 'https'; import os from 'os'; @@ -161,7 +161,7 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { } } - const realDevice = systemManager.getDeviceById(this.id); + const realDevice = systemManager.getDeviceById(this.id); let takePrebufferPicture: () => Promise; const preparePrebufferSnapshot = async () => { @@ -263,7 +263,7 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { } try { // consider waking the camera if - if (!eventSnapshot && this.mixinDeviceInterfaces.includes(ScryptedInterface.Battery) && !realDevice.online) + if (!eventSnapshot && this.mixinDeviceInterfaces.includes(ScryptedInterface.Sleep) && realDevice.sleeping) throw new Error('Not waking sleeping camera for periodic snapshot.'); return await this.mixinDevice.takePicture(takePictureOptions).then(mo => mediaManager.convertMediaObjectToBuffer(mo, 'image/jpeg')) }