@camstack/addon-provider-reolink 1.2.6 → 1.2.8
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 +257 -137
- package/dist/addon.mjs +257 -137
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -223465,6 +223465,97 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
223465
223465
|
}
|
|
223466
223466
|
}
|
|
223467
223467
|
/**
|
|
223468
|
+
* Per-cap "warm the cache while the camera is up" closures, registered
|
|
223469
|
+
* by each device-config cap alongside its provider and run by
|
|
223470
|
+
* `onWakeTransition`.
|
|
223471
|
+
*
|
|
223472
|
+
* This is the OTHER half of the read discipline. Gating reads stops us
|
|
223473
|
+
* waking the camera, but on its own it leaves the caches empty — and an
|
|
223474
|
+
* empty cache is what the operator actually sees: the State panel reads
|
|
223475
|
+
* the runtime-state slices (`deviceState.getAllSnapshots`), so a
|
|
223476
|
+
* sleeping camera whose slices were never written shows null for
|
|
223477
|
+
* everything. Before the gate, the admin UI's 2.5s aggregate poll kept
|
|
223478
|
+
* those slices warm by waking the camera every time — the very bug we
|
|
223479
|
+
* fixed. So the refresh has to move to the moment the camera is
|
|
223480
|
+
* ALREADY up, which is exactly the wake transition.
|
|
223481
|
+
*/
|
|
223482
|
+
capWarmers = /* @__PURE__ */ new Map();
|
|
223483
|
+
/** How fresh a slice must be for the wake warm-up to skip it. A battery
|
|
223484
|
+
* cam can wake many times an hour on motion; re-probing every cap on
|
|
223485
|
+
* every wake would spend the (~14s) window on data we already have. */
|
|
223486
|
+
static WAKE_WARM_MAX_AGE_MS = 10 * 6e4;
|
|
223487
|
+
registerCapWarmer(capName, warm) {
|
|
223488
|
+
this.capWarmers.set(capName, warm);
|
|
223489
|
+
}
|
|
223490
|
+
/**
|
|
223491
|
+
* Refresh the device-config cap caches during a wake window.
|
|
223492
|
+
*
|
|
223493
|
+
* SERIALIZED, like every other wake-path refresh — concurrent Baichuan
|
|
223494
|
+
* traffic on the single BCUDP stream starves the heavy reads. Each cap
|
|
223495
|
+
* is skipped when its slice is still fresh, and each failure is
|
|
223496
|
+
* swallowed: the window is short, partial progress accumulates across
|
|
223497
|
+
* wakes because the slices persist.
|
|
223498
|
+
*/
|
|
223499
|
+
async warmCapSlicesOnWake() {
|
|
223500
|
+
const now = Date.now();
|
|
223501
|
+
let warmed = 0;
|
|
223502
|
+
let skipped = 0;
|
|
223503
|
+
for (const [capName, warm] of this.capWarmers) {
|
|
223504
|
+
const slice = this.runtimeState.getCapState(capName);
|
|
223505
|
+
const fetchedAt = typeof slice?.lastFetchedAt === "number" ? slice.lastFetchedAt : 0;
|
|
223506
|
+
if (fetchedAt > 0 && now - fetchedAt < ReolinkCamera.WAKE_WARM_MAX_AGE_MS) {
|
|
223507
|
+
skipped += 1;
|
|
223508
|
+
continue;
|
|
223509
|
+
}
|
|
223510
|
+
try {
|
|
223511
|
+
await warm();
|
|
223512
|
+
warmed += 1;
|
|
223513
|
+
} catch (err) {
|
|
223514
|
+
this.ctx.logger.debug("wake warm-up: cap refresh failed — retrying on next wake", {
|
|
223515
|
+
tags: { deviceId: this.id },
|
|
223516
|
+
meta: {
|
|
223517
|
+
capName,
|
|
223518
|
+
error: err instanceof Error ? err.message : String(err)
|
|
223519
|
+
}
|
|
223520
|
+
});
|
|
223521
|
+
}
|
|
223522
|
+
}
|
|
223523
|
+
this.ctx.logger.info("wake warm-up: cap caches refreshed", {
|
|
223524
|
+
tags: { deviceId: this.id },
|
|
223525
|
+
meta: {
|
|
223526
|
+
warmed,
|
|
223527
|
+
skipped,
|
|
223528
|
+
total: this.capWarmers.size
|
|
223529
|
+
}
|
|
223530
|
+
});
|
|
223531
|
+
}
|
|
223532
|
+
/**
|
|
223533
|
+
* Ask the snapshot wrapper for a frame while the camera is up, so its
|
|
223534
|
+
* cache holds something recent to serve for the whole sleep window.
|
|
223535
|
+
*
|
|
223536
|
+
* The wrapper already does the right thing on a miss — `resolveOutcome`
|
|
223537
|
+
* falls back to the stale frame rather than blanking the UI — but only
|
|
223538
|
+
* if a frame was ever captured. With reads gated, nothing captures one
|
|
223539
|
+
* any more: the tile stayed empty for as long as the camera slept.
|
|
223540
|
+
* `force: true` bypasses the wrapper's freshness gate; our own
|
|
223541
|
+
* proactive gate lets it through because `sleeping` is false here.
|
|
223542
|
+
* Mirrors the reference's `updateBatteryAndSnapshot`.
|
|
223543
|
+
*/
|
|
223544
|
+
async warmSnapshotOnWake() {
|
|
223545
|
+
try {
|
|
223546
|
+
await this.ctx.api.snapshot.getSnapshot.query({
|
|
223547
|
+
deviceId: this.id,
|
|
223548
|
+
force: true
|
|
223549
|
+
});
|
|
223550
|
+
this.ctx.logger.debug("wake warm-up: snapshot cache refreshed", { tags: { deviceId: this.id } });
|
|
223551
|
+
} catch (err) {
|
|
223552
|
+
this.ctx.logger.debug("wake warm-up: snapshot refresh failed", {
|
|
223553
|
+
tags: { deviceId: this.id },
|
|
223554
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
223555
|
+
});
|
|
223556
|
+
}
|
|
223557
|
+
}
|
|
223558
|
+
/**
|
|
223468
223559
|
* Wrap a cap's `refreshFromCamera` so the READ side (the bridge's
|
|
223469
223560
|
* stale-check behind `getStatus`) never wakes a sleeping battery cam.
|
|
223470
223561
|
* The bridge then projects whatever the slice last held — which is
|
|
@@ -223569,6 +223660,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
223569
223660
|
this.ctx.eventBus.emit(createEvent(EventCategory.StreamParamsChanged, this.eventSource(), { deviceId: this.id }));
|
|
223570
223661
|
}
|
|
223571
223662
|
};
|
|
223663
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
223664
|
+
await provider.getOptions({ deviceId: this.id });
|
|
223665
|
+
await refreshFromCamera();
|
|
223666
|
+
});
|
|
223572
223667
|
this.ctx.registerNativeCap(streamParamsCapability, provider);
|
|
223573
223668
|
this.ctx.logger.info("Reolink stream-params cap registered", { tags: { deviceId: this.id } });
|
|
223574
223669
|
}
|
|
@@ -223759,6 +223854,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
223759
223854
|
await refreshFromCamera();
|
|
223760
223855
|
}
|
|
223761
223856
|
};
|
|
223857
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
223858
|
+
await provider.getOptions({ deviceId: this.id });
|
|
223859
|
+
await refreshFromCamera();
|
|
223860
|
+
});
|
|
223762
223861
|
this.ctx.registerNativeCap(motionZonesCapability, provider);
|
|
223763
223862
|
this.ctx.logger.info("Reolink motion-zones cap registered", { tags: { deviceId: this.id } });
|
|
223764
223863
|
}
|
|
@@ -223894,6 +223993,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
223894
223993
|
});
|
|
223895
223994
|
}
|
|
223896
223995
|
};
|
|
223996
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
223997
|
+
await provider.getOptions({ deviceId: this.id });
|
|
223998
|
+
await refreshFromCamera();
|
|
223999
|
+
});
|
|
223897
224000
|
this.ctx.registerNativeCap(privacyMaskCapability, provider);
|
|
223898
224001
|
this.ctx.logger.info("Reolink privacy-mask cap registered (read + enable + zone write)", { tags: { deviceId: this.id } });
|
|
223899
224002
|
}
|
|
@@ -224029,6 +224132,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
224029
224132
|
await refreshFromCamera();
|
|
224030
224133
|
}
|
|
224031
224134
|
};
|
|
224135
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
224136
|
+
await provider.getOptions({ deviceId: this.id });
|
|
224137
|
+
await refreshFromCamera();
|
|
224138
|
+
});
|
|
224032
224139
|
this.ctx.registerNativeCap(dayNightCapability, provider);
|
|
224033
224140
|
this.ctx.logger.info("Reolink day-night cap registered", { tags: { deviceId: this.id } });
|
|
224034
224141
|
}
|
|
@@ -224166,6 +224273,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
224166
224273
|
await refreshFromCamera();
|
|
224167
224274
|
}
|
|
224168
224275
|
};
|
|
224276
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
224277
|
+
await provider.getOptions({ deviceId: this.id });
|
|
224278
|
+
await refreshFromCamera();
|
|
224279
|
+
});
|
|
224169
224280
|
this.ctx.registerNativeCap(imageSettingsCapability, provider);
|
|
224170
224281
|
this.ctx.logger.info("Reolink image-settings cap registered", { tags: { deviceId: this.id } });
|
|
224171
224282
|
}
|
|
@@ -224435,6 +224546,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
224435
224546
|
await refreshFromCamera();
|
|
224436
224547
|
}
|
|
224437
224548
|
};
|
|
224549
|
+
this.registerCapWarmer(CAP_NAME, refreshFromCamera);
|
|
224438
224550
|
this.ctx.registerNativeCap(ptzAutotrackCapability, provider);
|
|
224439
224551
|
this.ctx.logger.info("Reolink ptz-autotrack cap registered", { tags: { deviceId: this.id } });
|
|
224440
224552
|
}
|
|
@@ -224442,148 +224554,154 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
224442
224554
|
if (this.ptzRegistered) return;
|
|
224443
224555
|
if (!this.getProbeFlags().hasPtz) return;
|
|
224444
224556
|
this.ptzRegistered = true;
|
|
224445
|
-
|
|
224446
|
-
|
|
224447
|
-
|
|
224448
|
-
|
|
224449
|
-
|
|
224450
|
-
|
|
224451
|
-
|
|
224452
|
-
|
|
224453
|
-
|
|
224454
|
-
|
|
224455
|
-
|
|
224456
|
-
|
|
224457
|
-
const channel = this.getChannel();
|
|
224458
|
-
try {
|
|
224459
|
-
await api.ptz(channel, {
|
|
224460
|
-
action: "stop",
|
|
224461
|
-
command: "Up"
|
|
224462
|
-
});
|
|
224463
|
-
} catch {}
|
|
224464
|
-
},
|
|
224465
|
-
getPresets: async ({ deviceId }) => {
|
|
224466
|
-
if (deviceId !== this.id) return [];
|
|
224467
|
-
try {
|
|
224557
|
+
{
|
|
224558
|
+
const ptzProvider = {
|
|
224559
|
+
move: async ({ deviceId, pan, tilt, zoom, speed }) => {
|
|
224560
|
+
if (deviceId !== this.id) return;
|
|
224561
|
+
await this.runPtz(pan, tilt, zoom, speed, false);
|
|
224562
|
+
},
|
|
224563
|
+
continuousMove: async ({ deviceId, pan, tilt, zoom, speed }) => {
|
|
224564
|
+
if (deviceId !== this.id) return;
|
|
224565
|
+
await this.runPtz(pan, tilt, zoom, speed, true);
|
|
224566
|
+
},
|
|
224567
|
+
stop: async ({ deviceId }) => {
|
|
224568
|
+
if (deviceId !== this.id) return;
|
|
224468
224569
|
const api = await this.ensureApi();
|
|
224469
224570
|
const channel = this.getChannel();
|
|
224470
|
-
|
|
224471
|
-
|
|
224472
|
-
|
|
224473
|
-
|
|
224474
|
-
|
|
224475
|
-
|
|
224476
|
-
}
|
|
224477
|
-
|
|
224478
|
-
|
|
224479
|
-
|
|
224480
|
-
|
|
224481
|
-
|
|
224482
|
-
|
|
224483
|
-
|
|
224484
|
-
|
|
224485
|
-
|
|
224486
|
-
|
|
224487
|
-
|
|
224488
|
-
const api = await this.ensureApi();
|
|
224489
|
-
const channel = this.getChannel();
|
|
224490
|
-
const id = Number(presetId);
|
|
224491
|
-
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224492
|
-
await api.setPtzPreset(channel, id, name);
|
|
224493
|
-
},
|
|
224494
|
-
deletePreset: async ({ deviceId, presetId }) => {
|
|
224495
|
-
if (deviceId !== this.id) return;
|
|
224496
|
-
const api = await this.ensureApi();
|
|
224497
|
-
const channel = this.getChannel();
|
|
224498
|
-
const id = Number(presetId);
|
|
224499
|
-
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224500
|
-
await api.deletePtzPreset(channel, id);
|
|
224501
|
-
},
|
|
224502
|
-
getOptions: async ({ deviceId }) => {
|
|
224503
|
-
if (deviceId !== this.id) return {
|
|
224504
|
-
hasPan: false,
|
|
224505
|
-
hasTilt: false,
|
|
224506
|
-
hasZoom: false,
|
|
224507
|
-
supportsPresets: false,
|
|
224508
|
-
hasAutofocus: false
|
|
224509
|
-
};
|
|
224510
|
-
const hasAutofocus = this.config.get("deviceCache")?.autoFocusSnapshot?.supported === true;
|
|
224511
|
-
return this.resolveCapOptions({
|
|
224512
|
-
capName: "ptz",
|
|
224513
|
-
schema: PtzOptionsSchema,
|
|
224514
|
-
probe: async () => {
|
|
224515
|
-
const { capabilities } = await (await this.ensureApi()).getDeviceCapabilities(this.getChannel());
|
|
224516
|
-
return {
|
|
224517
|
-
hasPan: capabilities.hasPan,
|
|
224518
|
-
hasTilt: capabilities.hasTilt,
|
|
224519
|
-
hasZoom: capabilities.hasZoom,
|
|
224520
|
-
supportsPresets: capabilities.hasPresets,
|
|
224521
|
-
hasAutofocus
|
|
224522
|
-
};
|
|
224523
|
-
},
|
|
224524
|
-
fallback: () => {
|
|
224525
|
-
const hasPtz = this.getProbeFlags().hasPtz === true;
|
|
224526
|
-
return {
|
|
224527
|
-
hasPan: hasPtz,
|
|
224528
|
-
hasTilt: hasPtz,
|
|
224529
|
-
hasZoom: hasPtz,
|
|
224530
|
-
supportsPresets: hasPtz,
|
|
224531
|
-
hasAutofocus
|
|
224532
|
-
};
|
|
224571
|
+
try {
|
|
224572
|
+
await api.ptz(channel, {
|
|
224573
|
+
action: "stop",
|
|
224574
|
+
command: "Up"
|
|
224575
|
+
});
|
|
224576
|
+
} catch {}
|
|
224577
|
+
},
|
|
224578
|
+
getPresets: async ({ deviceId }) => {
|
|
224579
|
+
if (deviceId !== this.id) return [];
|
|
224580
|
+
try {
|
|
224581
|
+
const api = await this.ensureApi();
|
|
224582
|
+
const channel = this.getChannel();
|
|
224583
|
+
return (await api.getPtzPresets(channel)).map((p) => ({
|
|
224584
|
+
id: String(p.id),
|
|
224585
|
+
name: p.name
|
|
224586
|
+
}));
|
|
224587
|
+
} catch {
|
|
224588
|
+
return [];
|
|
224533
224589
|
}
|
|
224534
|
-
}
|
|
224535
|
-
|
|
224536
|
-
|
|
224537
|
-
if (deviceId !== this.id) return;
|
|
224538
|
-
try {
|
|
224590
|
+
},
|
|
224591
|
+
goToPreset: async ({ deviceId, presetId }) => {
|
|
224592
|
+
if (deviceId !== this.id) return;
|
|
224539
224593
|
const api = await this.ensureApi();
|
|
224540
224594
|
const channel = this.getChannel();
|
|
224541
|
-
|
|
224542
|
-
|
|
224543
|
-
|
|
224544
|
-
|
|
224545
|
-
|
|
224546
|
-
|
|
224547
|
-
|
|
224548
|
-
|
|
224549
|
-
|
|
224550
|
-
|
|
224551
|
-
|
|
224552
|
-
|
|
224553
|
-
|
|
224554
|
-
|
|
224555
|
-
|
|
224556
|
-
|
|
224557
|
-
|
|
224558
|
-
|
|
224559
|
-
|
|
224560
|
-
|
|
224561
|
-
|
|
224562
|
-
|
|
224563
|
-
|
|
224564
|
-
|
|
224565
|
-
|
|
224566
|
-
|
|
224567
|
-
|
|
224568
|
-
|
|
224569
|
-
|
|
224570
|
-
|
|
224571
|
-
|
|
224572
|
-
|
|
224573
|
-
|
|
224574
|
-
|
|
224575
|
-
|
|
224576
|
-
|
|
224577
|
-
|
|
224578
|
-
|
|
224579
|
-
|
|
224580
|
-
|
|
224581
|
-
|
|
224595
|
+
const id = Number(presetId);
|
|
224596
|
+
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224597
|
+
await api.moveToPtzPreset(channel, id);
|
|
224598
|
+
},
|
|
224599
|
+
savePreset: async ({ deviceId, presetId, name }) => {
|
|
224600
|
+
if (deviceId !== this.id) return;
|
|
224601
|
+
const api = await this.ensureApi();
|
|
224602
|
+
const channel = this.getChannel();
|
|
224603
|
+
const id = Number(presetId);
|
|
224604
|
+
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224605
|
+
await api.setPtzPreset(channel, id, name);
|
|
224606
|
+
},
|
|
224607
|
+
deletePreset: async ({ deviceId, presetId }) => {
|
|
224608
|
+
if (deviceId !== this.id) return;
|
|
224609
|
+
const api = await this.ensureApi();
|
|
224610
|
+
const channel = this.getChannel();
|
|
224611
|
+
const id = Number(presetId);
|
|
224612
|
+
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224613
|
+
await api.deletePtzPreset(channel, id);
|
|
224614
|
+
},
|
|
224615
|
+
getOptions: async ({ deviceId }) => {
|
|
224616
|
+
if (deviceId !== this.id) return {
|
|
224617
|
+
hasPan: false,
|
|
224618
|
+
hasTilt: false,
|
|
224619
|
+
hasZoom: false,
|
|
224620
|
+
supportsPresets: false,
|
|
224621
|
+
hasAutofocus: false
|
|
224622
|
+
};
|
|
224623
|
+
const hasAutofocus = this.config.get("deviceCache")?.autoFocusSnapshot?.supported === true;
|
|
224624
|
+
return this.resolveCapOptions({
|
|
224625
|
+
capName: "ptz",
|
|
224626
|
+
schema: PtzOptionsSchema,
|
|
224627
|
+
probe: async () => {
|
|
224628
|
+
const { capabilities } = await (await this.ensureApi()).getDeviceCapabilities(this.getChannel());
|
|
224629
|
+
return {
|
|
224630
|
+
hasPan: capabilities.hasPan,
|
|
224631
|
+
hasTilt: capabilities.hasTilt,
|
|
224632
|
+
hasZoom: capabilities.hasZoom,
|
|
224633
|
+
supportsPresets: capabilities.hasPresets,
|
|
224634
|
+
hasAutofocus
|
|
224635
|
+
};
|
|
224636
|
+
},
|
|
224637
|
+
fallback: () => {
|
|
224638
|
+
const hasPtz = this.getProbeFlags().hasPtz === true;
|
|
224639
|
+
return {
|
|
224640
|
+
hasPan: hasPtz,
|
|
224641
|
+
hasTilt: hasPtz,
|
|
224642
|
+
hasZoom: hasPtz,
|
|
224643
|
+
supportsPresets: hasPtz,
|
|
224644
|
+
hasAutofocus
|
|
224645
|
+
};
|
|
224582
224646
|
}
|
|
224583
|
-
}
|
|
224584
|
-
}
|
|
224585
|
-
|
|
224586
|
-
|
|
224647
|
+
});
|
|
224648
|
+
},
|
|
224649
|
+
goHome: async ({ deviceId }) => {
|
|
224650
|
+
if (deviceId !== this.id) return;
|
|
224651
|
+
try {
|
|
224652
|
+
const api = await this.ensureApi();
|
|
224653
|
+
const channel = this.getChannel();
|
|
224654
|
+
await api.moveToPtzPreset(channel, 0);
|
|
224655
|
+
} catch {}
|
|
224656
|
+
},
|
|
224657
|
+
getPosition: async ({ deviceId }) => {
|
|
224658
|
+
if (deviceId !== this.id) return {
|
|
224659
|
+
pan: 0,
|
|
224660
|
+
tilt: 0,
|
|
224661
|
+
zoom: 0
|
|
224662
|
+
};
|
|
224663
|
+
return {
|
|
224664
|
+
pan: 0,
|
|
224665
|
+
tilt: 0,
|
|
224666
|
+
zoom: 0
|
|
224667
|
+
};
|
|
224668
|
+
},
|
|
224669
|
+
getStatus: async ({ deviceId }) => {
|
|
224670
|
+
if (deviceId !== this.id) return {
|
|
224671
|
+
pan: 0,
|
|
224672
|
+
tilt: 0,
|
|
224673
|
+
zoom: 0,
|
|
224674
|
+
autofocus: false
|
|
224675
|
+
};
|
|
224676
|
+
return {
|
|
224677
|
+
pan: 0,
|
|
224678
|
+
tilt: 0,
|
|
224679
|
+
zoom: 0,
|
|
224680
|
+
autofocus: (this.config.get("deviceCache")?.autoFocusSnapshot)?.enabled === true
|
|
224681
|
+
};
|
|
224682
|
+
},
|
|
224683
|
+
setAutofocus: async ({ deviceId, enabled }) => {
|
|
224684
|
+
if (deviceId !== this.id) return;
|
|
224685
|
+
const api = await this.ensureApi();
|
|
224686
|
+
const channel = this.getChannel();
|
|
224687
|
+
await api.setAutoFocus(channel, enabled ? 0 : 1);
|
|
224688
|
+
try {
|
|
224689
|
+
const a = (await api.getAutoFocus(channel, { timeoutMs: 1500 }))?.body?.AutoFocus;
|
|
224690
|
+
if (a) await this.config.setAll({ deviceCache: {
|
|
224691
|
+
...this.config.get("deviceCache"),
|
|
224692
|
+
autoFocusSnapshot: {
|
|
224693
|
+
enabled: typeof a.disable === "number" ? a.disable === 0 : null,
|
|
224694
|
+
supported: true
|
|
224695
|
+
}
|
|
224696
|
+
} });
|
|
224697
|
+
} catch {}
|
|
224698
|
+
}
|
|
224699
|
+
};
|
|
224700
|
+
this.registerCapWarmer("ptz", async () => {
|
|
224701
|
+
await ptzProvider.getOptions({ deviceId: this.id });
|
|
224702
|
+
});
|
|
224703
|
+
this.ctx.registerNativeCap(ptzCapability, ptzProvider);
|
|
224704
|
+
}
|
|
224587
224705
|
}
|
|
224588
224706
|
/**
|
|
224589
224707
|
* Translate normalized (-1..1) pan/tilt/zoom to one or more discrete
|
|
@@ -225286,7 +225404,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
225286
225404
|
if (this.batteryUpdateInFlight) return this.batteryUpdateInFlight;
|
|
225287
225405
|
const cycle = (async () => {
|
|
225288
225406
|
try {
|
|
225289
|
-
if (
|
|
225407
|
+
if (this.isBattery && this.sleeping) {
|
|
225290
225408
|
this.ctx.logger.debug("battery-update: cam sleeping — skipping cycle without connecting", { tags: { deviceId: this.id } });
|
|
225291
225409
|
return;
|
|
225292
225410
|
}
|
|
@@ -225388,6 +225506,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
225388
225506
|
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
225389
225507
|
});
|
|
225390
225508
|
}
|
|
225509
|
+
await this.warmCapSlicesOnWake();
|
|
225510
|
+
await this.warmSnapshotOnWake();
|
|
225391
225511
|
await this.alignAuxDevicesState("wake");
|
|
225392
225512
|
await this.refreshParentSettingsSnapshot();
|
|
225393
225513
|
}
|
package/dist/addon.mjs
CHANGED
|
@@ -223445,6 +223445,97 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
223445
223445
|
}
|
|
223446
223446
|
}
|
|
223447
223447
|
/**
|
|
223448
|
+
* Per-cap "warm the cache while the camera is up" closures, registered
|
|
223449
|
+
* by each device-config cap alongside its provider and run by
|
|
223450
|
+
* `onWakeTransition`.
|
|
223451
|
+
*
|
|
223452
|
+
* This is the OTHER half of the read discipline. Gating reads stops us
|
|
223453
|
+
* waking the camera, but on its own it leaves the caches empty — and an
|
|
223454
|
+
* empty cache is what the operator actually sees: the State panel reads
|
|
223455
|
+
* the runtime-state slices (`deviceState.getAllSnapshots`), so a
|
|
223456
|
+
* sleeping camera whose slices were never written shows null for
|
|
223457
|
+
* everything. Before the gate, the admin UI's 2.5s aggregate poll kept
|
|
223458
|
+
* those slices warm by waking the camera every time — the very bug we
|
|
223459
|
+
* fixed. So the refresh has to move to the moment the camera is
|
|
223460
|
+
* ALREADY up, which is exactly the wake transition.
|
|
223461
|
+
*/
|
|
223462
|
+
capWarmers = /* @__PURE__ */ new Map();
|
|
223463
|
+
/** How fresh a slice must be for the wake warm-up to skip it. A battery
|
|
223464
|
+
* cam can wake many times an hour on motion; re-probing every cap on
|
|
223465
|
+
* every wake would spend the (~14s) window on data we already have. */
|
|
223466
|
+
static WAKE_WARM_MAX_AGE_MS = 10 * 6e4;
|
|
223467
|
+
registerCapWarmer(capName, warm) {
|
|
223468
|
+
this.capWarmers.set(capName, warm);
|
|
223469
|
+
}
|
|
223470
|
+
/**
|
|
223471
|
+
* Refresh the device-config cap caches during a wake window.
|
|
223472
|
+
*
|
|
223473
|
+
* SERIALIZED, like every other wake-path refresh — concurrent Baichuan
|
|
223474
|
+
* traffic on the single BCUDP stream starves the heavy reads. Each cap
|
|
223475
|
+
* is skipped when its slice is still fresh, and each failure is
|
|
223476
|
+
* swallowed: the window is short, partial progress accumulates across
|
|
223477
|
+
* wakes because the slices persist.
|
|
223478
|
+
*/
|
|
223479
|
+
async warmCapSlicesOnWake() {
|
|
223480
|
+
const now = Date.now();
|
|
223481
|
+
let warmed = 0;
|
|
223482
|
+
let skipped = 0;
|
|
223483
|
+
for (const [capName, warm] of this.capWarmers) {
|
|
223484
|
+
const slice = this.runtimeState.getCapState(capName);
|
|
223485
|
+
const fetchedAt = typeof slice?.lastFetchedAt === "number" ? slice.lastFetchedAt : 0;
|
|
223486
|
+
if (fetchedAt > 0 && now - fetchedAt < ReolinkCamera.WAKE_WARM_MAX_AGE_MS) {
|
|
223487
|
+
skipped += 1;
|
|
223488
|
+
continue;
|
|
223489
|
+
}
|
|
223490
|
+
try {
|
|
223491
|
+
await warm();
|
|
223492
|
+
warmed += 1;
|
|
223493
|
+
} catch (err) {
|
|
223494
|
+
this.ctx.logger.debug("wake warm-up: cap refresh failed — retrying on next wake", {
|
|
223495
|
+
tags: { deviceId: this.id },
|
|
223496
|
+
meta: {
|
|
223497
|
+
capName,
|
|
223498
|
+
error: err instanceof Error ? err.message : String(err)
|
|
223499
|
+
}
|
|
223500
|
+
});
|
|
223501
|
+
}
|
|
223502
|
+
}
|
|
223503
|
+
this.ctx.logger.info("wake warm-up: cap caches refreshed", {
|
|
223504
|
+
tags: { deviceId: this.id },
|
|
223505
|
+
meta: {
|
|
223506
|
+
warmed,
|
|
223507
|
+
skipped,
|
|
223508
|
+
total: this.capWarmers.size
|
|
223509
|
+
}
|
|
223510
|
+
});
|
|
223511
|
+
}
|
|
223512
|
+
/**
|
|
223513
|
+
* Ask the snapshot wrapper for a frame while the camera is up, so its
|
|
223514
|
+
* cache holds something recent to serve for the whole sleep window.
|
|
223515
|
+
*
|
|
223516
|
+
* The wrapper already does the right thing on a miss — `resolveOutcome`
|
|
223517
|
+
* falls back to the stale frame rather than blanking the UI — but only
|
|
223518
|
+
* if a frame was ever captured. With reads gated, nothing captures one
|
|
223519
|
+
* any more: the tile stayed empty for as long as the camera slept.
|
|
223520
|
+
* `force: true` bypasses the wrapper's freshness gate; our own
|
|
223521
|
+
* proactive gate lets it through because `sleeping` is false here.
|
|
223522
|
+
* Mirrors the reference's `updateBatteryAndSnapshot`.
|
|
223523
|
+
*/
|
|
223524
|
+
async warmSnapshotOnWake() {
|
|
223525
|
+
try {
|
|
223526
|
+
await this.ctx.api.snapshot.getSnapshot.query({
|
|
223527
|
+
deviceId: this.id,
|
|
223528
|
+
force: true
|
|
223529
|
+
});
|
|
223530
|
+
this.ctx.logger.debug("wake warm-up: snapshot cache refreshed", { tags: { deviceId: this.id } });
|
|
223531
|
+
} catch (err) {
|
|
223532
|
+
this.ctx.logger.debug("wake warm-up: snapshot refresh failed", {
|
|
223533
|
+
tags: { deviceId: this.id },
|
|
223534
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
223535
|
+
});
|
|
223536
|
+
}
|
|
223537
|
+
}
|
|
223538
|
+
/**
|
|
223448
223539
|
* Wrap a cap's `refreshFromCamera` so the READ side (the bridge's
|
|
223449
223540
|
* stale-check behind `getStatus`) never wakes a sleeping battery cam.
|
|
223450
223541
|
* The bridge then projects whatever the slice last held — which is
|
|
@@ -223549,6 +223640,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
223549
223640
|
this.ctx.eventBus.emit(createEvent(EventCategory.StreamParamsChanged, this.eventSource(), { deviceId: this.id }));
|
|
223550
223641
|
}
|
|
223551
223642
|
};
|
|
223643
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
223644
|
+
await provider.getOptions({ deviceId: this.id });
|
|
223645
|
+
await refreshFromCamera();
|
|
223646
|
+
});
|
|
223552
223647
|
this.ctx.registerNativeCap(streamParamsCapability, provider);
|
|
223553
223648
|
this.ctx.logger.info("Reolink stream-params cap registered", { tags: { deviceId: this.id } });
|
|
223554
223649
|
}
|
|
@@ -223739,6 +223834,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
223739
223834
|
await refreshFromCamera();
|
|
223740
223835
|
}
|
|
223741
223836
|
};
|
|
223837
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
223838
|
+
await provider.getOptions({ deviceId: this.id });
|
|
223839
|
+
await refreshFromCamera();
|
|
223840
|
+
});
|
|
223742
223841
|
this.ctx.registerNativeCap(motionZonesCapability, provider);
|
|
223743
223842
|
this.ctx.logger.info("Reolink motion-zones cap registered", { tags: { deviceId: this.id } });
|
|
223744
223843
|
}
|
|
@@ -223874,6 +223973,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
223874
223973
|
});
|
|
223875
223974
|
}
|
|
223876
223975
|
};
|
|
223976
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
223977
|
+
await provider.getOptions({ deviceId: this.id });
|
|
223978
|
+
await refreshFromCamera();
|
|
223979
|
+
});
|
|
223877
223980
|
this.ctx.registerNativeCap(privacyMaskCapability, provider);
|
|
223878
223981
|
this.ctx.logger.info("Reolink privacy-mask cap registered (read + enable + zone write)", { tags: { deviceId: this.id } });
|
|
223879
223982
|
}
|
|
@@ -224009,6 +224112,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
224009
224112
|
await refreshFromCamera();
|
|
224010
224113
|
}
|
|
224011
224114
|
};
|
|
224115
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
224116
|
+
await provider.getOptions({ deviceId: this.id });
|
|
224117
|
+
await refreshFromCamera();
|
|
224118
|
+
});
|
|
224012
224119
|
this.ctx.registerNativeCap(dayNightCapability, provider);
|
|
224013
224120
|
this.ctx.logger.info("Reolink day-night cap registered", { tags: { deviceId: this.id } });
|
|
224014
224121
|
}
|
|
@@ -224146,6 +224253,10 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
224146
224253
|
await refreshFromCamera();
|
|
224147
224254
|
}
|
|
224148
224255
|
};
|
|
224256
|
+
this.registerCapWarmer(CAP_NAME, async () => {
|
|
224257
|
+
await provider.getOptions({ deviceId: this.id });
|
|
224258
|
+
await refreshFromCamera();
|
|
224259
|
+
});
|
|
224149
224260
|
this.ctx.registerNativeCap(imageSettingsCapability, provider);
|
|
224150
224261
|
this.ctx.logger.info("Reolink image-settings cap registered", { tags: { deviceId: this.id } });
|
|
224151
224262
|
}
|
|
@@ -224415,6 +224526,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
224415
224526
|
await refreshFromCamera();
|
|
224416
224527
|
}
|
|
224417
224528
|
};
|
|
224529
|
+
this.registerCapWarmer(CAP_NAME, refreshFromCamera);
|
|
224418
224530
|
this.ctx.registerNativeCap(ptzAutotrackCapability, provider);
|
|
224419
224531
|
this.ctx.logger.info("Reolink ptz-autotrack cap registered", { tags: { deviceId: this.id } });
|
|
224420
224532
|
}
|
|
@@ -224422,148 +224534,154 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
224422
224534
|
if (this.ptzRegistered) return;
|
|
224423
224535
|
if (!this.getProbeFlags().hasPtz) return;
|
|
224424
224536
|
this.ptzRegistered = true;
|
|
224425
|
-
|
|
224426
|
-
|
|
224427
|
-
|
|
224428
|
-
|
|
224429
|
-
|
|
224430
|
-
|
|
224431
|
-
|
|
224432
|
-
|
|
224433
|
-
|
|
224434
|
-
|
|
224435
|
-
|
|
224436
|
-
|
|
224437
|
-
const channel = this.getChannel();
|
|
224438
|
-
try {
|
|
224439
|
-
await api.ptz(channel, {
|
|
224440
|
-
action: "stop",
|
|
224441
|
-
command: "Up"
|
|
224442
|
-
});
|
|
224443
|
-
} catch {}
|
|
224444
|
-
},
|
|
224445
|
-
getPresets: async ({ deviceId }) => {
|
|
224446
|
-
if (deviceId !== this.id) return [];
|
|
224447
|
-
try {
|
|
224537
|
+
{
|
|
224538
|
+
const ptzProvider = {
|
|
224539
|
+
move: async ({ deviceId, pan, tilt, zoom, speed }) => {
|
|
224540
|
+
if (deviceId !== this.id) return;
|
|
224541
|
+
await this.runPtz(pan, tilt, zoom, speed, false);
|
|
224542
|
+
},
|
|
224543
|
+
continuousMove: async ({ deviceId, pan, tilt, zoom, speed }) => {
|
|
224544
|
+
if (deviceId !== this.id) return;
|
|
224545
|
+
await this.runPtz(pan, tilt, zoom, speed, true);
|
|
224546
|
+
},
|
|
224547
|
+
stop: async ({ deviceId }) => {
|
|
224548
|
+
if (deviceId !== this.id) return;
|
|
224448
224549
|
const api = await this.ensureApi();
|
|
224449
224550
|
const channel = this.getChannel();
|
|
224450
|
-
|
|
224451
|
-
|
|
224452
|
-
|
|
224453
|
-
|
|
224454
|
-
|
|
224455
|
-
|
|
224456
|
-
}
|
|
224457
|
-
|
|
224458
|
-
|
|
224459
|
-
|
|
224460
|
-
|
|
224461
|
-
|
|
224462
|
-
|
|
224463
|
-
|
|
224464
|
-
|
|
224465
|
-
|
|
224466
|
-
|
|
224467
|
-
|
|
224468
|
-
const api = await this.ensureApi();
|
|
224469
|
-
const channel = this.getChannel();
|
|
224470
|
-
const id = Number(presetId);
|
|
224471
|
-
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224472
|
-
await api.setPtzPreset(channel, id, name);
|
|
224473
|
-
},
|
|
224474
|
-
deletePreset: async ({ deviceId, presetId }) => {
|
|
224475
|
-
if (deviceId !== this.id) return;
|
|
224476
|
-
const api = await this.ensureApi();
|
|
224477
|
-
const channel = this.getChannel();
|
|
224478
|
-
const id = Number(presetId);
|
|
224479
|
-
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224480
|
-
await api.deletePtzPreset(channel, id);
|
|
224481
|
-
},
|
|
224482
|
-
getOptions: async ({ deviceId }) => {
|
|
224483
|
-
if (deviceId !== this.id) return {
|
|
224484
|
-
hasPan: false,
|
|
224485
|
-
hasTilt: false,
|
|
224486
|
-
hasZoom: false,
|
|
224487
|
-
supportsPresets: false,
|
|
224488
|
-
hasAutofocus: false
|
|
224489
|
-
};
|
|
224490
|
-
const hasAutofocus = this.config.get("deviceCache")?.autoFocusSnapshot?.supported === true;
|
|
224491
|
-
return this.resolveCapOptions({
|
|
224492
|
-
capName: "ptz",
|
|
224493
|
-
schema: PtzOptionsSchema,
|
|
224494
|
-
probe: async () => {
|
|
224495
|
-
const { capabilities } = await (await this.ensureApi()).getDeviceCapabilities(this.getChannel());
|
|
224496
|
-
return {
|
|
224497
|
-
hasPan: capabilities.hasPan,
|
|
224498
|
-
hasTilt: capabilities.hasTilt,
|
|
224499
|
-
hasZoom: capabilities.hasZoom,
|
|
224500
|
-
supportsPresets: capabilities.hasPresets,
|
|
224501
|
-
hasAutofocus
|
|
224502
|
-
};
|
|
224503
|
-
},
|
|
224504
|
-
fallback: () => {
|
|
224505
|
-
const hasPtz = this.getProbeFlags().hasPtz === true;
|
|
224506
|
-
return {
|
|
224507
|
-
hasPan: hasPtz,
|
|
224508
|
-
hasTilt: hasPtz,
|
|
224509
|
-
hasZoom: hasPtz,
|
|
224510
|
-
supportsPresets: hasPtz,
|
|
224511
|
-
hasAutofocus
|
|
224512
|
-
};
|
|
224551
|
+
try {
|
|
224552
|
+
await api.ptz(channel, {
|
|
224553
|
+
action: "stop",
|
|
224554
|
+
command: "Up"
|
|
224555
|
+
});
|
|
224556
|
+
} catch {}
|
|
224557
|
+
},
|
|
224558
|
+
getPresets: async ({ deviceId }) => {
|
|
224559
|
+
if (deviceId !== this.id) return [];
|
|
224560
|
+
try {
|
|
224561
|
+
const api = await this.ensureApi();
|
|
224562
|
+
const channel = this.getChannel();
|
|
224563
|
+
return (await api.getPtzPresets(channel)).map((p) => ({
|
|
224564
|
+
id: String(p.id),
|
|
224565
|
+
name: p.name
|
|
224566
|
+
}));
|
|
224567
|
+
} catch {
|
|
224568
|
+
return [];
|
|
224513
224569
|
}
|
|
224514
|
-
}
|
|
224515
|
-
|
|
224516
|
-
|
|
224517
|
-
if (deviceId !== this.id) return;
|
|
224518
|
-
try {
|
|
224570
|
+
},
|
|
224571
|
+
goToPreset: async ({ deviceId, presetId }) => {
|
|
224572
|
+
if (deviceId !== this.id) return;
|
|
224519
224573
|
const api = await this.ensureApi();
|
|
224520
224574
|
const channel = this.getChannel();
|
|
224521
|
-
|
|
224522
|
-
|
|
224523
|
-
|
|
224524
|
-
|
|
224525
|
-
|
|
224526
|
-
|
|
224527
|
-
|
|
224528
|
-
|
|
224529
|
-
|
|
224530
|
-
|
|
224531
|
-
|
|
224532
|
-
|
|
224533
|
-
|
|
224534
|
-
|
|
224535
|
-
|
|
224536
|
-
|
|
224537
|
-
|
|
224538
|
-
|
|
224539
|
-
|
|
224540
|
-
|
|
224541
|
-
|
|
224542
|
-
|
|
224543
|
-
|
|
224544
|
-
|
|
224545
|
-
|
|
224546
|
-
|
|
224547
|
-
|
|
224548
|
-
|
|
224549
|
-
|
|
224550
|
-
|
|
224551
|
-
|
|
224552
|
-
|
|
224553
|
-
|
|
224554
|
-
|
|
224555
|
-
|
|
224556
|
-
|
|
224557
|
-
|
|
224558
|
-
|
|
224559
|
-
|
|
224560
|
-
|
|
224561
|
-
|
|
224575
|
+
const id = Number(presetId);
|
|
224576
|
+
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224577
|
+
await api.moveToPtzPreset(channel, id);
|
|
224578
|
+
},
|
|
224579
|
+
savePreset: async ({ deviceId, presetId, name }) => {
|
|
224580
|
+
if (deviceId !== this.id) return;
|
|
224581
|
+
const api = await this.ensureApi();
|
|
224582
|
+
const channel = this.getChannel();
|
|
224583
|
+
const id = Number(presetId);
|
|
224584
|
+
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224585
|
+
await api.setPtzPreset(channel, id, name);
|
|
224586
|
+
},
|
|
224587
|
+
deletePreset: async ({ deviceId, presetId }) => {
|
|
224588
|
+
if (deviceId !== this.id) return;
|
|
224589
|
+
const api = await this.ensureApi();
|
|
224590
|
+
const channel = this.getChannel();
|
|
224591
|
+
const id = Number(presetId);
|
|
224592
|
+
if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`);
|
|
224593
|
+
await api.deletePtzPreset(channel, id);
|
|
224594
|
+
},
|
|
224595
|
+
getOptions: async ({ deviceId }) => {
|
|
224596
|
+
if (deviceId !== this.id) return {
|
|
224597
|
+
hasPan: false,
|
|
224598
|
+
hasTilt: false,
|
|
224599
|
+
hasZoom: false,
|
|
224600
|
+
supportsPresets: false,
|
|
224601
|
+
hasAutofocus: false
|
|
224602
|
+
};
|
|
224603
|
+
const hasAutofocus = this.config.get("deviceCache")?.autoFocusSnapshot?.supported === true;
|
|
224604
|
+
return this.resolveCapOptions({
|
|
224605
|
+
capName: "ptz",
|
|
224606
|
+
schema: PtzOptionsSchema,
|
|
224607
|
+
probe: async () => {
|
|
224608
|
+
const { capabilities } = await (await this.ensureApi()).getDeviceCapabilities(this.getChannel());
|
|
224609
|
+
return {
|
|
224610
|
+
hasPan: capabilities.hasPan,
|
|
224611
|
+
hasTilt: capabilities.hasTilt,
|
|
224612
|
+
hasZoom: capabilities.hasZoom,
|
|
224613
|
+
supportsPresets: capabilities.hasPresets,
|
|
224614
|
+
hasAutofocus
|
|
224615
|
+
};
|
|
224616
|
+
},
|
|
224617
|
+
fallback: () => {
|
|
224618
|
+
const hasPtz = this.getProbeFlags().hasPtz === true;
|
|
224619
|
+
return {
|
|
224620
|
+
hasPan: hasPtz,
|
|
224621
|
+
hasTilt: hasPtz,
|
|
224622
|
+
hasZoom: hasPtz,
|
|
224623
|
+
supportsPresets: hasPtz,
|
|
224624
|
+
hasAutofocus
|
|
224625
|
+
};
|
|
224562
224626
|
}
|
|
224563
|
-
}
|
|
224564
|
-
}
|
|
224565
|
-
|
|
224566
|
-
|
|
224627
|
+
});
|
|
224628
|
+
},
|
|
224629
|
+
goHome: async ({ deviceId }) => {
|
|
224630
|
+
if (deviceId !== this.id) return;
|
|
224631
|
+
try {
|
|
224632
|
+
const api = await this.ensureApi();
|
|
224633
|
+
const channel = this.getChannel();
|
|
224634
|
+
await api.moveToPtzPreset(channel, 0);
|
|
224635
|
+
} catch {}
|
|
224636
|
+
},
|
|
224637
|
+
getPosition: async ({ deviceId }) => {
|
|
224638
|
+
if (deviceId !== this.id) return {
|
|
224639
|
+
pan: 0,
|
|
224640
|
+
tilt: 0,
|
|
224641
|
+
zoom: 0
|
|
224642
|
+
};
|
|
224643
|
+
return {
|
|
224644
|
+
pan: 0,
|
|
224645
|
+
tilt: 0,
|
|
224646
|
+
zoom: 0
|
|
224647
|
+
};
|
|
224648
|
+
},
|
|
224649
|
+
getStatus: async ({ deviceId }) => {
|
|
224650
|
+
if (deviceId !== this.id) return {
|
|
224651
|
+
pan: 0,
|
|
224652
|
+
tilt: 0,
|
|
224653
|
+
zoom: 0,
|
|
224654
|
+
autofocus: false
|
|
224655
|
+
};
|
|
224656
|
+
return {
|
|
224657
|
+
pan: 0,
|
|
224658
|
+
tilt: 0,
|
|
224659
|
+
zoom: 0,
|
|
224660
|
+
autofocus: (this.config.get("deviceCache")?.autoFocusSnapshot)?.enabled === true
|
|
224661
|
+
};
|
|
224662
|
+
},
|
|
224663
|
+
setAutofocus: async ({ deviceId, enabled }) => {
|
|
224664
|
+
if (deviceId !== this.id) return;
|
|
224665
|
+
const api = await this.ensureApi();
|
|
224666
|
+
const channel = this.getChannel();
|
|
224667
|
+
await api.setAutoFocus(channel, enabled ? 0 : 1);
|
|
224668
|
+
try {
|
|
224669
|
+
const a = (await api.getAutoFocus(channel, { timeoutMs: 1500 }))?.body?.AutoFocus;
|
|
224670
|
+
if (a) await this.config.setAll({ deviceCache: {
|
|
224671
|
+
...this.config.get("deviceCache"),
|
|
224672
|
+
autoFocusSnapshot: {
|
|
224673
|
+
enabled: typeof a.disable === "number" ? a.disable === 0 : null,
|
|
224674
|
+
supported: true
|
|
224675
|
+
}
|
|
224676
|
+
} });
|
|
224677
|
+
} catch {}
|
|
224678
|
+
}
|
|
224679
|
+
};
|
|
224680
|
+
this.registerCapWarmer("ptz", async () => {
|
|
224681
|
+
await ptzProvider.getOptions({ deviceId: this.id });
|
|
224682
|
+
});
|
|
224683
|
+
this.ctx.registerNativeCap(ptzCapability, ptzProvider);
|
|
224684
|
+
}
|
|
224567
224685
|
}
|
|
224568
224686
|
/**
|
|
224569
224687
|
* Translate normalized (-1..1) pan/tilt/zoom to one or more discrete
|
|
@@ -225266,7 +225384,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
225266
225384
|
if (this.batteryUpdateInFlight) return this.batteryUpdateInFlight;
|
|
225267
225385
|
const cycle = (async () => {
|
|
225268
225386
|
try {
|
|
225269
|
-
if (
|
|
225387
|
+
if (this.isBattery && this.sleeping) {
|
|
225270
225388
|
this.ctx.logger.debug("battery-update: cam sleeping — skipping cycle without connecting", { tags: { deviceId: this.id } });
|
|
225271
225389
|
return;
|
|
225272
225390
|
}
|
|
@@ -225368,6 +225486,8 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
225368
225486
|
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
225369
225487
|
});
|
|
225370
225488
|
}
|
|
225489
|
+
await this.warmCapSlicesOnWake();
|
|
225490
|
+
await this.warmSnapshotOnWake();
|
|
225371
225491
|
await this.alignAuxDevicesState("wake");
|
|
225372
225492
|
await this.refreshParentSettingsSnapshot();
|
|
225373
225493
|
}
|