@camstack/addon-provider-reolink 1.2.125 → 1.2.126
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 +178 -40
- package/dist/addon.mjs +178 -40
- 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).
|
|
@@ -29343,11 +29343,39 @@ var NativeObjectDetectionStatusSchema = object({
|
|
|
29343
29343
|
supportedClasses: array(NativeObjectClassEnum).readonly(),
|
|
29344
29344
|
/**
|
|
29345
29345
|
* Whether forwarding of onboard AI detections is enabled for this device.
|
|
29346
|
-
*
|
|
29347
|
-
*
|
|
29346
|
+
*
|
|
29347
|
+
* The COLD-START default is the provider's, and it is not one value for
|
|
29348
|
+
* every camera: a mains camera carries the boxed side-channel and its
|
|
29349
|
+
* detections are usable subjects, so it defaults ON; a battery camera never
|
|
29350
|
+
* attaches that channel, so the same toggle would buy it nothing but a write
|
|
29351
|
+
* — it defaults OFF and an operator who wants it turns it on knowing what it
|
|
29352
|
+
* costs. {@link NativeObjectDetectionOptionsSchema.geometry} is how the UI
|
|
29353
|
+
* says which of the two a camera is.
|
|
29348
29354
|
*/
|
|
29349
29355
|
enabled: boolean()
|
|
29350
29356
|
});
|
|
29357
|
+
/**
|
|
29358
|
+
* What a camera's onboard detections actually CARRY.
|
|
29359
|
+
*
|
|
29360
|
+
* `boxed` — the firmware ships a bounding box with the class, so a detection is
|
|
29361
|
+
* a subject the pipeline can track, crop and score.
|
|
29362
|
+
* `flag-only` — the firmware ships the class and nothing else. The pipeline
|
|
29363
|
+
* needs geometry to make a subject out of it, so such a push reaches the
|
|
29364
|
+
* tracker as motion and no further. On Reolink this is every battery camera:
|
|
29365
|
+
* the boxes ride the BcMedia sub-stream, which a battery camera never attaches.
|
|
29366
|
+
*
|
|
29367
|
+
* Reported and not inferred, because the operator's question in front of the
|
|
29368
|
+
* toggle is "what do I get", and "a class with no box" and "a subject" are
|
|
29369
|
+
* different answers (D14: the derived form is only as honest as `getOptions`).
|
|
29370
|
+
*/
|
|
29371
|
+
var NativeObjectGeometryEnum = _enum(["boxed", "flag-only"]);
|
|
29372
|
+
var NativeObjectDetectionOptionsSchema = object({
|
|
29373
|
+
/** Classes this firmware can detect — the same list the status reports. */
|
|
29374
|
+
supportedClasses: array(NativeObjectClassEnum).readonly(),
|
|
29375
|
+
/** What a detection from this camera carries. */
|
|
29376
|
+
geometry: NativeObjectGeometryEnum
|
|
29377
|
+
});
|
|
29378
|
+
var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
|
|
29351
29379
|
var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
|
|
29352
29380
|
/** Required by createRuntimeStateBridge — epoch ms of last refresh. */
|
|
29353
29381
|
lastFetchedAt: number() });
|
|
@@ -29357,13 +29385,28 @@ var nativeObjectDetectionCapability = {
|
|
|
29357
29385
|
deviceNative: true,
|
|
29358
29386
|
mode: "singleton",
|
|
29359
29387
|
deviceTypes: [DeviceType.Camera],
|
|
29360
|
-
|
|
29361
|
-
|
|
29362
|
-
|
|
29363
|
-
|
|
29364
|
-
|
|
29365
|
-
|
|
29366
|
-
|
|
29388
|
+
deviceConfig: { ui: {
|
|
29389
|
+
kind: "derived-form",
|
|
29390
|
+
builderId: "native-object-detection",
|
|
29391
|
+
tab: "motion"
|
|
29392
|
+
} },
|
|
29393
|
+
methods: {
|
|
29394
|
+
getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
|
|
29395
|
+
setSettings: method(object({
|
|
29396
|
+
deviceId: number(),
|
|
29397
|
+
settings: NativeObjectDetectionSettingsPatchSchema
|
|
29398
|
+
}), _void(), {
|
|
29399
|
+
kind: "mutation",
|
|
29400
|
+
auth: "admin"
|
|
29401
|
+
}),
|
|
29402
|
+
setEnabled: method(object({
|
|
29403
|
+
deviceId: number(),
|
|
29404
|
+
enabled: boolean()
|
|
29405
|
+
}), _void(), {
|
|
29406
|
+
kind: "mutation",
|
|
29407
|
+
auth: "admin"
|
|
29408
|
+
})
|
|
29409
|
+
},
|
|
29367
29410
|
events: { onDetected: { data: object({
|
|
29368
29411
|
deviceId: number(),
|
|
29369
29412
|
detection: NativeDetectionSchema
|
|
@@ -39216,12 +39259,24 @@ Object.freeze({
|
|
|
39216
39259
|
addonId: null,
|
|
39217
39260
|
access: "create"
|
|
39218
39261
|
},
|
|
39262
|
+
"nativeObjectDetection.getOptions": {
|
|
39263
|
+
capName: "native-object-detection",
|
|
39264
|
+
capScope: "device",
|
|
39265
|
+
addonId: null,
|
|
39266
|
+
access: "view"
|
|
39267
|
+
},
|
|
39219
39268
|
"nativeObjectDetection.setEnabled": {
|
|
39220
39269
|
capName: "native-object-detection",
|
|
39221
39270
|
capScope: "device",
|
|
39222
39271
|
addonId: null,
|
|
39223
39272
|
access: "create"
|
|
39224
39273
|
},
|
|
39274
|
+
"nativeObjectDetection.setSettings": {
|
|
39275
|
+
capName: "native-object-detection",
|
|
39276
|
+
capScope: "device",
|
|
39277
|
+
addonId: null,
|
|
39278
|
+
access: "create"
|
|
39279
|
+
},
|
|
39225
39280
|
"navigation.getFeatures": {
|
|
39226
39281
|
capName: "navigation",
|
|
39227
39282
|
capScope: "device",
|
|
@@ -43448,11 +43503,21 @@ Object.freeze({
|
|
|
43448
43503
|
form: "single",
|
|
43449
43504
|
optional: false
|
|
43450
43505
|
}],
|
|
43506
|
+
"nativeObjectDetection.getOptions": [{
|
|
43507
|
+
name: "deviceId",
|
|
43508
|
+
form: "single",
|
|
43509
|
+
optional: false
|
|
43510
|
+
}],
|
|
43451
43511
|
"nativeObjectDetection.setEnabled": [{
|
|
43452
43512
|
name: "deviceId",
|
|
43453
43513
|
form: "single",
|
|
43454
43514
|
optional: false
|
|
43455
43515
|
}],
|
|
43516
|
+
"nativeObjectDetection.setSettings": [{
|
|
43517
|
+
name: "deviceId",
|
|
43518
|
+
form: "single",
|
|
43519
|
+
optional: false
|
|
43520
|
+
}],
|
|
43456
43521
|
"navigation.getFeatures": [{
|
|
43457
43522
|
name: "deviceId",
|
|
43458
43523
|
form: "single",
|
|
@@ -147632,10 +147697,22 @@ var REOLINK_ADDON_ID = "provider-reolink";
|
|
|
147632
147697
|
* CameraNativeDetection class strings the rest of camstack consumes.
|
|
147633
147698
|
* Keeping the loose-string contract — face/package etc are valid.
|
|
147634
147699
|
*/
|
|
147700
|
+
/**
|
|
147701
|
+
* Baichuan AI class name → the cap's `NativeObjectClass`.
|
|
147702
|
+
*
|
|
147703
|
+
* TWO vocabularies arrive here and both must be keyed. The simple-event push
|
|
147704
|
+
* (cmd 33 `<AItype>`) says `animal`; the capability probe (cmd 299
|
|
147705
|
+
* `getAiDetectTypes`) says `dog_cat` for the same class. Keying only the first
|
|
147706
|
+
* made `buildSupportedClasses` drop it, so camera 618 reported
|
|
147707
|
+
* `supportedClasses: ["person","vehicle"]` while its own `lastByClass` held a
|
|
147708
|
+
* live `animal` detection — the camera detected a class the system said it
|
|
147709
|
+
* could not. The two key-spaces do not collide, so one map serves both.
|
|
147710
|
+
*/
|
|
147635
147711
|
var AI_CLASS_MAP = {
|
|
147636
147712
|
people: "person",
|
|
147637
147713
|
vehicle: "vehicle",
|
|
147638
147714
|
animal: "animal",
|
|
147715
|
+
dog_cat: "animal",
|
|
147639
147716
|
face: "face",
|
|
147640
147717
|
package: "package"
|
|
147641
147718
|
};
|
|
@@ -148476,6 +148553,30 @@ function mapDetectionEvent(event, cameraId, nowMs) {
|
|
|
148476
148553
|
function isNativeObjectForwardingEnabled(capState) {
|
|
148477
148554
|
return capState?.enabled === true;
|
|
148478
148555
|
}
|
|
148556
|
+
/**
|
|
148557
|
+
* The classes this firmware can detect, from the cmd-299 probe
|
|
148558
|
+
* (`deviceCache.aiDetectTypes`), mapped through {@link AI_CLASS_MAP}.
|
|
148559
|
+
*
|
|
148560
|
+
* Module-level and exported because the version of this that lived as a
|
|
148561
|
+
* closure inside `registerNativeObjectDetectionCap` had a test that
|
|
148562
|
+
* RE-IMPLEMENTED it — and the copy encoded the `dog_cat` gap as intent
|
|
148563
|
+
* ("skips unknown Baichuan type names"), so the suite stayed green while
|
|
148564
|
+
* camera 618 reported it could not detect a class it was detecting. One
|
|
148565
|
+
* function, one test, no copy.
|
|
148566
|
+
*
|
|
148567
|
+
* An absent probe is not an empty answer: it means nobody has asked the camera
|
|
148568
|
+
* yet, and the honest fallback is the full mapped value-set rather than "this
|
|
148569
|
+
* camera detects nothing".
|
|
148570
|
+
*/
|
|
148571
|
+
function buildSupportedNativeClasses(aiDetectTypes, isSupportedClass) {
|
|
148572
|
+
if (!aiDetectTypes || aiDetectTypes.length === 0) return [...new Set(Object.values(AI_CLASS_MAP))].filter(isSupportedClass);
|
|
148573
|
+
const classes = [];
|
|
148574
|
+
for (const libName of aiDetectTypes) {
|
|
148575
|
+
const mapped = AI_CLASS_MAP[libName];
|
|
148576
|
+
if (mapped !== void 0 && isSupportedClass(mapped) && !classes.includes(mapped)) classes.push(mapped);
|
|
148577
|
+
}
|
|
148578
|
+
return classes;
|
|
148579
|
+
}
|
|
148479
148580
|
function formatUserLevel(level) {
|
|
148480
148581
|
if (level === void 0 || level === null) return "user";
|
|
148481
148582
|
if (typeof level === "string" && level.length > 0) return level;
|
|
@@ -149161,6 +149262,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
149161
149262
|
loginPromise = null;
|
|
149162
149263
|
/** True once we've discovered (via getBatteryInfo) that this is a battery cam. */
|
|
149163
149264
|
isBattery = false;
|
|
149265
|
+
/**
|
|
149266
|
+
* When each AI class last had its suppression reported, so the gate's drop is
|
|
149267
|
+
* on the record without one line per push. Per class because the vocabulary
|
|
149268
|
+
* is five values and the finding is WHICH class the camera is pushing that
|
|
149269
|
+
* nothing downstream receives.
|
|
149270
|
+
*/
|
|
149271
|
+
nativeDetectionSuppressedAt = /* @__PURE__ */ new Map();
|
|
149164
149272
|
/** Reconnect attempt counter — drives exponential backoff. */
|
|
149165
149273
|
reconnectAttempts = 0;
|
|
149166
149274
|
/** Pending reconnect timer; cleared on successful reconnect or removeDevice. */
|
|
@@ -152373,50 +152481,80 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152373
152481
|
* yet (e.g. fresh device, no config cached).
|
|
152374
152482
|
* - `lastByClass`: updated in `handleSimpleEvent` every time an AI-class
|
|
152375
152483
|
* event fires, regardless of the `enabled` flag.
|
|
152376
|
-
* - `enabled`: operator toggle. Cold-start default
|
|
152377
|
-
*
|
|
152378
|
-
*
|
|
152484
|
+
* - `enabled`: operator toggle. Cold-start default is the camera's power
|
|
152485
|
+
* source — ON for a mains camera (its detections carry boxes and become
|
|
152486
|
+
* subjects), OFF for a battery one (no boxed side-channel, so the toggle
|
|
152487
|
+
* would buy it nothing). See `buildEmptyState`.
|
|
152379
152488
|
*
|
|
152380
152489
|
* Follows the trampoline pattern from `registerStreamParamsCap` — no
|
|
152381
152490
|
* parallel private fields; all state lives in the kernel runtimeState
|
|
152382
152491
|
* slice.
|
|
152383
152492
|
*/
|
|
152493
|
+
/**
|
|
152494
|
+
* Report — once a minute per class — that the forwarding gate dropped an
|
|
152495
|
+
* onboard AI detection. A branch that drops work names it (D391): without
|
|
152496
|
+
* this the toggle being off looks exactly like a camera that pushes nothing,
|
|
152497
|
+
* and the operator's only symptom is silence.
|
|
152498
|
+
*/
|
|
152499
|
+
noteNativeDetectionSuppressed(aiClass) {
|
|
152500
|
+
const now = Date.now();
|
|
152501
|
+
if (now - (this.nativeDetectionSuppressedAt.get(aiClass) ?? 0) < 6e4) return;
|
|
152502
|
+
this.nativeDetectionSuppressedAt.set(aiClass, now);
|
|
152503
|
+
this.ctx.logger.info("Reolink onboard AI detection dropped — forwarding is off for this camera", {
|
|
152504
|
+
tags: { deviceId: this.id },
|
|
152505
|
+
meta: {
|
|
152506
|
+
class: aiClass,
|
|
152507
|
+
battery: this.isBattery
|
|
152508
|
+
}
|
|
152509
|
+
});
|
|
152510
|
+
}
|
|
152384
152511
|
registerNativeObjectDetectionCap() {
|
|
152385
152512
|
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
|
-
};
|
|
152513
|
+
const buildSupportedClasses = () => buildSupportedNativeClasses(this.config.get("deviceCache")?.aiDetectTypes, isNativeObjectClass).filter(isNativeObjectClass);
|
|
152396
152514
|
const buildEmptyState = () => ({
|
|
152397
|
-
enabled:
|
|
152515
|
+
enabled: !this.isBattery,
|
|
152398
152516
|
lastByClass: {},
|
|
152399
152517
|
supportedClasses: buildSupportedClasses(),
|
|
152400
152518
|
lastFetchedAt: 0
|
|
152401
152519
|
});
|
|
152520
|
+
const bridge = createRuntimeStateBridge({
|
|
152521
|
+
runtimeState: this.runtimeState,
|
|
152522
|
+
cap: nativeObjectDetectionCapability,
|
|
152523
|
+
ownDeviceId: this.id,
|
|
152524
|
+
refresh: async () => {},
|
|
152525
|
+
staleMs: Infinity,
|
|
152526
|
+
empty: buildEmptyState
|
|
152527
|
+
});
|
|
152528
|
+
/** Shared by `setEnabled` and the derived form's `setSettings`. */
|
|
152529
|
+
const applyEnabled = async (enabled) => {
|
|
152530
|
+
await this.runtimeState.patchCapState(CAP_NAME, { enabled });
|
|
152531
|
+
const api = this.api;
|
|
152532
|
+
if (!api) return;
|
|
152533
|
+
if (enabled) await this.resubscribeObjectDetections(api, "cap-enabled").catch((err) => {
|
|
152534
|
+
this.ctx.logger.debug("Reolink objectDetections re-arm on enable failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
152535
|
+
});
|
|
152536
|
+
else await this.unsubscribeObjectDetections(api);
|
|
152537
|
+
};
|
|
152402
152538
|
const provider = {
|
|
152403
|
-
getStatus:
|
|
152404
|
-
|
|
152405
|
-
|
|
152406
|
-
|
|
152407
|
-
|
|
152408
|
-
|
|
152409
|
-
|
|
152410
|
-
|
|
152539
|
+
getStatus: bridge.getStatus,
|
|
152540
|
+
getOptions: async ({ deviceId }) => {
|
|
152541
|
+
if (deviceId !== this.id) return {
|
|
152542
|
+
supportedClasses: [],
|
|
152543
|
+
geometry: "flag-only"
|
|
152544
|
+
};
|
|
152545
|
+
return {
|
|
152546
|
+
supportedClasses: buildSupportedClasses(),
|
|
152547
|
+
geometry: this.isBattery ? "flag-only" : "boxed"
|
|
152548
|
+
};
|
|
152549
|
+
},
|
|
152550
|
+
setSettings: async ({ deviceId, settings }) => {
|
|
152551
|
+
if (deviceId !== this.id) return;
|
|
152552
|
+
if (settings.enabled === void 0) return;
|
|
152553
|
+
await applyEnabled(settings.enabled);
|
|
152554
|
+
},
|
|
152411
152555
|
setEnabled: async ({ deviceId, enabled }) => {
|
|
152412
152556
|
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);
|
|
152557
|
+
await applyEnabled(enabled);
|
|
152420
152558
|
}
|
|
152421
152559
|
};
|
|
152422
152560
|
this.ctx.registerNativeCap(nativeObjectDetectionCapability, provider);
|
|
@@ -155796,7 +155934,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
155796
155934
|
timestamp: now
|
|
155797
155935
|
}]
|
|
155798
155936
|
}));
|
|
155799
|
-
}
|
|
155937
|
+
} else this.noteNativeDetectionSuppressed(aiClass);
|
|
155800
155938
|
this.ctx.eventBus.emit(createEvent(EventCategory.MotionOnMotionChanged, eventSource, {
|
|
155801
155939
|
deviceId: this.id,
|
|
155802
155940
|
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).
|
|
@@ -29338,11 +29338,39 @@ var NativeObjectDetectionStatusSchema = object({
|
|
|
29338
29338
|
supportedClasses: array(NativeObjectClassEnum).readonly(),
|
|
29339
29339
|
/**
|
|
29340
29340
|
* Whether forwarding of onboard AI detections is enabled for this device.
|
|
29341
|
-
*
|
|
29342
|
-
*
|
|
29341
|
+
*
|
|
29342
|
+
* The COLD-START default is the provider's, and it is not one value for
|
|
29343
|
+
* every camera: a mains camera carries the boxed side-channel and its
|
|
29344
|
+
* detections are usable subjects, so it defaults ON; a battery camera never
|
|
29345
|
+
* attaches that channel, so the same toggle would buy it nothing but a write
|
|
29346
|
+
* — it defaults OFF and an operator who wants it turns it on knowing what it
|
|
29347
|
+
* costs. {@link NativeObjectDetectionOptionsSchema.geometry} is how the UI
|
|
29348
|
+
* says which of the two a camera is.
|
|
29343
29349
|
*/
|
|
29344
29350
|
enabled: boolean()
|
|
29345
29351
|
});
|
|
29352
|
+
/**
|
|
29353
|
+
* What a camera's onboard detections actually CARRY.
|
|
29354
|
+
*
|
|
29355
|
+
* `boxed` — the firmware ships a bounding box with the class, so a detection is
|
|
29356
|
+
* a subject the pipeline can track, crop and score.
|
|
29357
|
+
* `flag-only` — the firmware ships the class and nothing else. The pipeline
|
|
29358
|
+
* needs geometry to make a subject out of it, so such a push reaches the
|
|
29359
|
+
* tracker as motion and no further. On Reolink this is every battery camera:
|
|
29360
|
+
* the boxes ride the BcMedia sub-stream, which a battery camera never attaches.
|
|
29361
|
+
*
|
|
29362
|
+
* Reported and not inferred, because the operator's question in front of the
|
|
29363
|
+
* toggle is "what do I get", and "a class with no box" and "a subject" are
|
|
29364
|
+
* different answers (D14: the derived form is only as honest as `getOptions`).
|
|
29365
|
+
*/
|
|
29366
|
+
var NativeObjectGeometryEnum = _enum(["boxed", "flag-only"]);
|
|
29367
|
+
var NativeObjectDetectionOptionsSchema = object({
|
|
29368
|
+
/** Classes this firmware can detect — the same list the status reports. */
|
|
29369
|
+
supportedClasses: array(NativeObjectClassEnum).readonly(),
|
|
29370
|
+
/** What a detection from this camera carries. */
|
|
29371
|
+
geometry: NativeObjectGeometryEnum
|
|
29372
|
+
});
|
|
29373
|
+
var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
|
|
29346
29374
|
var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
|
|
29347
29375
|
/** Required by createRuntimeStateBridge — epoch ms of last refresh. */
|
|
29348
29376
|
lastFetchedAt: number() });
|
|
@@ -29352,13 +29380,28 @@ var nativeObjectDetectionCapability = {
|
|
|
29352
29380
|
deviceNative: true,
|
|
29353
29381
|
mode: "singleton",
|
|
29354
29382
|
deviceTypes: [DeviceType.Camera],
|
|
29355
|
-
|
|
29356
|
-
|
|
29357
|
-
|
|
29358
|
-
|
|
29359
|
-
|
|
29360
|
-
|
|
29361
|
-
|
|
29383
|
+
deviceConfig: { ui: {
|
|
29384
|
+
kind: "derived-form",
|
|
29385
|
+
builderId: "native-object-detection",
|
|
29386
|
+
tab: "motion"
|
|
29387
|
+
} },
|
|
29388
|
+
methods: {
|
|
29389
|
+
getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
|
|
29390
|
+
setSettings: method(object({
|
|
29391
|
+
deviceId: number(),
|
|
29392
|
+
settings: NativeObjectDetectionSettingsPatchSchema
|
|
29393
|
+
}), _void(), {
|
|
29394
|
+
kind: "mutation",
|
|
29395
|
+
auth: "admin"
|
|
29396
|
+
}),
|
|
29397
|
+
setEnabled: method(object({
|
|
29398
|
+
deviceId: number(),
|
|
29399
|
+
enabled: boolean()
|
|
29400
|
+
}), _void(), {
|
|
29401
|
+
kind: "mutation",
|
|
29402
|
+
auth: "admin"
|
|
29403
|
+
})
|
|
29404
|
+
},
|
|
29362
29405
|
events: { onDetected: { data: object({
|
|
29363
29406
|
deviceId: number(),
|
|
29364
29407
|
detection: NativeDetectionSchema
|
|
@@ -39211,12 +39254,24 @@ Object.freeze({
|
|
|
39211
39254
|
addonId: null,
|
|
39212
39255
|
access: "create"
|
|
39213
39256
|
},
|
|
39257
|
+
"nativeObjectDetection.getOptions": {
|
|
39258
|
+
capName: "native-object-detection",
|
|
39259
|
+
capScope: "device",
|
|
39260
|
+
addonId: null,
|
|
39261
|
+
access: "view"
|
|
39262
|
+
},
|
|
39214
39263
|
"nativeObjectDetection.setEnabled": {
|
|
39215
39264
|
capName: "native-object-detection",
|
|
39216
39265
|
capScope: "device",
|
|
39217
39266
|
addonId: null,
|
|
39218
39267
|
access: "create"
|
|
39219
39268
|
},
|
|
39269
|
+
"nativeObjectDetection.setSettings": {
|
|
39270
|
+
capName: "native-object-detection",
|
|
39271
|
+
capScope: "device",
|
|
39272
|
+
addonId: null,
|
|
39273
|
+
access: "create"
|
|
39274
|
+
},
|
|
39220
39275
|
"navigation.getFeatures": {
|
|
39221
39276
|
capName: "navigation",
|
|
39222
39277
|
capScope: "device",
|
|
@@ -43443,11 +43498,21 @@ Object.freeze({
|
|
|
43443
43498
|
form: "single",
|
|
43444
43499
|
optional: false
|
|
43445
43500
|
}],
|
|
43501
|
+
"nativeObjectDetection.getOptions": [{
|
|
43502
|
+
name: "deviceId",
|
|
43503
|
+
form: "single",
|
|
43504
|
+
optional: false
|
|
43505
|
+
}],
|
|
43446
43506
|
"nativeObjectDetection.setEnabled": [{
|
|
43447
43507
|
name: "deviceId",
|
|
43448
43508
|
form: "single",
|
|
43449
43509
|
optional: false
|
|
43450
43510
|
}],
|
|
43511
|
+
"nativeObjectDetection.setSettings": [{
|
|
43512
|
+
name: "deviceId",
|
|
43513
|
+
form: "single",
|
|
43514
|
+
optional: false
|
|
43515
|
+
}],
|
|
43451
43516
|
"navigation.getFeatures": [{
|
|
43452
43517
|
name: "deviceId",
|
|
43453
43518
|
form: "single",
|
|
@@ -147627,10 +147692,22 @@ var REOLINK_ADDON_ID = "provider-reolink";
|
|
|
147627
147692
|
* CameraNativeDetection class strings the rest of camstack consumes.
|
|
147628
147693
|
* Keeping the loose-string contract — face/package etc are valid.
|
|
147629
147694
|
*/
|
|
147695
|
+
/**
|
|
147696
|
+
* Baichuan AI class name → the cap's `NativeObjectClass`.
|
|
147697
|
+
*
|
|
147698
|
+
* TWO vocabularies arrive here and both must be keyed. The simple-event push
|
|
147699
|
+
* (cmd 33 `<AItype>`) says `animal`; the capability probe (cmd 299
|
|
147700
|
+
* `getAiDetectTypes`) says `dog_cat` for the same class. Keying only the first
|
|
147701
|
+
* made `buildSupportedClasses` drop it, so camera 618 reported
|
|
147702
|
+
* `supportedClasses: ["person","vehicle"]` while its own `lastByClass` held a
|
|
147703
|
+
* live `animal` detection — the camera detected a class the system said it
|
|
147704
|
+
* could not. The two key-spaces do not collide, so one map serves both.
|
|
147705
|
+
*/
|
|
147630
147706
|
var AI_CLASS_MAP = {
|
|
147631
147707
|
people: "person",
|
|
147632
147708
|
vehicle: "vehicle",
|
|
147633
147709
|
animal: "animal",
|
|
147710
|
+
dog_cat: "animal",
|
|
147634
147711
|
face: "face",
|
|
147635
147712
|
package: "package"
|
|
147636
147713
|
};
|
|
@@ -148471,6 +148548,30 @@ function mapDetectionEvent(event, cameraId, nowMs) {
|
|
|
148471
148548
|
function isNativeObjectForwardingEnabled(capState) {
|
|
148472
148549
|
return capState?.enabled === true;
|
|
148473
148550
|
}
|
|
148551
|
+
/**
|
|
148552
|
+
* The classes this firmware can detect, from the cmd-299 probe
|
|
148553
|
+
* (`deviceCache.aiDetectTypes`), mapped through {@link AI_CLASS_MAP}.
|
|
148554
|
+
*
|
|
148555
|
+
* Module-level and exported because the version of this that lived as a
|
|
148556
|
+
* closure inside `registerNativeObjectDetectionCap` had a test that
|
|
148557
|
+
* RE-IMPLEMENTED it — and the copy encoded the `dog_cat` gap as intent
|
|
148558
|
+
* ("skips unknown Baichuan type names"), so the suite stayed green while
|
|
148559
|
+
* camera 618 reported it could not detect a class it was detecting. One
|
|
148560
|
+
* function, one test, no copy.
|
|
148561
|
+
*
|
|
148562
|
+
* An absent probe is not an empty answer: it means nobody has asked the camera
|
|
148563
|
+
* yet, and the honest fallback is the full mapped value-set rather than "this
|
|
148564
|
+
* camera detects nothing".
|
|
148565
|
+
*/
|
|
148566
|
+
function buildSupportedNativeClasses(aiDetectTypes, isSupportedClass) {
|
|
148567
|
+
if (!aiDetectTypes || aiDetectTypes.length === 0) return [...new Set(Object.values(AI_CLASS_MAP))].filter(isSupportedClass);
|
|
148568
|
+
const classes = [];
|
|
148569
|
+
for (const libName of aiDetectTypes) {
|
|
148570
|
+
const mapped = AI_CLASS_MAP[libName];
|
|
148571
|
+
if (mapped !== void 0 && isSupportedClass(mapped) && !classes.includes(mapped)) classes.push(mapped);
|
|
148572
|
+
}
|
|
148573
|
+
return classes;
|
|
148574
|
+
}
|
|
148474
148575
|
function formatUserLevel(level) {
|
|
148475
148576
|
if (level === void 0 || level === null) return "user";
|
|
148476
148577
|
if (typeof level === "string" && level.length > 0) return level;
|
|
@@ -149156,6 +149257,13 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
149156
149257
|
loginPromise = null;
|
|
149157
149258
|
/** True once we've discovered (via getBatteryInfo) that this is a battery cam. */
|
|
149158
149259
|
isBattery = false;
|
|
149260
|
+
/**
|
|
149261
|
+
* When each AI class last had its suppression reported, so the gate's drop is
|
|
149262
|
+
* on the record without one line per push. Per class because the vocabulary
|
|
149263
|
+
* is five values and the finding is WHICH class the camera is pushing that
|
|
149264
|
+
* nothing downstream receives.
|
|
149265
|
+
*/
|
|
149266
|
+
nativeDetectionSuppressedAt = /* @__PURE__ */ new Map();
|
|
149159
149267
|
/** Reconnect attempt counter — drives exponential backoff. */
|
|
149160
149268
|
reconnectAttempts = 0;
|
|
149161
149269
|
/** Pending reconnect timer; cleared on successful reconnect or removeDevice. */
|
|
@@ -152368,50 +152476,80 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
152368
152476
|
* yet (e.g. fresh device, no config cached).
|
|
152369
152477
|
* - `lastByClass`: updated in `handleSimpleEvent` every time an AI-class
|
|
152370
152478
|
* event fires, regardless of the `enabled` flag.
|
|
152371
|
-
* - `enabled`: operator toggle. Cold-start default
|
|
152372
|
-
*
|
|
152373
|
-
*
|
|
152479
|
+
* - `enabled`: operator toggle. Cold-start default is the camera's power
|
|
152480
|
+
* source — ON for a mains camera (its detections carry boxes and become
|
|
152481
|
+
* subjects), OFF for a battery one (no boxed side-channel, so the toggle
|
|
152482
|
+
* would buy it nothing). See `buildEmptyState`.
|
|
152374
152483
|
*
|
|
152375
152484
|
* Follows the trampoline pattern from `registerStreamParamsCap` — no
|
|
152376
152485
|
* parallel private fields; all state lives in the kernel runtimeState
|
|
152377
152486
|
* slice.
|
|
152378
152487
|
*/
|
|
152488
|
+
/**
|
|
152489
|
+
* Report — once a minute per class — that the forwarding gate dropped an
|
|
152490
|
+
* onboard AI detection. A branch that drops work names it (D391): without
|
|
152491
|
+
* this the toggle being off looks exactly like a camera that pushes nothing,
|
|
152492
|
+
* and the operator's only symptom is silence.
|
|
152493
|
+
*/
|
|
152494
|
+
noteNativeDetectionSuppressed(aiClass) {
|
|
152495
|
+
const now = Date.now();
|
|
152496
|
+
if (now - (this.nativeDetectionSuppressedAt.get(aiClass) ?? 0) < 6e4) return;
|
|
152497
|
+
this.nativeDetectionSuppressedAt.set(aiClass, now);
|
|
152498
|
+
this.ctx.logger.info("Reolink onboard AI detection dropped — forwarding is off for this camera", {
|
|
152499
|
+
tags: { deviceId: this.id },
|
|
152500
|
+
meta: {
|
|
152501
|
+
class: aiClass,
|
|
152502
|
+
battery: this.isBattery
|
|
152503
|
+
}
|
|
152504
|
+
});
|
|
152505
|
+
}
|
|
152379
152506
|
registerNativeObjectDetectionCap() {
|
|
152380
152507
|
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
|
-
};
|
|
152508
|
+
const buildSupportedClasses = () => buildSupportedNativeClasses(this.config.get("deviceCache")?.aiDetectTypes, isNativeObjectClass).filter(isNativeObjectClass);
|
|
152391
152509
|
const buildEmptyState = () => ({
|
|
152392
|
-
enabled:
|
|
152510
|
+
enabled: !this.isBattery,
|
|
152393
152511
|
lastByClass: {},
|
|
152394
152512
|
supportedClasses: buildSupportedClasses(),
|
|
152395
152513
|
lastFetchedAt: 0
|
|
152396
152514
|
});
|
|
152515
|
+
const bridge = createRuntimeStateBridge({
|
|
152516
|
+
runtimeState: this.runtimeState,
|
|
152517
|
+
cap: nativeObjectDetectionCapability,
|
|
152518
|
+
ownDeviceId: this.id,
|
|
152519
|
+
refresh: async () => {},
|
|
152520
|
+
staleMs: Infinity,
|
|
152521
|
+
empty: buildEmptyState
|
|
152522
|
+
});
|
|
152523
|
+
/** Shared by `setEnabled` and the derived form's `setSettings`. */
|
|
152524
|
+
const applyEnabled = async (enabled) => {
|
|
152525
|
+
await this.runtimeState.patchCapState(CAP_NAME, { enabled });
|
|
152526
|
+
const api = this.api;
|
|
152527
|
+
if (!api) return;
|
|
152528
|
+
if (enabled) await this.resubscribeObjectDetections(api, "cap-enabled").catch((err) => {
|
|
152529
|
+
this.ctx.logger.debug("Reolink objectDetections re-arm on enable failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
152530
|
+
});
|
|
152531
|
+
else await this.unsubscribeObjectDetections(api);
|
|
152532
|
+
};
|
|
152397
152533
|
const provider = {
|
|
152398
|
-
getStatus:
|
|
152399
|
-
|
|
152400
|
-
|
|
152401
|
-
|
|
152402
|
-
|
|
152403
|
-
|
|
152404
|
-
|
|
152405
|
-
|
|
152534
|
+
getStatus: bridge.getStatus,
|
|
152535
|
+
getOptions: async ({ deviceId }) => {
|
|
152536
|
+
if (deviceId !== this.id) return {
|
|
152537
|
+
supportedClasses: [],
|
|
152538
|
+
geometry: "flag-only"
|
|
152539
|
+
};
|
|
152540
|
+
return {
|
|
152541
|
+
supportedClasses: buildSupportedClasses(),
|
|
152542
|
+
geometry: this.isBattery ? "flag-only" : "boxed"
|
|
152543
|
+
};
|
|
152544
|
+
},
|
|
152545
|
+
setSettings: async ({ deviceId, settings }) => {
|
|
152546
|
+
if (deviceId !== this.id) return;
|
|
152547
|
+
if (settings.enabled === void 0) return;
|
|
152548
|
+
await applyEnabled(settings.enabled);
|
|
152549
|
+
},
|
|
152406
152550
|
setEnabled: async ({ deviceId, enabled }) => {
|
|
152407
152551
|
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);
|
|
152552
|
+
await applyEnabled(enabled);
|
|
152415
152553
|
}
|
|
152416
152554
|
};
|
|
152417
152555
|
this.ctx.registerNativeCap(nativeObjectDetectionCapability, provider);
|
|
@@ -155791,7 +155929,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
155791
155929
|
timestamp: now
|
|
155792
155930
|
}]
|
|
155793
155931
|
}));
|
|
155794
|
-
}
|
|
155932
|
+
} else this.noteNativeDetectionSuppressed(aiClass);
|
|
155795
155933
|
this.ctx.eventBus.emit(createEvent(EventCategory.MotionOnMotionChanged, eventSource, {
|
|
155796
155934
|
deviceId: this.id,
|
|
155797
155935
|
detected: true,
|