@apocaliss92/scrypted-reolink-native 0.4.46 → 0.4.48

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.4.46",
3
+ "version": "0.4.48",
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",
@@ -6,17 +6,18 @@ import {
6
6
  SettingValue,
7
7
  } from "@scrypted/sdk";
8
8
  import { StorageSettings } from "@scrypted/sdk/storage-settings";
9
- import type { ChimeCfg } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
10
9
  import type { ReolinkCamera } from "../camera";
11
10
 
11
+ const DEFAULT_SILENT_DURATION_MINUTES = 30;
12
+
12
13
  /**
13
14
  * Chime: enable/disable a paired wireless Reolink Chime receiver.
14
15
  *
15
- * Uses SetDingDongCfg (cmd 487) to enable/disable all event types on the chime,
16
- * matching the approach used by Home Assistant / reolink_aio.
16
+ * Uses SetDingDongSilent (cmd 610) to silence/unsilence the chime for a
17
+ * configurable duration in minutes.
17
18
  *
18
- * turnOn() enables all event types (chime rings on events).
19
- * turnOff() disables all event types (chime stays silent).
19
+ * turnOn() setDingDongSilent(chimeId, 0) un-silence the chime.
20
+ * turnOff() setDingDongSilent(chimeId, seconds) silence the chime for the configured duration.
20
21
  *
21
22
  * The chime ID is auto-synced from getDingDongList during alignAuxDevicesState.
22
23
  */
@@ -36,6 +37,14 @@ export class ReolinkCameraChime
36
37
  defaultValue: -1,
37
38
  readonly: true,
38
39
  },
40
+ silentDurationMinutes: {
41
+ title: "Silent Duration (minutes)",
42
+ description:
43
+ "How long the chime stays silent when turned off, in minutes. " +
44
+ "After this time the chime automatically becomes active again.",
45
+ type: "number",
46
+ defaultValue: DEFAULT_SILENT_DURATION_MINUTES,
47
+ },
39
48
  });
40
49
 
41
50
  constructor(
@@ -57,6 +66,12 @@ export class ReolinkCameraChime
57
66
  return this.storageSettings.values.wirelessChimeId ?? -1;
58
67
  }
59
68
 
69
+ /** Silent duration in seconds derived from the user-facing minutes setting. */
70
+ private get silentDurationSeconds(): number {
71
+ const minutes = this.storageSettings.values.silentDurationMinutes ?? DEFAULT_SILENT_DURATION_MINUTES;
72
+ return Math.max(1, Math.round(minutes)) * 60;
73
+ }
74
+
60
75
  /**
61
76
  * Refresh chime ID from device when -1.
62
77
  * Python lib: GetDingDongList uses deviceId; GetDingDongCfg uses ringId (skips if < 0).
@@ -82,25 +97,18 @@ export class ReolinkCameraChime
82
97
  return false;
83
98
  }
84
99
 
85
- private async getChimeCfg(): Promise<ChimeCfg | undefined> {
86
- const channel = this.camera.storageSettings.values.rtspChannel;
87
- const chimeId = this.wirelessChimeId;
88
- if (chimeId < 0) return undefined;
89
- const api = await this.camera.ensureClient();
90
- const configs = await api.getDingDongCfg(channel);
91
- return configs.find(c => c.id === chimeId);
92
- }
93
-
94
100
  /**
95
- * Determine if the chime is active by checking if any event type is enabled.
101
+ * Determine if the chime is active by querying the silent-mode state (cmd 609).
102
+ * active === true means the chime is NOT silenced (on).
96
103
  */
97
104
  async syncStateFromDevice(): Promise<boolean | undefined> {
98
105
  await this.ensureChimeId();
99
- const cfg = await this.getChimeCfg();
100
- if (!cfg) return undefined;
101
- const eventTypes = Object.values(cfg.type);
102
- if (eventTypes.length === 0) return undefined;
103
- return eventTypes.some(e => e.valid === 1);
106
+ const chimeId = this.wirelessChimeId;
107
+ if (chimeId < 0) return undefined;
108
+ const channel = this.camera.storageSettings.values.rtspChannel;
109
+ const api = await this.camera.ensureClient();
110
+ const state = await api.getDingDongSilent(chimeId, channel);
111
+ return state.active;
104
112
  }
105
113
 
106
114
  async turnOn(): Promise<void> {
@@ -115,22 +123,15 @@ export class ReolinkCameraChime
115
123
  }
