@apocaliss92/scrypted-reolink-native 0.5.31 → 0.5.33

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/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apocaliss92/scrypted-reolink-native",
3
- "version": "0.5.31",
3
+ "version": "0.5.33",
4
4
  "description": "Use any reolink camera with Scrypted, even older/unsupported models without HTTP protocol support",
5
5
  "author": "@apocaliss92",
6
6
  "license": "Apache",
package/src/camera.ts CHANGED
@@ -2838,6 +2838,17 @@ export class ReolinkCamera
2838
2838
  * `takePicture()` on the back of the motion get the trigger frame
2839
2839
  * instead of the previous cached one. Fire-and-forget; per-camera
2840
2840
  * debounce so an event burst doesn't trigger overlapping fetches.
2841
+ *
2842
+ * Critical for Reolink battery cams: must call `client.wakeUp()`
2843
+ * before fetching the snapshot. Reolink firmwares can put the cam
2844
+ * back to sleep within a few seconds of the email/motion push, so
2845
+ * by the time `getSnapshot` (cmd 109) hits the device it may
2846
+ * already be unreachable — the snapshot push response never
2847
+ * arrives, the lib times out, and the cmdId=109 hang knocks the
2848
+ * UDP session into the reconnect path which itself wakes the cam.
2849
+ * `wakeUp` explicitly tells the cam to stay awake long enough to
2850
+ * service the snapshot — same pattern as the regular `takePicture`
2851
+ * battery path (see line ~2945).
2841
2852
  */
2842
2853
  private lastMotionSnapshotAtMs = 0;
2843
2854
  private motionSnapshotInFlight = false;
@@ -2849,6 +2860,13 @@ export class ReolinkCamera
2849
2860
  const logger = this.getBaichuanLogger();
2850
2861
  try {
2851
2862
  const client = await this.ensureClient();
2863
+ if (this.isBattery) {
2864
+ // Keep the cam awake for the snapshot fetch — without this the
2865
+ // cmdId=109 (snapshot push) frequently times out on battery
2866
+ // cams and we end up triggering a full socket reconnect which
2867
+ // ironically wakes the cam more than the original snapshot.
2868
+ await client.wakeUp();
2869
+ }
2852
2870
  const mo = await this.takePictureInternal(client);
2853
2871
  this.lastPicture = { mo, atMs: timestampMs };
2854
2872
  this.forceNewSnapshot = false;
@@ -2856,8 +2874,13 @@ export class ReolinkCamera
2856
2874
  `Motion snapshot refreshed — next takePicture will serve the trigger frame`,
2857
2875
  );
2858
2876
  } catch (e) {
2859
- logger.warn(
2860
- `Motion snapshot refresh failed: ${e?.message || String(e)}`,
2877
+ // Demote to debug — for battery cams the cam goes back to sleep
2878
+ // aggressively and a snapshot miss is normal: the motion event
2879
+ // itself was already dispatched on the bus, MQTT/HA will reuse
2880
+ // the previous cached frame, no functional impact. Warn would
2881
+ // pollute the device log on every motion in noisy environments.
2882
+ logger.debug?.(
2883
+ `Motion snapshot refresh failed (cam may have slept already): ${e?.message || String(e)}`,
2861
2884
  );
2862
2885
  } finally {
2863
2886
  this.motionSnapshotInFlight = false;
@@ -3921,20 +3944,26 @@ export class ReolinkCamera
3921
3944
  }
3922
3945
  }
3923
3946
  } else if (sleepStatus.state === "awake") {
3924
- // Camera is awake
3947
+ // Camera is awake — but DON'T do any proactive networking
3948
+ // here. Sleep inference can flicker (idle_disconnect FIN
3949
+ // packets, brief housekeeping packets from the cam, etc.),
3950
+ // and reacting to every flicker with `ensureClient + wakeUp
3951
+ // + takePicture + battery + aux state` ends up waking the cam
3952
+ // far more than the original flicker — the reconnect alone
3953
+ // re-logs in, re-subscribes events, and triggers a fresh
3954
+ // sleep/awake cycle. Real motion-triggered wake-ups arrive
3955
+ // via SMTP / native push and have their own snapshot refresh
3956
+ // path (`refreshSnapshotOnMotion`). Periodic battery polling
3957
+ // is owned by `batteryUpdateTimer` (hourly), not by sleep
3958
+ // inference. So here we only flip the local state flag.
3925
3959
  const wasSleeping = this.sleeping;
3926
3960
  if (wasSleeping) {
3927
3961
  this.getBaichuanLogger().log(`Camera is awake`);
3928
3962
  this.sleeping = false;
3929
- const client = await this.ensureClient();
3930
- await client.wakeUp();
3931
- await this.takePictureInternal(client);
3932
- }
3933
-
3934
- if (wasSleeping) {
3935
- this.updateBatteryInfo().catch(() => {});
3936
- this.alignAuxDevicesState().catch(() => {});
3937
3963
  if (this.forceNewSnapshot) {
3964
+ // Honor an explicit refresh requested by some other path
3965
+ // (e.g. user-triggered or capability discovery) — they
3966
+ // set `forceNewSnapshot=true` and we serve it here.
3938
3967
  this.takePicture().catch(() => {});
3939
3968
  }
3940
3969
  }