@camstack/addon-provider-reolink 1.2.125 → 1.2.127
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/addon.js +232 -43
- package/dist/addon.mjs +232 -43
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -5386,7 +5386,7 @@ var ZodIssueCode = {
|
|
|
5386
5386
|
var ZodFirstPartyTypeKind;
|
|
5387
5387
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5388
5388
|
//#endregion
|
|
5389
|
-
//#region ../types/dist/sleep-
|
|
5389
|
+
//#region ../types/dist/sleep-BEyvfshj.mjs
|
|
5390
5390
|
/**
|
|
5391
5391
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5392
5392
|
* window to float samples (D455).
|
|
@@ -22078,6 +22078,14 @@ var MotionSourceEnum = _enum([
|
|
|
22078
22078
|
*/
|
|
22079
22079
|
var MotionSourcesSchema = array(MotionSourceEnum);
|
|
22080
22080
|
/**
|
|
22081
|
+
* Which root detectors a camera runs. Deliberately the SAME vocabulary
|
|
22082
|
+
* post-analysis already tags every detection with (`DetectionSource`) rather
|
|
22083
|
+
* than a second spelling of the same two ideas — the value an operator picks
|
|
22084
|
+
* here is the value that comes back on the track, the overlay and the debug
|
|
22085
|
+
* row.
|
|
22086
|
+
*/
|
|
22087
|
+
var DetectionSourcesSchema = array(DetectionSourceSchema);
|
|
22088
|
+
/**
|
|
22081
22089
|
* Input shape for `pipeline-runner.reportMotion` cap method. Exported
|
|
22082
22090
|
* so cap-side consumers (the orchestrator forward, the runner addon's
|
|
22083
22091
|
* cap implementation, tests) can reuse the type instead of redeclaring
|
|
@@ -22228,6 +22236,35 @@ var RunnerCameraConfigSchema = object({
|
|
|
22228
22236
|
* events; the orchestrator forwards to `reportMotion`.
|
|
22229
22237
|
*/
|
|
22230
22238
|
motionSources: MotionSourcesSchema.default(["analyzer"]),
|
|
22239
|
+
/**
|
|
22240
|
+
* WHICH root detector runs for this camera. The detection counterpart of
|
|
22241
|
+
* {@link motionSources}, same shape and same discipline: a per-camera list
|
|
22242
|
+
* of sources the system understands, vendor-agnostic, owned here.
|
|
22243
|
+
*
|
|
22244
|
+
* - `pipeline` — this runner's own root step on decoded pixels (`steps`).
|
|
22245
|
+
* - `onboard` — the CAMERA's own detector, admitted only when it is named
|
|
22246
|
+
* here. Its boxes reach post-analysis on the same
|
|
22247
|
+
* `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
|
|
22248
|
+
* track gets the same tracker, the same overlay and the same detail
|
|
22249
|
+
* subtree (face / plate / embedding).
|
|
22250
|
+
*
|
|
22251
|
+
* `['onboard']` alone is the REPLACEMENT: this runner's root step does not
|
|
22252
|
+
* run, and that is where a camera with a usable onboard detector buys back
|
|
22253
|
+
* its share of the accelerator. It saves the root inference, not the decode
|
|
22254
|
+
* — the detail subtree still needs pixels to cut crops from.
|
|
22255
|
+
*
|
|
22256
|
+
* A camera whose onboard detections carry no geometry cannot serve as a root
|
|
22257
|
+
* step at all (there is nothing to track), and the cap that knows this says
|
|
22258
|
+
* so on `native-object-detection.getOptions().geometry`. On Reolink that is
|
|
22259
|
+
* every battery camera: the boxes ride a sub-stream a battery camera never
|
|
22260
|
+
* attaches, so such a camera stays on `['pipeline']` and its onboard AI is
|
|
22261
|
+
* worth exactly what it already is — a wake signal with a class.
|
|
22262
|
+
*
|
|
22263
|
+
* Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
|
|
22264
|
+
* vendor never turns a detector on for the operator, and neither does a
|
|
22265
|
+
* default.
|
|
22266
|
+
*/
|
|
22267
|
+
detectionSources: DetectionSourcesSchema.default(["pipeline"]),
|
|
22231
22268
|
pipelineEnabled: boolean().default(true),
|
|
22232
22269
|
/** Ordered tree of video steps. Absent → runner skips video detection. */
|
|
22233
22270
|
steps: array(PipelineStepInputSchema).readonly().optional(),
|
|
@@ -29343,11 +29380,46 @@ var NativeObjectDetectionStatusSchema = object({
|
|
|
29343
29380
|
supportedClasses: array(NativeObjectClassEnum).readonly(),
|
|
29344
29381
|
/**
|
|
29345
29382
|
* Whether forwarding of onboard AI detections is enabled for this device.
|
|
29346
|
-
*
|
|
29347
|
-
*
|
|
29383
|
+
*
|
|
29384
|
+
* Cold-start OFF on every camera. A vendor does not decide which detector a
|
|
29385
|
+
* camera runs — that is the operator's choice, and it is made where every
|
|
29386
|
+
* other detection choice is made. `geometry` on
|
|
29387
|
+
* {@link NativeObjectDetectionOptionsSchema} is how the form says what the
|
|
29388
|
+
* choice would buy this particular camera.
|
|
29348
29389
|
*/
|
|
29349
29390
|
enabled: boolean()
|
|
29350
29391
|
});
|
|
29392
|
+
/**
|
|
29393
|
+
* WHEN a camera's onboard detections carry geometry.
|
|
29394
|
+
*
|
|
29395
|
+
* - `boxed` — always. The provider holds a standing channel for the boxes, so
|
|
29396
|
+
* a detection is a trackable subject whenever the camera sees one.
|
|
29397
|
+
* - `while-streaming` — only while some stream of this camera is already open.
|
|
29398
|
+
* The boxes ride the video's own side-channel rather than a feed of their
|
|
29399
|
+
* own, so they cost nothing and they exist only when something is pulling.
|
|
29400
|
+
* On Reolink this is every BATTERY camera: a persistent second feed is real
|
|
29401
|
+
* radio drain, but a camera that is awake is awake precisely because
|
|
29402
|
+
* something is in front of it, and its stream is already being pulled.
|
|
29403
|
+
* - `flag-only` — never. The firmware ships the class and nothing else; the
|
|
29404
|
+
* pipeline needs geometry to make a subject, so such a push reaches the
|
|
29405
|
+
* tracker as motion and no further.
|
|
29406
|
+
*
|
|
29407
|
+
* Reported and not inferred, because the operator's question in front of the
|
|
29408
|
+
* picker is "what do I get", and these are three different answers (D14: the
|
|
29409
|
+
* derived form is only as honest as `getOptions`).
|
|
29410
|
+
*/
|
|
29411
|
+
var NativeObjectGeometryEnum = _enum([
|
|
29412
|
+
"boxed",
|
|
29413
|
+
"while-streaming",
|
|
29414
|
+
"flag-only"
|
|
29415
|
+
]);
|
|
29416
|
+
var NativeObjectDetectionOptionsSchema = object({
|
|
29417
|
+
/** Classes this firmware can detect — the same list the status reports. */
|
|
29418
|
+
supportedClasses: array(NativeObjectClassEnum).readonly(),
|
|
29419
|
+
/** What a detection from this camera carries. */
|
|
29420
|
+
geometry: NativeObjectGeometryEnum
|
|
29421
|
+
});
|
|
29422
|
+
var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
|
|
29351
29423
|
var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
|
|
29352
29424
|
/** Required by createRuntimeStateBridge — epoch ms of last refresh. */
|
|
29353
29425
|
lastFetchedAt: number() });
|
|
@@ -29357,13 +29429,23 @@ var nativeObjectDetectionCapability = {
|
|
|
29357
29429
|
deviceNative: true,
|
|
29358
29430
|
mode: "singleton",
|
|
29359
29431
|
deviceTypes: [DeviceType.Camera],
|
|
29360
|
-
methods: {
|
|
29361
|
-
deviceId: number(),
|
|
29362
|
-
|
|
29363
|
-
|
|
29364
|
-
|
|
29365
|
-
|
|
29366
|
-
|
|
29432
|
+
methods: {
|
|
29433
|
+
getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
|
|
29434
|
+
setSettings: method(object({
|
|
29435
|
+
deviceId: number(),
|
|
29436
|
+
settings: NativeObjectDetectionSettingsPatchSchema
|
|
29437
|
+
}), _void(), {
|
|
29438
|
+
kind: "mutation",
|
|
29439
|
+
auth: "admin"
|
|
29440
|
+
}),
|
|
29441
|
+
setEnabled: method(object({
|
|
29442
|
+
deviceId: number(),
|
|
29443
|
+
enabled: boolean()
|
|
29444
|
+
}), _void(), {
|
|
29445
|
+
kind: "mutation",
|
|
29446
|
+
auth: "admin"
|
|
29447
|
+
})
|
|
29448
|
+
},
|
|
29367
29449
|
events: { onDetected: { data: object({
|
|
29368
29450
|
deviceId: number(),
|
|
29369
29451
|
detection: NativeDetectionSchema
|
|
@@ -39216,12 +39298,24 @@ Object.freeze({
|
|
|
39216
39298
|
addonId: null,
|
|
39217
39299
|
access: "create"
|
|
39218
39300
|
},
|
|
39301
|
+
"nativeObjectDetection.getOptions": {
|
|
39302
|
+
capName: "native-object-detection",
|
|
39303
|
+
capScope: "device",
|
|
39304
|
+
addonId: null,
|
|
39305
|
+
access: "view"
|
|
39306
|
+
},
|
|
39219
39307
|
"nativeObjectDetection.setEnabled": {
|
|
39220
39308
|
capName: "native-object-detection",
|
|
39221
39309
|
capScope: "device",
|
|
39222
39310
|
addonId: null,
|
|
39223
39311
|
access: "create"
|
|
39224
39312
|
},
|
|
39313
|
+
"nativeObjectDetection.setSettings": {
|
|
39314
|
+
capName: "native-object-detection",
|
|
39315
|
+
capScope: "device",
|
|
39316
|
+
addonId: null,
|
|
39317
|
+
access: "create"
|
|
39318
|
+
},
|
|
39225
39319
|
"navigation.getFeatures": {
|
|
39226
39320
|
capName: "navigation",
|
|
39227
39321
|
capScope: "device",
|
|
@@ -43448,11 +43542,21 @@ Object.freeze({
|
|
|
43448
43542
|
form: "single",
|
|
43449
43543
|
optional: false
|
|
43450
43544
|
}],
|
|
43545
|
+
"nativeObjectDetection.getOptions": [{
|
|
43546
|
+
name: "deviceId",
|
|
43547
|
+
form: "single",
|
|
43548
|
+
optional: false
|
|
43549
|
+
}],
|
|
43451
43550
|
"nativeObjectDetection.setEnabled": [{
|
|
43452
43551
|
name: "deviceId",
|
|
43453
43552
|
form: "single",
|
|
43454
43553
|
optional: false
|
|
43455
43554
|
}],
|
|
43555
|
+
"nativeObjectDetection.setSettings": [{
|
|
43556
|
+
name: "deviceId",
|
|
43557
|
+
form: "single",
|
|
43558
|
+
optional: false
|
|
43559
|
+
}],
|
|
43456
43560
|
"navigation.getFeatures": [{
|
|
43457
43561
|
name: "deviceId",
|
|
43458
43562
|
form: "single",
|
|
@@ -147632,10 +147736,22 @@ var REOLINK_ADDON_ID = "provider-reolink";
|
|
|
147632
147736
|
* CameraNativeDetection class strings the rest of camstack consumes.
|
|
147633
147737
|
* Keeping the loose-string contract — face/package etc are valid.
|
|
147634
147738
|
*/
|
|
147739
|
+
/**
|
|
147740
|
+
* Baichuan AI class name → the cap's `NativeObjectClass`.
|
|
147741
|
+
*
|
|
147742
|
+
* TWO vocabularies arrive here and both must be keyed. The simple-event push
|
|
147743
|
+
* (cmd 33 `<AItype>`) says `animal`; the capability probe (cmd 299
|
|
147744
|
+
* `getAiDetectTypes`) says `dog_cat` for the same class. Keying only the first
|
|
147745
|
+
* made `buildSupportedClasses` drop it, so camera 618 reported
|
|
147746
|
+
* `supportedClasses: ["person","vehicle"]` while its own `lastByClass` held a
|
|
147747
|
+
* live `animal` detection — the camera detected a class the system said it
|
|
147748
|
+
* could not. The two key-spaces do not collide, so one map serves both.
|
|
147749
|
+
*/
|
|
147635
147750
|
var AI_CLASS_MAP = {
|
|
147636
147751
|
people: "person",
|
|
147637
147752
|
vehicle: "vehicle",
|
|
147638
147753
|
animal: "animal",
|
|
147754
|
+
dog_cat: "animal",
|
|
147639
147755
|
face: "face",
|
|
147640
147756
|
package: "package"
|
|
147641
147757
|
};
|
|
@@ -148476,6 +148592,30 @@ function mapDetectionEvent(event, cameraId, nowMs) {
|
|
|
148476
148592
|
function isNativeObjectForwardingEnabled(capState) {
|
|
148477
148593
|
return capState?.enabled === true;
|
|
148478
148594
|
}
|
|
148595
|
+
/**
|
|
148596
|
+
* The classes this firmware can detect, from the cmd-299 probe
|
|
148597
|
+
* (`deviceCache.aiDetectTypes`), mapped through {@link AI_CLASS_MAP}.
|
|
148598
|
+
*
|
|
148599
|
+
* Module-level and exported because the version of this that lived as a
|
|
148600
|
+
* closure inside `registerNativeObjectDetectionCap` had a test that
|
|
148601
|
+
* RE-IMPLEMENTED it — and the copy encoded the `dog_cat` gap as intent
|
|
148602
|
+
* ("skips unknown Baichuan type names"), so the suite stayed green while
|
|
148603
|
+
* camera 618 reported it could not detect a class it was detecting. One
|
|
148604
|
+
* function, one test, no copy.
|
|
148605
|
+
*
|
|
148606
|
+
* An absent probe is not an empty answer: it means nobody has asked the camera
|
|
148607
|
+
* yet, and the honest fallback is the full mapped value-set rather than "this
|
|
148608
|
+
* camera detects nothing".
|
|
148609
|
+
*/
|
|
148610
|
+
function buildSupportedNativeClasses(aiDetectTypes, isSupportedClass) {
|
|
148611
|
+
if (!aiDetectTypes || aiDetectTypes.length === 0) return [...new Set(Object.values(AI_CLASS_MAP))].filter(isSupportedClass);
|
|
148612
|
+
const classes = [];
|
|
148613
|
+
for (const libName of aiDetectTypes) {
|
|
148614
|
+
const mapped = AI_CLASS_MAP[libName];
|
|
148615
|
+
if (mapped !== void 0 && isSupportedClass(mapped) && !classes.includes(mapped)) classes.push(mapped);
|
|
148616
|
+
}
|
|
148617
|
+
return classes;
|
|
148618
|
+
}
|
|
148479
148619
|
function formatUserLevel(level) {
|
|
148480
148620
|
if (level === void 0 || level === null) return "user";
|
|
148481
148621
|
if (typeof level === "string" && level.length > 0) return level;
|
|
@@ -149161,6 +149301,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
149161
149301
|
loginPromise = null;
|
|
149162
149302
|
/** True once we've discovered (via getBatteryInfo) that this is a battery cam. */
|
|
149163
149303
|
isBattery = false;
|
|
149304
|
+
/**
|
|
149305
|
+
* When each AI class last had its suppression reported, so the gate's drop is
|
|
149306
|
+
* on the record without one line per push. Per class because the vocabulary
|
|
149307
|
+
* is five values and the finding is WHICH class the camera is pushing that
|
|
149308
|
+
* nothing downstream receives.
|
|
149309
|
+
*/
|
|
149310
|
+
nativeDetectionSuppressedAt = /* @__PURE__ */ new Map();
|
|
149164
149311
|
/** Reconnect attempt counter — drives exponential backoff. */
|
|
149165
149312
|
reconnectAttempts = 0;
|
|
149166
149313
|
/** Pending reconnect timer; cleared on successful reconnect or removeDevice. */
|
|
@@ -152373,50 +152520,78 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152373
152520
|
* yet (e.g. fresh device, no config cached).
|
|
152374
152521
|
* - `lastByClass`: updated in `handleSimpleEvent` every time an AI-class
|
|
152375
152522
|
* event fires, regardless of the `enabled` flag.
|
|
152376
|
-
* - `enabled`: operator toggle
|
|
152377
|
-
*
|
|
152378
|
-
* behaviour.
|
|
152523
|
+
* - `enabled`: operator toggle, cold-start OFF on every camera. A vendor
|
|
152524
|
+
* never turns a detector on for the operator; see `buildEmptyState`.
|
|
152379
152525
|
*
|
|
152380
152526
|
* Follows the trampoline pattern from `registerStreamParamsCap` — no
|
|
152381
152527
|
* parallel private fields; all state lives in the kernel runtimeState
|
|
152382
152528
|
* slice.
|
|
152383
152529
|
*/
|
|
152530
|
+
/**
|
|
152531
|
+
* Report — once a minute per class — that the forwarding gate dropped an
|
|
152532
|
+
* onboard AI detection. A branch that drops work names it (D391): without
|
|
152533
|
+
* this the toggle being off looks exactly like a camera that pushes nothing,
|
|
152534
|
+
* and the operator's only symptom is silence.
|
|
152535
|
+
*/
|
|
152536
|
+
noteNativeDetectionSuppressed(aiClass) {
|
|
152537
|
+
const now = Date.now();
|
|
152538
|
+
if (now - (this.nativeDetectionSuppressedAt.get(aiClass) ?? 0) < 6e4) return;
|
|
152539
|
+
this.nativeDetectionSuppressedAt.set(aiClass, now);
|
|
152540
|
+
this.ctx.logger.info("Reolink onboard AI detection dropped — forwarding is off for this camera", {
|
|
152541
|
+
tags: { deviceId: this.id },
|
|
152542
|
+
meta: {
|
|
152543
|
+
class: aiClass,
|
|
152544
|
+
battery: this.isBattery
|
|
152545
|
+
}
|
|
152546
|
+
});
|
|
152547
|
+
}
|
|
152384
152548
|
registerNativeObjectDetectionCap() {
|
|
152385
152549
|
const CAP_NAME = "native-object-detection";
|
|
152386
|
-
const buildSupportedClasses = () =>
|
|
152387
|
-
const cached = this.config.get("deviceCache")?.aiDetectTypes;
|
|
152388
|
-
if (!cached || cached.length === 0) return Object.values(AI_CLASS_MAP).filter(isNativeObjectClass);
|
|
152389
|
-
const classes = [];
|
|
152390
|
-
for (const libName of cached) {
|
|
152391
|
-
const mapped = AI_CLASS_MAP[libName];
|
|
152392
|
-
if (mapped !== void 0 && isNativeObjectClass(mapped)) classes.push(mapped);
|
|
152393
|
-
}
|
|
152394
|
-
return classes;
|
|
152395
|
-
};
|
|
152550
|
+
const buildSupportedClasses = () => buildSupportedNativeClasses(this.config.get("deviceCache")?.aiDetectTypes, isNativeObjectClass).filter(isNativeObjectClass);
|
|
152396
152551
|
const buildEmptyState = () => ({
|
|
152397
152552
|
enabled: false,
|
|
152398
152553
|
lastByClass: {},
|
|
152399
152554
|
supportedClasses: buildSupportedClasses(),
|
|
152400
152555
|
lastFetchedAt: 0
|
|
152401
152556
|
});
|
|
152557
|
+
const bridge = createRuntimeStateBridge({
|
|
152558
|
+
runtimeState: this.runtimeState,
|
|
152559
|
+
cap: nativeObjectDetectionCapability,
|
|
152560
|
+
ownDeviceId: this.id,
|
|
152561
|
+
refresh: async () => {},
|
|
152562
|
+
staleMs: Infinity,
|
|
152563
|
+
empty: buildEmptyState
|
|
152564
|
+
});
|
|
152565
|
+
/** Shared by `setEnabled` and the derived form's `setSettings`. */
|
|
152566
|
+
const applyEnabled = async (enabled) => {
|
|
152567
|
+
await this.runtimeState.patchCapState(CAP_NAME, { enabled });
|
|
152568
|
+
const api = this.api;
|
|
152569
|
+
if (!api) return;
|
|
152570
|
+
if (enabled) await this.resubscribeObjectDetections(api, "cap-enabled").catch((err) => {
|
|
152571
|
+
this.ctx.logger.debug("Reolink objectDetections re-arm on enable failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
152572
|
+
});
|
|
152573
|
+
else await this.unsubscribeObjectDetections(api);
|
|
152574
|
+
};
|
|
152402
152575
|
const provider = {
|
|
152403
|
-
getStatus:
|
|
152404
|
-
|
|
152405
|
-
|
|
152406
|
-
|
|
152407
|
-
|
|
152408
|
-
|
|
152409
|
-
|
|
152410
|
-
|
|
152576
|
+
getStatus: bridge.getStatus,
|
|
152577
|
+
getOptions: async ({ deviceId }) => {
|
|
152578
|
+
if (deviceId !== this.id) return {
|
|
152579
|
+
supportedClasses: [],
|
|
152580
|
+
geometry: "flag-only"
|
|
152581
|
+
};
|
|
152582
|
+
return {
|
|
152583
|
+
supportedClasses: buildSupportedClasses(),
|
|
152584
|
+
geometry: this.isBattery ? "while-streaming" : "boxed"
|
|
152585
|
+
};
|
|
152586
|
+
},
|
|
152587
|
+
setSettings: async ({ deviceId, settings }) => {
|
|
152588
|
+
if (deviceId !== this.id) return;
|
|
152589
|
+
if (settings.enabled === void 0) return;
|
|
152590
|
+
await applyEnabled(settings.enabled);
|
|
152591
|
+
},
|
|
152411
152592
|
setEnabled: async ({ deviceId, enabled }) => {
|
|
152412
152593
|
if (deviceId !== this.id) return;
|
|
152413
|
-
await
|
|
152414
|
-
const api = this.api;
|
|
152415
|
-
if (!api) return;
|
|
152416
|
-
if (enabled) await this.resubscribeObjectDetections(api, "cap-enabled").catch((err) => {
|
|
152417
|
-
this.ctx.logger.debug("Reolink objectDetections re-arm on enable failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
152418
|
-
});
|
|
152419
|
-
else await this.unsubscribeObjectDetections(api);
|
|
152594
|
+
await applyEnabled(enabled);
|
|
152420
152595
|
}
|
|
152421
152596
|
};
|
|
152422
152597
|
this.ctx.registerNativeCap(nativeObjectDetectionCapability, provider);
|
|
@@ -154047,14 +154222,22 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
154047
154222
|
});
|
|
154048
154223
|
} catch {}
|
|
154049
154224
|
this.objectDetectionsSubscribed = false;
|
|
154050
|
-
if (this.isBattery) {
|
|
154051
|
-
this.ctx.logger.debug("Reolink objectDetections subscribe skipped — battery cam", { meta: { reason } });
|
|
154052
|
-
return;
|
|
154053
|
-
}
|
|
154054
154225
|
if (!isNativeObjectForwardingEnabled(this.runtimeState.getCapState("native-object-detection"))) {
|
|
154055
154226
|
this.ctx.logger.debug("Reolink objectDetections subscribe skipped — cap disabled", { meta: { reason } });
|
|
154056
154227
|
return;
|
|
154057
154228
|
}
|
|
154229
|
+
if (this.isBattery) {
|
|
154230
|
+
api.onDetection(this.handleObjectDetectionsBound);
|
|
154231
|
+
this.objectDetectionsSubscribed = true;
|
|
154232
|
+
this.ctx.logger.info("Reolink onboard boxes ride the open stream (battery cam)", {
|
|
154233
|
+
tags: { deviceId: this.id },
|
|
154234
|
+
meta: {
|
|
154235
|
+
reason,
|
|
154236
|
+
channel
|
|
154237
|
+
}
|
|
154238
|
+
});
|
|
154239
|
+
return;
|
|
154240
|
+
}
|
|
154058
154241
|
if (!api.client.isSocketConnected() || !api.client.loggedIn) {
|
|
154059
154242
|
this.ctx.logger.debug("Reolink objectDetections subscribe skipped — socket not ready", { meta: {
|
|
154060
154243
|
reason,
|
|
@@ -154080,6 +154263,11 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
154080
154263
|
*/
|
|
154081
154264
|
async unsubscribeObjectDetections(api) {
|
|
154082
154265
|
if (!this.objectDetectionsSubscribed) return;
|
|
154266
|
+
if (this.isBattery) {
|
|
154267
|
+
api.offDetection(this.handleObjectDetectionsBound);
|
|
154268
|
+
this.objectDetectionsSubscribed = false;
|
|
154269
|
+
return;
|
|
154270
|
+
}
|
|
154083
154271
|
try {
|
|
154084
154272
|
await api.offObjectDetections(this.handleObjectDetectionsBound, {
|
|
154085
154273
|
channel: this.getChannel(),
|
|
@@ -154109,6 +154297,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
154109
154297
|
*/
|
|
154110
154298
|
handleObjectDetections(event) {
|
|
154111
154299
|
if (!isNativeObjectForwardingEnabled(this.runtimeState.getCapState("native-object-detection"))) return;
|
|
154300
|
+
if (event.channel !== this.getChannel()) return;
|
|
154112
154301
|
if (event.boxes.length === 0) return;
|
|
154113
154302
|
const now = Date.now();
|
|
154114
154303
|
const payload = mapDetectionEvent(event, this.id, now);
|
|
@@ -155796,7 +155985,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
155796
155985
|
timestamp: now
|
|
155797
155986
|
}]
|
|
155798
155987
|
}));
|
|
155799
|
-
}
|
|
155988
|
+
} else this.noteNativeDetectionSuppressed(aiClass);
|
|
155800
155989
|
this.ctx.eventBus.emit(createEvent(EventCategory.MotionOnMotionChanged, eventSource, {
|
|
155801
155990
|
deviceId: this.id,
|
|
155802
155991
|
detected: true,
|
package/dist/addon.mjs
CHANGED
|
@@ -5381,7 +5381,7 @@ var ZodIssueCode = {
|
|
|
5381
5381
|
var ZodFirstPartyTypeKind;
|
|
5382
5382
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5383
5383
|
//#endregion
|
|
5384
|
-
//#region ../types/dist/sleep-
|
|
5384
|
+
//#region ../types/dist/sleep-BEyvfshj.mjs
|
|
5385
5385
|
/**
|
|
5386
5386
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5387
5387
|
* window to float samples (D455).
|
|
@@ -22073,6 +22073,14 @@ var MotionSourceEnum = _enum([
|
|
|
22073
22073
|
*/
|
|
22074
22074
|
var MotionSourcesSchema = array(MotionSourceEnum);
|
|
22075
22075
|
/**
|
|
22076
|
+
* Which root detectors a camera runs. Deliberately the SAME vocabulary
|
|
22077
|
+
* post-analysis already tags every detection with (`DetectionSource`) rather
|
|
22078
|
+
* than a second spelling of the same two ideas — the value an operator picks
|
|
22079
|
+
* here is the value that comes back on the track, the overlay and the debug
|
|
22080
|
+
* row.
|
|
22081
|
+
*/
|
|
22082
|
+
var DetectionSourcesSchema = array(DetectionSourceSchema);
|
|
22083
|
+
/**
|
|
22076
22084
|
* Input shape for `pipeline-runner.reportMotion` cap method. Exported
|
|
22077
22085
|
* so cap-side consumers (the orchestrator forward, the runner addon's
|
|
22078
22086
|
* cap implementation, tests) can reuse the type instead of redeclaring
|
|
@@ -22223,6 +22231,35 @@ var RunnerCameraConfigSchema = object({
|
|
|
22223
22231
|
* events; the orchestrator forwards to `reportMotion`.
|
|
22224
22232
|
*/
|
|
22225
22233
|
motionSources: MotionSourcesSchema.default(["analyzer"]),
|
|
22234
|
+
/**
|
|
22235
|
+
* WHICH root detector runs for this camera. The detection counterpart of
|
|
22236
|
+
* {@link motionSources}, same shape and same discipline: a per-camera list
|
|
22237
|
+
* of sources the system understands, vendor-agnostic, owned here.
|
|
22238
|
+
*
|
|
22239
|
+
* - `pipeline` — this runner's own root step on decoded pixels (`steps`).
|
|
22240
|
+
* - `onboard` — the CAMERA's own detector, admitted only when it is named
|
|
22241
|
+
* here. Its boxes reach post-analysis on the same
|
|
22242
|
+
* `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
|
|
22243
|
+
* track gets the same tracker, the same overlay and the same detail
|
|
22244
|
+
* subtree (face / plate / embedding).
|
|
22245
|
+
*
|
|
22246
|
+
* `['onboard']` alone is the REPLACEMENT: this runner's root step does not
|
|
22247
|
+
* run, and that is where a camera with a usable onboard detector buys back
|
|
22248
|
+
* its share of the accelerator. It saves the root inference, not the decode
|
|
22249
|
+
* — the detail subtree still needs pixels to cut crops from.
|
|
22250
|
+
*
|
|
22251
|
+
* A camera whose onboard detections carry no geometry cannot serve as a root
|
|
22252
|
+
* step at all (there is nothing to track), and the cap that knows this says
|
|
22253
|
+
* so on `native-object-detection.getOptions().geometry`. On Reolink that is
|
|
22254
|
+
* every battery camera: the boxes ride a sub-stream a battery camera never
|
|
22255
|
+
* attaches, so such a camera stays on `['pipeline']` and its onboard AI is
|
|
22256
|
+
* worth exactly what it already is — a wake signal with a class.
|
|
22257
|
+
*
|
|
22258
|
+
* Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
|
|
22259
|
+
* vendor never turns a detector on for the operator, and neither does a
|
|
22260
|
+
* default.
|
|
22261
|
+
*/
|
|
22262
|
+
detectionSources: DetectionSourcesSchema.default(["pipeline"]),
|
|
22226
22263
|
pipelineEnabled: boolean().default(true),
|
|
22227
22264
|
/** Ordered tree of video steps. Absent → runner skips video detection. */
|
|
22228
22265
|
steps: array(PipelineStepInputSchema).readonly().optional(),
|
|
@@ -29338,11 +29375,46 @@ var NativeObjectDetectionStatusSchema = object({
|
|
|
29338
29375
|
supportedClasses: array(NativeObjectClassEnum).readonly(),
|
|
29339
29376
|
/**
|
|
29340
29377
|
* Whether forwarding of onboard AI detections is enabled for this device.
|
|
29341
|
-
*
|
|
29342
|
-
*
|
|
29378
|
+
*
|
|
29379
|
+
* Cold-start OFF on every camera. A vendor does not decide which detector a
|
|
29380
|
+
* camera runs — that is the operator's choice, and it is made where every
|
|
29381
|
+
* other detection choice is made. `geometry` on
|
|
29382
|
+
* {@link NativeObjectDetectionOptionsSchema} is how the form says what the
|
|
29383
|
+
* choice would buy this particular camera.
|
|
29343
29384
|
*/
|
|
29344
29385
|
enabled: boolean()
|
|
29345
29386
|
});
|
|
29387
|
+
/**
|
|
29388
|
+
* WHEN a camera's onboard detections carry geometry.
|
|
29389
|
+
*
|
|
29390
|
+
* - `boxed` — always. The provider holds a standing channel for the boxes, so
|
|
29391
|
+
* a detection is a trackable subject whenever the camera sees one.
|
|
29392
|
+
* - `while-streaming` — only while some stream of this camera is already open.
|
|
29393
|
+
* The boxes ride the video's own side-channel rather than a feed of their
|
|
29394
|
+
* own, so they cost nothing and they exist only when something is pulling.
|
|
29395
|
+
* On Reolink this is every BATTERY camera: a persistent second feed is real
|
|
29396
|
+
* radio drain, but a camera that is awake is awake precisely because
|
|
29397
|
+
* something is in front of it, and its stream is already being pulled.
|
|
29398
|
+
* - `flag-only` — never. The firmware ships the class and nothing else; the
|
|
29399
|
+
* pipeline needs geometry to make a subject, so such a push reaches the
|
|
29400
|
+
* tracker as motion and no further.
|
|
29401
|
+
*
|
|
29402
|
+
* Reported and not inferred, because the operator's question in front of the
|
|
29403
|
+
* picker is "what do I get", and these are three different answers (D14: the
|
|
29404
|
+
* derived form is only as honest as `getOptions`).
|
|
29405
|
+
*/
|
|
29406
|
+
var NativeObjectGeometryEnum = _enum([
|
|
29407
|
+
"boxed",
|
|
29408
|
+
"while-streaming",
|
|
29409
|
+
"flag-only"
|
|
29410
|
+
]);
|
|
29411
|
+
var NativeObjectDetectionOptionsSchema = object({
|
|
29412
|
+
/** Classes this firmware can detect — the same list the status reports. */
|
|
29413
|
+
supportedClasses: array(NativeObjectClassEnum).readonly(),
|
|
29414
|
+
/** What a detection from this camera carries. */
|
|
29415
|
+
geometry: NativeObjectGeometryEnum
|
|
29416
|
+
});
|
|
29417
|
+
var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
|
|
29346
29418
|
var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
|
|
29347
29419
|
/** Required by createRuntimeStateBridge — epoch ms of last refresh. */
|
|
29348
29420
|
lastFetchedAt: number() });
|
|
@@ -29352,13 +29424,23 @@ var nativeObjectDetectionCapability = {
|
|
|
29352
29424
|
deviceNative: true,
|
|
29353
29425
|
mode: "singleton",
|
|
29354
29426
|
deviceTypes: [DeviceType.Camera],
|
|
29355
|
-
methods: {
|
|
29356
|
-
deviceId: number(),
|
|
29357
|
-
|
|
29358
|
-
|
|
29359
|
-
|
|
29360
|
-
|
|
29361
|
-
|
|
29427
|
+
methods: {
|
|
29428
|
+
getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
|
|
29429
|
+
setSettings: method(object({
|
|
29430
|
+
deviceId: number(),
|
|
29431
|
+
settings: NativeObjectDetectionSettingsPatchSchema
|
|
29432
|
+
}), _void(), {
|
|
29433
|
+
kind: "mutation",
|
|
29434
|
+
auth: "admin"
|
|
29435
|
+
}),
|
|
29436
|
+
setEnabled: method(object({
|
|
29437
|
+
deviceId: number(),
|
|
29438
|
+
enabled: boolean()
|
|
29439
|
+
}), _void(), {
|
|
29440
|
+
kind: "mutation",
|
|
29441
|
+
auth: "admin"
|
|
29442
|
+
})
|
|
29443
|
+
},
|
|
29362
29444
|
events: { onDetected: { data: object({
|
|
29363
29445
|
deviceId: number(),
|
|
29364
29446
|
detection: NativeDetectionSchema
|
|
@@ -39211,12 +39293,24 @@ Object.freeze({
|
|
|
39211
39293
|
addonId: null,
|
|
39212
39294
|
access: "create"
|
|
39213
39295
|
},
|
|
39296
|
+
"nativeObjectDetection.getOptions": {
|
|
39297
|
+
capName: "native-object-detection",
|
|
39298
|
+
capScope: "device",
|
|
39299
|
+
addonId: null,
|
|
39300
|
+
access: "view"
|
|
39301
|
+
},
|
|
39214
39302
|
"nativeObjectDetection.setEnabled": {
|
|
39215
39303
|
capName: "native-object-detection",
|
|
39216
39304
|
capScope: "device",
|
|
39217
39305
|
addonId: null,
|
|
39218
39306
|
access: "create"
|
|
39219
39307
|
},
|
|
39308
|
+
"nativeObjectDetection.setSettings": {
|
|
39309
|
+
capName: "native-object-detection",
|
|
39310
|
+
capScope: "device",
|
|
39311
|
+
addonId: null,
|
|
39312
|
+
access: "create"
|
|
39313
|
+
},
|
|
39220
39314
|
"navigation.getFeatures": {
|
|
39221
39315
|
capName: "navigation",
|
|
39222
39316
|
capScope: "device",
|
|
@@ -43443,11 +43537,21 @@ Object.freeze({
|
|
|
43443
43537
|
form: "single",
|
|
43444
43538
|
optional: false
|
|
43445
43539
|
}],
|
|
43540
|
+
"nativeObjectDetection.getOptions": [{
|
|
43541
|
+
name: "deviceId",
|
|
43542
|
+
form: "single",
|
|
43543
|
+
optional: false
|
|
43544
|
+
}],
|
|
43446
43545
|
"nativeObjectDetection.setEnabled": [{
|
|
43447
43546
|
name: "deviceId",
|
|
43448
43547
|
form: "single",
|
|
43449
43548
|
optional: false
|
|
43450
43549
|
}],
|
|
43550
|
+
"nativeObjectDetection.setSettings": [{
|
|
43551
|
+
name: "deviceId",
|
|
43552
|
+
form: "single",
|
|
43553
|
+
optional: false
|
|
43554
|
+
}],
|
|
43451
43555
|
"navigation.getFeatures": [{
|
|
43452
43556
|
name: "deviceId",
|
|
43453
43557
|
form: "single",
|
|
@@ -147627,10 +147731,22 @@ var REOLINK_ADDON_ID = "provider-reolink";
|
|
|
147627
147731
|
* CameraNativeDetection class strings the rest of camstack consumes.
|
|
147628
147732
|
* Keeping the loose-string contract — face/package etc are valid.
|
|
147629
147733
|
*/
|
|
147734
|
+
/**
|
|
147735
|
+
* Baichuan AI class name → the cap's `NativeObjectClass`.
|
|
147736
|
+
*
|
|
147737
|
+
* TWO vocabularies arrive here and both must be keyed. The simple-event push
|
|
147738
|
+
* (cmd 33 `<AItype>`) says `animal`; the capability probe (cmd 299
|
|
147739
|
+
* `getAiDetectTypes`) says `dog_cat` for the same class. Keying only the first
|
|
147740
|
+
* made `buildSupportedClasses` drop it, so camera 618 reported
|
|
147741
|
+
* `supportedClasses: ["person","vehicle"]` while its own `lastByClass` held a
|
|
147742
|
+
* live `animal` detection — the camera detected a class the system said it
|
|
147743
|
+
* could not. The two key-spaces do not collide, so one map serves both.
|
|
147744
|
+
*/
|
|
147630
147745
|
var AI_CLASS_MAP = {
|
|
147631
147746
|
people: "person",
|
|
147632
147747
|
vehicle: "vehicle",
|
|
147633
147748
|
animal: "animal",
|
|
147749
|
+
dog_cat: "animal",
|
|
147634
147750
|
face: "face",
|
|
147635
147751
|
package: "package"
|
|
147636
147752
|
};
|
|
@@ -148471,6 +148587,30 @@ function mapDetectionEvent(event, cameraId, nowMs) {
|
|
|
148471
148587
|
function isNativeObjectForwardingEnabled(capState) {
|
|
148472
148588
|
return capState?.enabled === true;
|
|
148473
148589
|
}
|
|
148590
|
+
/**
|
|
148591
|
+
* The classes this firmware can detect, from the cmd-299 probe
|
|
148592
|
+
* (`deviceCache.aiDetectTypes`), mapped through {@link AI_CLASS_MAP}.
|
|
148593
|
+
*
|
|
148594
|
+
* Module-level and exported because the version of this that lived as a
|
|
148595
|
+
* closure inside `registerNativeObjectDetectionCap` had a test that
|
|
148596
|
+
* RE-IMPLEMENTED it — and the copy encoded the `dog_cat` gap as intent
|
|
148597
|
+
* ("skips unknown Baichuan type names"), so the suite stayed green while
|
|
148598
|
+
* camera 618 reported it could not detect a class it was detecting. One
|
|
148599
|
+
* function, one test, no copy.
|
|
148600
|
+
*
|
|
148601
|
+
* An absent probe is not an empty answer: it means nobody has asked the camera
|
|
148602
|
+
* yet, and the honest fallback is the full mapped value-set rather than "this
|
|
148603
|
+
* camera detects nothing".
|
|
148604
|
+
*/
|
|
148605
|
+
function buildSupportedNativeClasses(aiDetectTypes, isSupportedClass) {
|
|
148606
|
+
if (!aiDetectTypes || aiDetectTypes.length === 0) return [...new Set(Object.values(AI_CLASS_MAP))].filter(isSupportedClass);
|
|
148607
|
+
const classes = [];
|
|
148608
|
+
for (const libName of aiDetectTypes) {
|
|
148609
|
+
const mapped = AI_CLASS_MAP[libName];
|
|
148610
|
+
if (mapped !== void 0 && isSupportedClass(mapped) && !classes.includes(mapped)) classes.push(mapped);
|
|
148611
|
+
}
|
|
148612
|
+
return classes;
|
|
148613
|
+
}
|
|
148474
148614
|
function formatUserLevel(level) {
|
|
148475
148615
|
if (level === void 0 || level === null) return "user";
|
|
148476
148616
|
if (typeof level === "string" && level.length > 0) return level;
|
|
@@ -149156,6 +149296,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
149156
149296
|
loginPromise = null;
|
|
149157
149297
|
/** True once we've discovered (via getBatteryInfo) that this is a battery cam. */
|
|
149158
149298
|
isBattery = false;
|
|
149299
|
+
/**
|
|
149300
|
+
* When each AI class last had its suppression reported, so the gate's drop is
|
|
149301
|
+
* on the record without one line per push. Per class because the vocabulary
|
|
149302
|
+
* is five values and the finding is WHICH class the camera is pushing that
|
|
149303
|
+
* nothing downstream receives.
|
|
149304
|
+
*/
|
|
149305
|
+
nativeDetectionSuppressedAt = /* @__PURE__ */ new Map();
|
|
149159
149306
|
/** Reconnect attempt counter — drives exponential backoff. */
|
|
149160
149307
|
reconnectAttempts = 0;
|
|
149161
149308
|
/** Pending reconnect timer; cleared on successful reconnect or removeDevice. */
|
|
@@ -152368,50 +152515,78 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152368
152515
|
* yet (e.g. fresh device, no config cached).
|
|
152369
152516
|
* - `lastByClass`: updated in `handleSimpleEvent` every time an AI-class
|
|
152370
152517
|
* event fires, regardless of the `enabled` flag.
|
|
152371
|
-
* - `enabled`: operator toggle
|
|
152372
|
-
*
|
|
152373
|
-
* behaviour.
|
|
152518
|
+
* - `enabled`: operator toggle, cold-start OFF on every camera. A vendor
|
|
152519
|
+
* never turns a detector on for the operator; see `buildEmptyState`.
|
|
152374
152520
|
*
|
|
152375
152521
|
* Follows the trampoline pattern from `registerStreamParamsCap` — no
|
|
152376
152522
|
* parallel private fields; all state lives in the kernel runtimeState
|
|
152377
152523
|
* slice.
|
|
152378
152524
|
*/
|
|
152525
|
+
/**
|
|
152526
|
+
* Report — once a minute per class — that the forwarding gate dropped an
|
|
152527
|
+
* onboard AI detection. A branch that drops work names it (D391): without
|
|
152528
|
+
* this the toggle being off looks exactly like a camera that pushes nothing,
|
|
152529
|
+
* and the operator's only symptom is silence.
|
|
152530
|
+
*/
|
|
152531
|
+
noteNativeDetectionSuppressed(aiClass) {
|
|
152532
|
+
const now = Date.now();
|
|
152533
|
+
if (now - (this.nativeDetectionSuppressedAt.get(aiClass) ?? 0) < 6e4) return;
|
|
152534
|
+
this.nativeDetectionSuppressedAt.set(aiClass, now);
|
|
152535
|
+
this.ctx.logger.info("Reolink onboard AI detection dropped — forwarding is off for this camera", {
|
|
152536
|
+
tags: { deviceId: this.id },
|
|
152537
|
+
meta: {
|
|
152538
|
+
class: aiClass,
|
|
152539
|
+
battery: this.isBattery
|
|
152540
|
+
}
|
|
152541
|
+
});
|
|
152542
|
+
}
|
|
152379
152543
|
registerNativeObjectDetectionCap() {
|
|
152380
152544
|
const CAP_NAME = "native-object-detection";
|
|
152381
|
-
const buildSupportedClasses = () =>
|
|
152382
|
-
const cached = this.config.get("deviceCache")?.aiDetectTypes;
|
|
152383
|
-
if (!cached || cached.length === 0) return Object.values(AI_CLASS_MAP).filter(isNativeObjectClass);
|
|
152384
|
-
const classes = [];
|
|
152385
|
-
for (const libName of cached) {
|
|
152386
|
-
const mapped = AI_CLASS_MAP[libName];
|
|
152387
|
-
if (mapped !== void 0 && isNativeObjectClass(mapped)) classes.push(mapped);
|
|
152388
|
-
}
|
|
152389
|
-
return classes;
|
|
152390
|
-
};
|
|
152545
|
+
const buildSupportedClasses = () => buildSupportedNativeClasses(this.config.get("deviceCache")?.aiDetectTypes, isNativeObjectClass).filter(isNativeObjectClass);
|
|
152391
152546
|
const buildEmptyState = () => ({
|
|
152392
152547
|
enabled: false,
|
|
152393
152548
|
lastByClass: {},
|
|
152394
152549
|
supportedClasses: buildSupportedClasses(),
|
|
152395
152550
|
lastFetchedAt: 0
|
|
152396
152551
|
});
|
|
152552
|
+
const bridge = createRuntimeStateBridge({
|
|
152553
|
+
runtimeState: this.runtimeState,
|
|
152554
|
+
cap: nativeObjectDetectionCapability,
|
|
152555
|
+
ownDeviceId: this.id,
|
|
152556
|
+
refresh: async () => {},
|
|
152557
|
+
staleMs: Infinity,
|
|
152558
|
+
empty: buildEmptyState
|
|
152559
|
+
});
|
|
152560
|
+
/** Shared by `setEnabled` and the derived form's `setSettings`. */
|
|
152561
|
+
const applyEnabled = async (enabled) => {
|
|
152562
|
+
await this.runtimeState.patchCapState(CAP_NAME, { enabled });
|
|
152563
|
+
const api = this.api;
|
|
152564
|
+
if (!api) return;
|
|
152565
|
+
if (enabled) await this.resubscribeObjectDetections(api, "cap-enabled").catch((err) => {
|
|
152566
|
+
this.ctx.logger.debug("Reolink objectDetections re-arm on enable failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
152567
|
+
});
|
|
152568
|
+
else await this.unsubscribeObjectDetections(api);
|
|
152569
|
+
};
|
|
152397
152570
|
const provider = {
|
|
152398
|
-
getStatus:
|
|
152399
|
-
|
|
152400
|
-
|
|
152401
|
-
|
|
152402
|
-
|
|
152403
|
-
|
|
152404
|
-
|
|
152405
|
-
|
|
152571
|
+
getStatus: bridge.getStatus,
|
|
152572
|
+
getOptions: async ({ deviceId }) => {
|
|
152573
|
+
if (deviceId !== this.id) return {
|
|
152574
|
+
supportedClasses: [],
|
|
152575
|
+
geometry: "flag-only"
|
|
152576
|
+
};
|
|
152577
|
+
return {
|
|
152578
|
+
supportedClasses: buildSupportedClasses(),
|
|
152579
|
+
geometry: this.isBattery ? "while-streaming" : "boxed"
|
|
152580
|
+
};
|
|
152581
|
+
},
|
|
152582
|
+
setSettings: async ({ deviceId, settings }) => {
|
|
152583
|
+
if (deviceId !== this.id) return;
|
|
152584
|
+
if (settings.enabled === void 0) return;
|
|
152585
|
+
await applyEnabled(settings.enabled);
|
|
152586
|
+
},
|
|
152406
152587
|
setEnabled: async ({ deviceId, enabled }) => {
|
|
152407
152588
|
if (deviceId !== this.id) return;
|
|
152408
|
-
await
|
|
152409
|
-
const api = this.api;
|
|
152410
|
-
if (!api) return;
|
|
152411
|
-
if (enabled) await this.resubscribeObjectDetections(api, "cap-enabled").catch((err) => {
|
|
152412
|
-
this.ctx.logger.debug("Reolink objectDetections re-arm on enable failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
152413
|
-
});
|
|
152414
|
-
else await this.unsubscribeObjectDetections(api);
|
|
152589
|
+
await applyEnabled(enabled);
|
|
152415
152590
|
}
|
|
152416
152591
|
};
|
|
152417
152592
|
this.ctx.registerNativeCap(nativeObjectDetectionCapability, provider);
|
|
@@ -154042,14 +154217,22 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
154042
154217
|
});
|
|
154043
154218
|
} catch {}
|
|
154044
154219
|
this.objectDetectionsSubscribed = false;
|
|
154045
|
-
if (this.isBattery) {
|
|
154046
|
-
this.ctx.logger.debug("Reolink objectDetections subscribe skipped — battery cam", { meta: { reason } });
|
|
154047
|
-
return;
|
|
154048
|
-
}
|
|
154049
154220
|
if (!isNativeObjectForwardingEnabled(this.runtimeState.getCapState("native-object-detection"))) {
|
|
154050
154221
|
this.ctx.logger.debug("Reolink objectDetections subscribe skipped — cap disabled", { meta: { reason } });
|
|
154051
154222
|
return;
|
|
154052
154223
|
}
|
|
154224
|
+
if (this.isBattery) {
|
|
154225
|
+
api.onDetection(this.handleObjectDetectionsBound);
|
|
154226
|
+
this.objectDetectionsSubscribed = true;
|
|
154227
|
+
this.ctx.logger.info("Reolink onboard boxes ride the open stream (battery cam)", {
|
|
154228
|
+
tags: { deviceId: this.id },
|
|
154229
|
+
meta: {
|
|
154230
|
+
reason,
|
|
154231
|
+
channel
|
|
154232
|
+
}
|
|
154233
|
+
});
|
|
154234
|
+
return;
|
|
154235
|
+
}
|
|
154053
154236
|
if (!api.client.isSocketConnected() || !api.client.loggedIn) {
|
|
154054
154237
|
this.ctx.logger.debug("Reolink objectDetections subscribe skipped — socket not ready", { meta: {
|
|
154055
154238
|
reason,
|
|
@@ -154075,6 +154258,11 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
154075
154258
|
*/
|
|
154076
154259
|
async unsubscribeObjectDetections(api) {
|
|
154077
154260
|
if (!this.objectDetectionsSubscribed) return;
|
|
154261
|
+
if (this.isBattery) {
|
|
154262
|
+
api.offDetection(this.handleObjectDetectionsBound);
|
|
154263
|
+
this.objectDetectionsSubscribed = false;
|
|
154264
|
+
return;
|
|
154265
|
+
}
|
|
154078
154266
|
try {
|
|
154079
154267
|
await api.offObjectDetections(this.handleObjectDetectionsBound, {
|
|
154080
154268
|
channel: this.getChannel(),
|
|
@@ -154104,6 +154292,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
154104
154292
|
*/
|
|
154105
154293
|
handleObjectDetections(event) {
|
|
154106
154294
|
if (!isNativeObjectForwardingEnabled(this.runtimeState.getCapState("native-object-detection"))) return;
|
|
154295
|
+
if (event.channel !== this.getChannel()) return;
|
|
154107
154296
|
if (event.boxes.length === 0) return;
|
|
154108
154297
|
const now = Date.now();
|
|
154109
154298
|
const payload = mapDetectionEvent(event, this.id, now);
|
|
@@ -155791,7 +155980,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
155791
155980
|
timestamp: now
|
|
155792
155981
|
}]
|
|
155793
155982
|
}));
|
|
155794
|
-
}
|
|
155983
|
+
} else this.noteNativeDetectionSuppressed(aiClass);
|
|
155795
155984
|
this.ctx.eventBus.emit(createEvent(EventCategory.MotionOnMotionChanged, eventSource, {
|
|
155796
155985
|
deviceId: this.id,
|
|
155797
155986
|
detected: true,
|