116
124
  const channel = this.camera.storageSettings.values.rtspChannel;
117
125
  const chimeId = this.wirelessChimeId;
118
- this.logger.log(`Chime: enable all events (device=${this.nativeId}, chimeId=${chimeId})`);
119
- const cfg = await this.getChimeCfg();
120
- if (!cfg) throw new Error(`Chime config not found for chimeId=${chimeId}`);
126
+ this.logger.log(`Chime: un-silence (device=${this.nativeId}, chimeId=${chimeId})`);
121
127
  const api = await this.camera.ensureClient();
122
- for (const [eventType, alarmCfg] of Object.entries(cfg.type)) {
123
- if (alarmCfg.valid !== 1) {
124
- const musicId = alarmCfg.musicId || 1;
125
- await api.setDingDongCfg(chimeId, eventType, 1, musicId, channel);
126
- }
127
- }
128
+ await api.setDingDongSilent(chimeId, 0, channel);
128
129
  });
129
130
  this.on = true;
130
- this.logger.log(`Chime: enable ok (device=${this.nativeId})`);
131
+ this.logger.log(`Chime: un-silence ok (device=${this.nativeId})`);
131
132
  } catch (e: any) {
132
133
  this.logger.error(
133
- `Chime: enable failed (device=${this.nativeId})`,
134
+ `Chime: un-silence failed (device=${this.nativeId})`,
134
135
  e?.message || String(e),
135
136
  );
136
137
  throw e;
@@ -149,21 +150,18 @@ export class ReolinkCameraChime
149
150
  }
150
151
  const channel = this.camera.storageSettings.values.rtspChannel;
151
152
  const chimeId = this.wirelessChimeId;
152
- this.logger.log(`Chime: disable all events (device=${this.nativeId}, chimeId=${chimeId})`);
153
- const cfg = await this.getChimeCfg();
154
- if (!cfg) throw new Error(`Chime config not found for chimeId=${chimeId}`);
153
+ const seconds = this.silentDurationSeconds;
154
+ this.logger.log(
155
+ `Chime: silence for ${seconds}s (device=${this.nativeId}, chimeId=${chimeId})`,
156
+ );
155
157
  const api = await this.camera.ensureClient();
156
- for (const [eventType, alarmCfg] of Object.entries(cfg.type)) {
157
- if (alarmCfg.valid !== 0) {
158
- await api.setDingDongCfg(chimeId, eventType, 0, alarmCfg.musicId, channel);
159
- }
160
- }
158
+ await api.setDingDongSilent(chimeId, seconds, channel);
161
159
  });
162
160
  this.on = false;
163
- this.logger.log(`Chime: disable ok (device=${this.nativeId})`);
161
+ this.logger.log(`Chime: silence ok (device=${this.nativeId})`);
164
162
  } catch (e: any) {
165
163
  this.logger.error(
166
- `Chime: disable failed (device=${this.nativeId})`,
164
+ `Chime: silence failed (device=${this.nativeId})`,
167
165
  e?.message || String(e),
168
166
  );
169
167
  throw e;
package/src/camera.ts CHANGED
@@ -2838,7 +2838,7 @@ export class ReolinkCamera
2838
2838
  }
2839
2839
  }
2840
2840
 
2841
- // Align wireless chime state via getDingDongCfg (cmd 486)
2841
+ // Align wireless chime state via getDingDongSilent (cmd 609)
2842
2842
  if (hasWirelessChime && this.chime) {
2843
2843
  if (isInCooldown(this.auxDeviceCooldowns.chime)) {
2844
2844
  logger.log(`[alignAuxDevicesState] Skipping chime (in cooldown)`);
@@ -67,7 +67,15 @@ export class ReolinkNativeIntercom
67
67
  if (!match) return;
68
68
 
69
69
  // Only auto-enable for cameras provided by our own plugin
70
- if (!this.plugin?.camerasMap?.has(device.id)) return;
70
+ const camera = this.plugin?.camerasMap?.get(device.id);
71
+ if (!camera) return;
72
+
73
+ // Only auto-enable if the camera actually supports intercom.
74
+ // If capabilities aren't loaded yet, skip — we'll be called again
75
+ // when the device descriptor updates after capability detection.
76
+ const caps = camera.cachedCapabilities;
77
+ if (!caps) return;
78
+ if (!caps.hasIntercom) return;
71
79
 
72
80
  this.console.log(`Auto-enabling intercom mixin for ${device.name}`);
73
81
  const mixins = (device.mixins || []).slice();