@apocaliss92/scrypted-reolink-native 0.5.22 → 0.5.24

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.22",
3
+ "version": "0.5.24",
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",
@@ -44,7 +44,7 @@
44
44
  ]
45
45
  },
46
46
  "dependencies": {
47
- "@apocaliss92/nodelink-js": "^0.4.29",
47
+ "@apocaliss92/nodelink-js": "^0.4.31",
48
48
  "@scrypted/common": "file:../../scrypted/common",
49
49
  "@scrypted/rtsp": "file:../../scrypted/plugins/rtsp",
50
50
  "@scrypted/sdk": "^0.3.118"
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  BaichuanClientOptions,
3
+ EmailPushEvent,
3
4
  ReolinkBaichuanApi,
4
5
  ReolinkSimpleEvent,
5
6
  } from "@apocaliss92/nodelink-js" with { "resolution-mode": "import" };
@@ -32,6 +33,16 @@ export interface BaichuanConnectionCallbacks {
32
33
  * leave undefined on NVR children where email-push isn't meaningful.
33
34
  */
34
35
  emailPushCameraId?: () => string;
36
+ /**
37
+ * Optional. When the email-push event carries an image attachment
38
+ * (typical for `attachmentType=picture` on motion), the camera
39
+ * receives the full event so it can republish the snapshot —
40
+ * usually by updating its `lastPicture` cache so subsequent
41
+ * `takePicture()` calls return the fresh thumbnail without waking
42
+ * the camera. Invoked AFTER the simple-event dispatch so any motion
43
+ * listener has already fired.
44
+ */
45
+ onEmailPushEvent?: (event: EmailPushEvent) => void;
35
46
  }
36
47
 
37
48
  /**
@@ -908,6 +919,9 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
908
919
  this.currentEmailPushOff = api.subscribeEmailPushEvents({
909
920
  cameraId: callbacks.emailPushCameraId(),
910
921
  channel: 0,
922
+ ...(callbacks.onEmailPushEvent
923
+ ? { onEvent: callbacks.onEmailPushEvent }
924
+ : {}),
911
925
  });
912
926
  logger.debug("Bridged email-push bus to onSimpleEvent");
913
927
  } catch (e) {
package/src/camera.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  BatteryInfo,
3
3
  DeviceCapabilities,
4
+ EmailPushEvent,
4
5
  NativeVideoStreamVariant,
5
6
  PtzCommand,
6
7
  PtzPreset,
@@ -1614,6 +1615,14 @@ export class ReolinkCamera
1614
1615
  // (singleton under the provider) maps `cam-<nativeId>@<domain>`
1615
1616
  // RCPT addresses back to this `nativeId`.
1616
1617
  emailPushCameraId: () => this.nativeId ?? "",
1618
+ // When the e-mail carries the snapshot Reolink sends with
1619
+ // `attachmentType=picture`, hand it to the camera's `lastPicture`
1620
+ // cache. Downstream consumers (Snapshot mixin → MQTT image
1621
+ // entity, Advanced Notifier, HA discovery) call `takePicture()`
1622
+ // on the back of the motion event; for battery cams that returns
1623
+ // the cached value, so dropping the fresh snapshot in here makes
1624
+ // the trigger frame surface in MQTT/HA without waking the camera.
1625
+ onEmailPushEvent: (event) => this.handleEmailPushSnapshot(event),
1617
1626
  };
1618
1627
  }
1619
1628
 
@@ -2734,6 +2743,38 @@ export class ReolinkCamera
2734
2743
  }
2735
2744
  }
2736
2745
 
2746
+ /**
2747
+ * Called from `baichuan-base.subscribeToEvents` when a per-camera
2748
+ * email-push event lands on the bus. We don't use the e-mail's image
2749
+ * attachment — Reolink firmwares vary on quality/encoding and we'd
2750
+ * be reimplementing the snapshot pipeline. Instead we leverage the
2751
+ * fact that the camera is *guaranteed awake* right now (it just
2752
+ * pushed an e-mail) and call the live Baichuan snapshot API: it
2753
+ * returns a fresh frame in milliseconds without forcing an extra
2754
+ * wake-up cycle. The result is stashed in `lastPicture` so the
2755
+ * Snapshot mixin → MQTT image entity (and any other consumer that
2756
+ * calls `takePicture()` on the back of the motion event) serves the
2757
+ * trigger frame instead of the previous cached one.
2758
+ */
2759
+ private async handleEmailPushSnapshot(
2760
+ event: EmailPushEvent,
2761
+ ): Promise<void> {
2762
+ const logger = this.getBaichuanLogger();
2763
+ try {
2764
+ const client = await this.ensureClient();
2765
+ const mo = await this.takePictureInternal(client);
2766
+ this.lastPicture = { mo, atMs: event.receivedAtMs };
2767
+ this.forceNewSnapshot = false;
2768
+ logger.log(
2769
+ `E-mail Push snapshot fetched live (type=${event.inferredType}) — next takePicture will serve it`,
2770
+ );
2771
+ } catch (e) {
2772
+ logger.warn(
2773
+ `E-mail Push: live snapshot fetch failed: ${e?.message || String(e)}`,
2774
+ );
2775
+ }
2776
+ }
2777
+
2737
2778
  async takePictureInternal(client: ReolinkBaichuanApi) {
2738
2779
  const { rtspChannel, variantType } = this.storageSettings.values;
2739
2780
  const logger = this.getBaichuanLogger();
@@ -214,7 +214,13 @@ export class EmailPushServerDevice
214
214
  managerHost,
215
215
  managerPort: cfg.port,
216
216
  domain: cfg.domain,
217
- recipientLocalPart: nativeId,
217
+ // The lib's `resolveCameraIdFromRecipient` matches `^cam-(.+)$`
218
+ // and feeds the captured group to `cameraResolver`. We MUST send
219
+ // the recipient with the `cam-` prefix so the camera-side
220
+ // MAIL FROM produces `cam-<nativeId>@<domain>`; without it every
221
+ // delivery is rejected with `550 Unknown recipient` and motion
222
+ // events never reach Scrypted.
223
+ recipientLocalPart: `cam-${nativeId}`,
218
224
  sendNickname: cam.name ?? nativeId,
219
225
  ...(cfg.requireAuth
220
226
  ? {
@@ -536,7 +542,9 @@ export class EmailPushServerDevice
536
542
  managerHost,
537
543
  managerPort: cfg.port,
538
544
  domain: cfg.domain,
539
- recipientLocalPart: nativeId,
545
+ // See `getManagerSetupParamsForCamera` for why the `cam-`
546
+ // prefix is mandatory.
547
+ recipientLocalPart: `cam-${nativeId}`,
540
548
  sendNickname: cam.name ?? nativeId,
541
549
  attachmentType: "picture",
542
550
  triggerTypes: ["MD", "people", "vehicle"],