@apocaliss92/scrypted-reolink-native 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/camera-battery.ts +28 -0
- package/src/camera.ts +1 -5
- package/src/common.ts +96 -31
- package/src/main.ts +8 -0
- package/src/stream-utils.ts +2 -2
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/camera-battery.ts
CHANGED
|
@@ -180,6 +180,26 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
async checkRecordingAction(newBatteryLevel: number) {
|
|
184
|
+
const nvrDeviceId = this.plugin.nvrDeviceId;
|
|
185
|
+
if (nvrDeviceId && this.mixins.includes(nvrDeviceId)) {
|
|
186
|
+
const logger = this.getBaichuanLogger();
|
|
187
|
+
|
|
188
|
+
const settings = await this.thisDevice.getSettings();
|
|
189
|
+
const isRecording = !settings.find(setting => setting.key === 'recording:privacyMode')?.value;
|
|
190
|
+
const { lowThresholdBatteryRecording, highThresholdBatteryRecording } = this.storageSettings.values;
|
|
191
|
+
|
|
192
|
+
if (isRecording && newBatteryLevel < lowThresholdBatteryRecording) {
|
|
193
|
+
logger.log(`Recording is enabled, but battery level is below low threshold (${newBatteryLevel}% < ${lowThresholdBatteryRecording}%), disabling recording`);
|
|
194
|
+
await this.thisDevice.putSetting('recording:privacyMode', true);
|
|
195
|
+
} else if (!isRecording && newBatteryLevel > highThresholdBatteryRecording) {
|
|
196
|
+
logger.log(`Recording is disabled, but battery level is above high threshold (${newBatteryLevel}% > ${highThresholdBatteryRecording}%), enabling recording`);
|
|
197
|
+
await this.thisDevice.putSetting('recording:privacyMode', false);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
183
203
|
async updateBatteryInfo() {
|
|
184
204
|
const api = await this.ensureClient();
|
|
185
205
|
const channel = this.storageSettings.values.rtspChannel;
|
|
@@ -194,6 +214,8 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
194
214
|
this.batteryLevel = batteryInfo.batteryPercent;
|
|
195
215
|
this.lastBatteryLevel = batteryInfo.batteryPercent;
|
|
196
216
|
|
|
217
|
+
let shouldCheckRecordingAction = true;
|
|
218
|
+
|
|
197
219
|
// Log only if battery level changed
|
|
198
220
|
if (oldLevel !== undefined && oldLevel !== batteryInfo.batteryPercent) {
|
|
199
221
|
if (batteryInfo.chargeStatus !== undefined) {
|
|
@@ -211,6 +233,12 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
211
233
|
} else {
|
|
212
234
|
this.getBaichuanLogger().log(`Battery level set: ${batteryInfo.batteryPercent}%`);
|
|
213
235
|
}
|
|
236
|
+
} else {
|
|
237
|
+
shouldCheckRecordingAction = false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (shouldCheckRecordingAction) {
|
|
241
|
+
await this.checkRecordingAction(batteryInfo.batteryPercent);
|
|
214
242
|
}
|
|
215
243
|
}
|
|
216
244
|
}
|
package/src/camera.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReolinkBaichuanApi } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
2
|
import sdk, { MediaObject, ObjectsDetected, RequestPictureOptions, ResponsePictureOptions, ScryptedInterface, Setting } from "@scrypted/sdk";
|
|
3
3
|
import { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
|
|
4
4
|
import {
|
|
@@ -6,10 +6,6 @@ import {
|
|
|
6
6
|
} from "./common";
|
|
7
7
|
import { createBaichuanApi } from './connect';
|
|
8
8
|
import ReolinkNativePlugin from "./main";
|
|
9
|
-
import {
|
|
10
|
-
buildVideoStreamOptionsFromRtspRtmp,
|
|
11
|
-
fetchVideoStreamOptionsFromApi
|
|
12
|
-
} from './stream-utils';
|
|
13
9
|
|
|
14
10
|
export const moToB64 = async (mo: MediaObject) => {
|
|
15
11
|
const bufferImage = await sdk.mediaManager.convertMediaObjectToBuffer(mo, 'image/jpeg');
|
package/src/common.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
1
|
+
import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
2
|
import sdk, { BinarySensor, Brightness, Camera, Device, DeviceProvider, Intercom, MediaObject, MediaStreamUrl, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, OnOff, PanTiltZoom, PanTiltZoomCommand, RequestMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
|
|
3
3
|
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
4
4
|
import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
|
|
5
|
-
import { BaseBaichuanClass, type
|
|
6
|
-
import {
|
|
5
|
+
import { BaseBaichuanClass, type BaichuanConnectionCallbacks, type BaichuanConnectionConfig } from "./baichuan-base";
|
|
6
|
+
import { normalizeUid, type BaichuanTransport } from "./connect";
|
|
7
7
|
import { convertDebugLogsToApiOptions, DebugLogDisplayNames, DebugLogOption, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
|
|
8
8
|
import { ReolinkBaichuanIntercom } from "./intercom";
|
|
9
9
|
import ReolinkNativePlugin from "./main";
|
|
10
10
|
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
11
11
|
import { ReolinkPtzPresets } from "./presets";
|
|
12
12
|
import {
|
|
13
|
-
|
|
13
|
+
buildVideoStreamOptions,
|
|
14
14
|
createRfc4571MediaObjectFromStreamManager,
|
|
15
15
|
expectedVideoTypeFromUrlMediaStreamOptions,
|
|
16
16
|
isNativeStreamId,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
StreamManager
|
|
20
20
|
} from "./stream-utils";
|
|
21
21
|
import { getDeviceInterfaces, updateDeviceInfo } from "./utils";
|
|
22
|
+
import path from 'path';
|
|
22
23
|
|
|
23
24
|
export type CameraType = 'battery' | 'regular';
|
|
24
25
|
|
|
@@ -238,22 +239,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
238
239
|
type: 'boolean',
|
|
239
240
|
hide: true,
|
|
240
241
|
},
|
|
241
|
-
// snapshotCacheMinutes: {
|
|
242
|
-
// title: "Snapshot Cache Minutes",
|
|
243
|
-
// subgroup: 'Advanced',
|
|
244
|
-
// description: "Return a cached snapshot if taken within the last N minutes.",
|
|
245
|
-
// type: "number",
|
|
246
|
-
// defaultValue: 60,
|
|
247
|
-
// hide: true,
|
|
248
|
-
// },
|
|
249
|
-
batteryUpdateIntervalMinutes: {
|
|
250
|
-
title: "Battery Update Interval (minutes)",
|
|
251
|
-
subgroup: 'Advanced',
|
|
252
|
-
description: "How often to wake up the camera and update battery status and snapshot (default: 60 minutes).",
|
|
253
|
-
type: "number",
|
|
254
|
-
defaultValue: 60,
|
|
255
|
-
hide: true,
|
|
256
|
-
},
|
|
257
242
|
// Regular camera specific
|
|
258
243
|
dispatchEvents: {
|
|
259
244
|
subgroup: 'Advanced',
|
|
@@ -330,7 +315,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
330
315
|
},
|
|
331
316
|
// PTZ Presets
|
|
332
317
|
presets: {
|
|
333
|
-
|
|
318
|
+
subgroup: 'PTZ',
|
|
334
319
|
title: 'Presets to enable',
|
|
335
320
|
description: 'PTZ Presets in the format "id=name". Where id is the PTZ Preset identifier and name is a friendly name.',
|
|
336
321
|
multiple: true,
|
|
@@ -358,11 +343,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
358
343
|
description: 'How long a PTZ command moves before sending stop. Higher = more movement per click.',
|
|
359
344
|
type: 'number',
|
|
360
345
|
defaultValue: 300,
|
|
361
|
-
|
|
346
|
+
subgroup: 'PTZ',
|
|
362
347
|
hide: true,
|
|
363
348
|
},
|
|
364
349
|
ptzZoomStep: {
|
|
365
|
-
|
|
350
|
+
subgroup: 'PTZ',
|
|
366
351
|
title: 'PTZ Zoom Step',
|
|
367
352
|
description: 'How much to change zoom per zoom command (in zoom factor units, where 1.0 is normal).',
|
|
368
353
|
type: 'number',
|
|
@@ -370,7 +355,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
370
355
|
hide: true,
|
|
371
356
|
},
|
|
372
357
|
ptzCreatePreset: {
|
|
373
|
-
|
|
358
|
+
subgroup: 'PTZ',
|
|
374
359
|
title: 'Create Preset',
|
|
375
360
|
description: 'Enter a name and press Save to create a new PTZ preset at the current position.',
|
|
376
361
|
type: 'string',
|
|
@@ -407,7 +392,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
407
392
|
},
|
|
408
393
|
},
|
|
409
394
|
ptzSelectedPreset: {
|
|
410
|
-
|
|
395
|
+
subgroup: 'PTZ',
|
|
411
396
|
title: 'Selected Preset',
|
|
412
397
|
description: 'Select the preset to update or delete. Format: "id=name".',
|
|
413
398
|
type: 'string',
|
|
@@ -416,7 +401,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
416
401
|
hide: true,
|
|
417
402
|
},
|
|
418
403
|
ptzUpdateSelectedPreset: {
|
|
419
|
-
|
|
404
|
+
subgroup: 'PTZ',
|
|
420
405
|
title: 'Update Selected Preset Position',
|
|
421
406
|
description: 'Overwrite the selected preset with the current PTZ position.',
|
|
422
407
|
type: 'button',
|
|
@@ -439,7 +424,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
439
424
|
},
|
|
440
425
|
},
|
|
441
426
|
ptzDeleteSelectedPreset: {
|
|
442
|
-
|
|
427
|
+
subgroup: 'PTZ',
|
|
443
428
|
title: 'Delete Selected Preset',
|
|
444
429
|
description: 'Delete the selected preset (firmware dependent).',
|
|
445
430
|
type: 'button',
|
|
@@ -463,6 +448,47 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
463
448
|
logger.log(`PTZ presets: delete ok (presetId=${presetId})`);
|
|
464
449
|
},
|
|
465
450
|
},
|
|
451
|
+
batteryUpdateIntervalMinutes: {
|
|
452
|
+
title: "Battery Update Interval (minutes)",
|
|
453
|
+
subgroup: 'Advanced',
|
|
454
|
+
description: "How often to wake up the camera and update battery status and snapshot (default: 60 minutes).",
|
|
455
|
+
type: "number",
|
|
456
|
+
defaultValue: 60,
|
|
457
|
+
hide: true,
|
|
458
|
+
},
|
|
459
|
+
lowThresholdBatteryRecording: {
|
|
460
|
+
title: "Low Threshold Battery Recording (%)",
|
|
461
|
+
subgroup: 'Recording',
|
|
462
|
+
description: "Battery level threshold below which recording is disabled (default: 15%).",
|
|
463
|
+
type: "number",
|
|
464
|
+
defaultValue: 15,
|
|
465
|
+
hide: true,
|
|
466
|
+
},
|
|
467
|
+
highThresholdBatteryRecording: {
|
|
468
|
+
title: "High Threshold Battery Recording (%)",
|
|
469
|
+
subgroup: 'Recording',
|
|
470
|
+
description: "Battery level threshold above which recording is enabled (default: 35%).",
|
|
471
|
+
type: "number",
|
|
472
|
+
defaultValue: 35,
|
|
473
|
+
hide: true,
|
|
474
|
+
},
|
|
475
|
+
diagnosticsOutputPath: {
|
|
476
|
+
title: "Diagnostics Output Path",
|
|
477
|
+
subgroup: 'Diagnostics',
|
|
478
|
+
description: "Directory where diagnostics files will be saved (default: plugin volume).",
|
|
479
|
+
type: "string",
|
|
480
|
+
defaultValue: path.join(process.env.SCRYPTED_PLUGIN_VOLUME, 'diagnostics', this.name),
|
|
481
|
+
},
|
|
482
|
+
diagnosticsRun: {
|
|
483
|
+
subgroup: 'Diagnostics',
|
|
484
|
+
title: 'Run Diagnostics',
|
|
485
|
+
description: 'Run all diagnostics and save results to the output path.',
|
|
486
|
+
type: 'button',
|
|
487
|
+
immediate: true,
|
|
488
|
+
onPut: async () => {
|
|
489
|
+
await this.runDiagnostics();
|
|
490
|
+
},
|
|
491
|
+
},
|
|
466
492
|
});
|
|
467
493
|
|
|
468
494
|
ptzPresets = new ReolinkPtzPresets(this);
|
|
@@ -497,15 +523,16 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
497
523
|
initComplete?: boolean;
|
|
498
524
|
resetBaichuanClient?(reason?: any): Promise<void>;
|
|
499
525
|
|
|
500
|
-
protected nvrDevice?:
|
|
526
|
+
protected nvrDevice?: ReolinkNativeNvrDevice;
|
|
527
|
+
thisDevice: Settings
|
|
501
528
|
|
|
502
529
|
constructor(nativeId: string, public plugin: ReolinkNativePlugin, public options: CommonCameraMixinOptions) {
|
|
503
530
|
super(nativeId);
|
|
504
|
-
// Set protocol based on camera type
|
|
505
531
|
this.protocol = !options.nvrDevice && options.type === 'battery' ? 'udp' : 'tcp';
|
|
506
532
|
|
|
507
533
|
// Store NVR device reference if provided
|
|
508
534
|
this.nvrDevice = options.nvrDevice;
|
|
535
|
+
this.thisDevice = sdk.systemManager.getDeviceById<Settings>(this.id);
|
|
509
536
|
|
|
510
537
|
setTimeout(async () => {
|
|
511
538
|
await this.parentInit();
|
|
@@ -554,6 +581,42 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
554
581
|
return this.name || 'Camera';
|
|
555
582
|
}
|
|
556
583
|
|
|
584
|
+
private async runDiagnostics(): Promise<void> {
|
|
585
|
+
const logger = this.getBaichuanLogger();
|
|
586
|
+
const outputPath = this.storageSettings.values.diagnosticsOutputPath || process.env.SCRYPTED_PLUGIN_VOLUME || "";
|
|
587
|
+
|
|
588
|
+
if (!outputPath) {
|
|
589
|
+
throw new Error('Diagnostics output path is required');
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const channel = this.storageSettings.values.rtspChannel || 0;
|
|
593
|
+
const durationSeconds = 15;
|
|
594
|
+
const selection: StreamSamplingSelection = {
|
|
595
|
+
kinds: ['native'],
|
|
596
|
+
profiles: ['main', 'ext', 'sub'],
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
logger.log(`Starting diagnostics with parameters: outDir=${outputPath}, channel=${channel}, durationSeconds=${durationSeconds}, selection=${JSON.stringify(selection)}`);
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
const api = await this.ensureClient();
|
|
603
|
+
|
|
604
|
+
const result = await api.runAllDiagnosticsConsecutively({
|
|
605
|
+
outDir: outputPath,
|
|
606
|
+
channel,
|
|
607
|
+
durationSeconds,
|
|
608
|
+
selection,
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
logger.log(`Diagnostics completed successfully. Output directory: ${result.runDir}`);
|
|
612
|
+
logger.log(`Diagnostics file: ${result.diagnosticsPath}`);
|
|
613
|
+
logger.log(`Streams directory: ${result.streamsDir}`);
|
|
614
|
+
} catch (e) {
|
|
615
|
+
logger.error('Failed to run diagnostics', e);
|
|
616
|
+
throw e;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
557
620
|
protected async onBeforeCleanup(): Promise<void> {
|
|
558
621
|
// Unsubscribe from events if needed
|
|
559
622
|
if (this.onSimpleEvent && this.baichuanApi) {
|
|
@@ -1261,7 +1324,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1261
1324
|
|
|
1262
1325
|
const client = await this.ensureClient();
|
|
1263
1326
|
|
|
1264
|
-
const { ipAddress, rtspChannel
|
|
1327
|
+
const { ipAddress, rtspChannel } = this.storageSettings.values;
|
|
1265
1328
|
|
|
1266
1329
|
try {
|
|
1267
1330
|
await this.ensureNetPortCache();
|
|
@@ -1272,7 +1335,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1272
1335
|
}
|
|
1273
1336
|
|
|
1274
1337
|
try {
|
|
1275
|
-
streams = await
|
|
1338
|
+
streams = await buildVideoStreamOptions(
|
|
1276
1339
|
{
|
|
1277
1340
|
client,
|
|
1278
1341
|
ipAddress,
|
|
@@ -1501,6 +1564,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1501
1564
|
// this.storageSettings.settings.snapshotCacheMinutes.hide = !isBattery;
|
|
1502
1565
|
this.storageSettings.settings.uid.hide = !isBattery;
|
|
1503
1566
|
this.storageSettings.settings.batteryUpdateIntervalMinutes.hide = !isBattery;
|
|
1567
|
+
this.storageSettings.settings.lowThresholdBatteryRecording.hide = !isBattery;
|
|
1568
|
+
this.storageSettings.settings.highThresholdBatteryRecording.hide = !isBattery;
|
|
1504
1569
|
|
|
1505
1570
|
if (isBattery && !this.storageSettings.values.mixinsSetup) {
|
|
1506
1571
|
try {
|
package/src/main.ts
CHANGED
|
@@ -7,6 +7,14 @@ import { getDeviceInterfaces } from "./utils";
|
|
|
7
7
|
|
|
8
8
|
class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
|
|
9
9
|
devices = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera | ReolinkNativeNvrDevice>();
|
|
10
|
+
nvrDeviceId: string;
|
|
11
|
+
|
|
12
|
+
constructor(nativeId: string) {
|
|
13
|
+
super(nativeId);
|
|
14
|
+
|
|
15
|
+
const nvrDevice = sdk.systemManager.getDeviceByName('Scrypted NVR');
|
|
16
|
+
this.nvrDeviceId = nvrDevice?.id;
|
|
17
|
+
}
|
|
10
18
|
|
|
11
19
|
getScryptedDeviceCreator(): string {
|
|
12
20
|
return 'Reolink Native camera';
|
package/src/stream-utils.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ReolinkBaichuanApi,
|
|
3
|
-
StreamProfile,
|
|
4
3
|
ScryptedRfc4571TcpServer,
|
|
4
|
+
StreamProfile,
|
|
5
5
|
VideoType,
|
|
6
6
|
} from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
7
7
|
|
|
@@ -97,7 +97,7 @@ export async function fetchVideoStreamOptionsFromApi(
|
|
|
97
97
|
return streams;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export async function
|
|
100
|
+
export async function buildVideoStreamOptions(
|
|
101
101
|
props: {
|
|
102
102
|
client: ReolinkBaichuanApi,
|
|
103
103
|
ipAddress: string,
|