@apocaliss92/scrypted-reolink-native 0.4.32 → 0.4.34

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.32",
3
+ "version": "0.4.34",
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
@@ -16,7 +16,6 @@ import sdk, {
16
16
  ChargeState,
17
17
  Device,
18
18
  DeviceProvider,
19
- Intercom,
20
19
  MediaObject,
21
20
  MediaStreamUrl,
22
21
  ObjectDetectionTypes,
@@ -65,7 +64,6 @@ import {
65
64
  getApiRelevantDebugLogs,
66
65
  getDebugLogChoices,
67
66
  } from "./debug-options";
68
- import { ReolinkBaichuanIntercom } from "./intercom";
69
67
  import ReolinkNativePlugin from "./main";
70
68
  import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
71
69
  import { ReolinkNativeNvrDevice } from "./nvr";
@@ -115,7 +113,6 @@ export class ReolinkCamera
115
113
  PanTiltZoom,
116
114
  VideoTextOverlays,
117
115
  BinarySensor,
118
- Intercom,
119
116
  Reboot,
120
117
  VideoClips
121
118
  {
@@ -280,30 +277,6 @@ export class ReolinkCamera
280
277
  json: true,
281
278
  defaultValue: [],
282
279
  },
283
- intercomBlocksPerPayload: {
284
- group: "Intercom",
285
- title: "Blocks Per Payload",
286
- description:
287
- "Lower reduces latency (more packets). Typical: 1-4. Requires restarting talk session to take effect.",
288
- type: "number",
289
- defaultValue: 1,
290
- },
291
- intercomMaxBacklogMs: {
292
- group: "Intercom",
293
- title: "Max Backlog (ms)",
294
- description:
295
- "Maximum PCM backlog before dropping old audio to cap latency. Higher improves stability on slow systems but increases latency. Typical: 80-250. Requires restarting talk session to take effect.",
296
- type: "number",
297
- defaultValue: 120,
298
- },
299
- intercomGain: {
300
- group: "Intercom",
301
- title: "Gain",
302
- description:
303
- "Output gain multiplier applied before encoding. 1.0 = normal, 2.0 ≈ +6dB, 0.5 ≈ -6dB. Requires restarting talk session to take effect.",
304
- type: "number",
305
- defaultValue: 1.0,
306
- },
307
280
  // PTZ Presets
308
281
  presets: {
309
282
  group: "PTZ",
@@ -459,24 +432,6 @@ export class ReolinkCamera
459
432
  defaultValue: 60,
460
433
  hide: true,
461
434
  },
462
- lowThresholdBatteryRecording: {
463
- title: "Low Threshold Battery Recording (%)",
464
- subgroup: "Recording",
465
- description:
466
- "Battery level threshold below which recording is disabled (default: 15%).",
467
- type: "number",
468
- defaultValue: 15,
469
- hide: true,
470
- },
471
- highThresholdBatteryRecording: {
472
- title: "High Threshold Battery Recording (%)",
473
- subgroup: "Recording",
474
- description:
475
- "Battery level threshold above which recording is enabled (default: 35%).",
476
- type: "number",
477
- defaultValue: 35,
478
- hide: true,
479
- },
480
435
  diagnosticsOutputPath: {
481
436
  title: "Diagnostics Output Path",
482
437
  subgroup: "Diagnostics",
@@ -696,7 +651,6 @@ export class ReolinkCamera
696
651
  classes: string[] = [];
697
652
  presets: PtzPreset[] = [];
698
653
  streamManager?: StreamManager;
699
- intercom?: ReolinkBaichuanIntercom;
700
654
 
701
655
  motionSiren?: ReolinkCameraMotionSiren;
702
656
  siren?: ReolinkCameraSiren;
@@ -2570,23 +2524,6 @@ export class ReolinkCamera
2570
2524
  return [];
2571
2525
  }
2572
2526
 
2573
- // Intercom interface methods
2574
- async startIntercom(media: MediaObject): Promise<void> {
2575
- if (this.intercom) {
2576
- await this.intercom.start(media);
2577
- } else {
2578
- throw new Error("Intercom not initialized");
2579
- }
2580
- }
2581
-
2582
- async stopIntercom(): Promise<void> {
2583
- if (this.intercom) {
2584
- return await this.intercom.stop();
2585
- } else {
2586
- throw new Error("Intercom not initialized");
2587
- }
2588
- }
2589
-
2590
2527
  async updateDeviceInfo(): Promise<void> {
2591
2528
  const logger = this.getBaichuanLogger();
2592
2529
 
@@ -3301,10 +3238,6 @@ export class ReolinkCamera
3301
3238
 
3302
3239
  this.storageSettings.settings.batteryUpdateIntervalMinutes.hide =
3303
3240
  !this.isBattery;
3304
- this.storageSettings.settings.lowThresholdBatteryRecording.hide =
3305
- !this.isBattery;
3306
- this.storageSettings.settings.highThresholdBatteryRecording.hide =
3307
- !this.isBattery;
3308
3241
 
3309
3242
  // Show PIP settings only for multifocal devices
3310
3243
  this.storageSettings.settings.pipPosition.hide = !this.isMultiFocal;
@@ -3362,11 +3295,7 @@ export class ReolinkCamera
3362
3295
  );
3363
3296
  }
3364
3297
 
3365
- const { hasIntercom, hasPtz } = await this.getAbilities();
3366
-
3367
- if (hasIntercom) {
3368
- this.intercom = new ReolinkBaichuanIntercom(this);
3369
- }
3298
+ const { hasPtz } = await this.getAbilities();
3370
3299
 
3371
3300
  if (hasPtz && !this.multiFocalDevice) {
3372
3301
  const choices = (this.presets || []).map(
@@ -3532,44 +3461,6 @@ export class ReolinkCamera
3532
3461
  }
3533
3462
  }
3534
3463
 
3535
- async checkRecordingAction(newBatteryLevel: number) {
3536
- const nvrDeviceId = this.plugin.nvrDeviceId;
3537
- if (nvrDeviceId && this.mixins.includes(nvrDeviceId)) {
3538
- const logger = this.getBaichuanLogger();
3539
-
3540
- const settings = await this.thisDevice.getSettings();
3541
- const isPrivacyEnabled =
3542
- settings.find((s) => s.key === "prebuffer:privacyMode")?.value ||
3543
- settings.find((s) => s.key === "recording:privacyMode")?.value ||
3544
- settings.find((s) => s.key === "snapshot:privacyMode")?.value;
3545
- const { lowThresholdBatteryRecording, highThresholdBatteryRecording } =
3546
- this.storageSettings.values;
3547
-
3548
- if (!isPrivacyEnabled && newBatteryLevel < lowThresholdBatteryRecording) {
3549
- logger.log(
3550
- `Battery level is below low threshold (${newBatteryLevel}% < ${lowThresholdBatteryRecording}%), enabling privacy mode`,
3551
- );
3552
- await Promise.all([
3553
- this.thisDevice.putSetting("prebuffer:privacyMode", true),
3554
- this.thisDevice.putSetting("recording:privacyMode", true),
3555
- this.thisDevice.putSetting("snapshot:privacyMode", true),
3556
- ]);
3557
- } else if (
3558
- isPrivacyEnabled &&
3559
- newBatteryLevel > highThresholdBatteryRecording
3560
- ) {
3561
- logger.log(
3562
- `Battery level is above high threshold (${newBatteryLevel}% > ${highThresholdBatteryRecording}%), disabling privacy mode`,
3563
- );
3564
- await Promise.all([
3565
- this.thisDevice.putSetting("prebuffer:privacyMode", false),
3566
- this.thisDevice.putSetting("recording:privacyMode", false),
3567
- this.thisDevice.putSetting("snapshot:privacyMode", false),
3568
- ]);
3569
- }
3570
- }
3571
- }
3572
-
3573
3464
  async updateBatteryInfo(batteryInfoParent?: BatteryInfo) {
3574
3465
  const api = await this.ensureClient();
3575
3466
  const channel = this.storageSettings.values.rtspChannel;
@@ -3603,8 +3494,6 @@ export class ReolinkCamera
3603
3494
  this.chargeState = newChargeState;
3604
3495
  }
3605
3496
 
3606
- let shouldCheckRecordingAction = true;
3607
-
3608
3497
  // Log only if battery level changed
3609
3498
  if (oldLevel !== batteryInfo.batteryPercent) {
3610
3499
  if (batteryInfo.adapterStatus !== undefined) {
@@ -3625,8 +3514,6 @@ export class ReolinkCamera
3625
3514
  } else {
3626
3515
  logger.log(`Battery level set: ${batteryInfo.batteryPercent}%`);
3627
3516
  }
3628
- } else {
3629
- shouldCheckRecordingAction = false;
3630
3517
  }
3631
3518
 
3632
3519
  // Forward battery/charge state changes to Scrypted (plugin, HomeKit, etc.)
@@ -3641,10 +3528,6 @@ export class ReolinkCamera
3641
3528
  void this.onDeviceEvent(ScryptedInterface.Charger, undefined);
3642
3529
  }
3643
3530
  }
3644
-
3645
- if (shouldCheckRecordingAction) {
3646
- await this.checkRecordingAction(batteryInfo.batteryPercent);
3647
- }
3648
3531
  }
3649
3532
 
3650
3533
  return batteryInfo;
@@ -0,0 +1,302 @@
1
+ import type { ReolinkBaichuanApi } from "@apocaliss92/reolink-baichuan-js" with {
2
+ "resolution-mode": "import",
3
+ };
4
+ import sdk, {
5
+ Intercom,
6
+ MediaObject,
7
+ Setting,
8
+ Settings,
9
+ SettingValue,
10
+ } from "@scrypted/sdk";
11
+ import {
12
+ SettingsMixinDeviceBase,
13
+ SettingsMixinDeviceOptions,
14
+ } from "@scrypted/sdk/settings-mixin";
15
+ import { StorageSettings } from "@scrypted/sdk/storage-settings";
16
+ import type { BaichuanTransport } from "./connect";
17
+ import type { ReolinkNativeIntercom } from "./intercom-provider";
18
+ import { ReolinkBaichuanIntercom, type IntercomHost } from "./intercom";
19
+ import type { ReolinkCamera } from "./camera";
20
+
21
+ export class ReolinkNativeIntercomMixin
22
+ extends SettingsMixinDeviceBase<any>
23
+ implements Intercom, Settings
24
+ {
25
+ private intercomEngine?: ReolinkBaichuanIntercom;
26
+
27
+ storageSettings = new StorageSettings(this, {
28
+ // Connection settings (hidden when attached to an internal Reolink Native camera)
29
+ intercomIpAddress: {
30
+ group: "Connection",
31
+ title: "IP Address",
32
+ description: "Camera IP address for Baichuan connection",
33
+ type: "string",
34
+ },
35
+ intercomUsername: {
36
+ group: "Connection",
37
+ title: "Username",
38
+ type: "string",
39
+ defaultValue: "admin",
40
+ },
41
+ intercomPassword: {
42
+ group: "Connection",
43
+ title: "Password",
44
+ type: "password",
45
+ },
46
+ intercomUid: {
47
+ group: "Connection",
48
+ title: "UID",
49
+ description: "Required for battery/UDP cameras",
50
+ type: "string",
51
+ },
52
+ intercomChannel: {
53
+ group: "Connection",
54
+ title: "Channel",
55
+ description: "RTSP channel number (0-based)",
56
+ type: "number",
57
+ defaultValue: 0,
58
+ },
59
+ intercomTransport: {
60
+ group: "Connection",
61
+ title: "Transport",
62
+ description: "Connection transport protocol",
63
+ type: "string",
64
+ choices: ["tcp", "udp"],
65
+ defaultValue: "tcp",
66
+ },
67
+ // Audio pipeline settings (always visible)
68
+ intercomBlocksPerPayload: {
69
+ group: "Audio",
70
+ title: "Blocks Per Payload",
71
+ description:
72
+ "Lower reduces latency (more packets). Typical: 1-4. Requires restarting talk session to take effect.",
73
+ type: "number",
74
+ defaultValue: 1,
75
+ },
76
+ intercomMaxBacklogMs: {
77
+ group: "Audio",
78
+ title: "Max Backlog (ms)",
79
+ description:
80
+ "Maximum PCM backlog before dropping old audio to cap latency. Higher improves stability on slow systems but increases latency. Typical: 80-250. Requires restarting talk session to take effect.",
81
+ type: "number",
82
+ defaultValue: 120,
83
+ },
84
+ intercomGain: {
85
+ group: "Audio",
86
+ title: "Gain",
87
+ description:
88
+ "Output gain multiplier applied before encoding. 1.0 = normal, 2.0 ≈ +6dB, 0.5 ≈ -6dB. Requires restarting talk session to take effect.",
89
+ type: "number",
90
+ defaultValue: 1.0,
91
+ },
92
+ });
93
+
94
+ constructor(
95
+ options: SettingsMixinDeviceOptions<any>,
96
+ public provider: ReolinkNativeIntercom,
97
+ ) {
98
+ super(options);
99
+ this.provider.currentMixinsMap[this.id] = this;
100
+ }
101
+
102
+ private getInternalCamera(): ReolinkCamera | undefined {
103
+ return this.provider.plugin?.camerasMap?.get(this.id);
104
+ }
105
+
106
+ private isReolinkPluginCamera(): boolean {
107
+ try {
108
+ const device = sdk.systemManager.getDeviceById(this.id);
109
+ return device?.pluginId === "@scrypted/reolink";
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ private async getReolinkPluginCredentials(): Promise<
116
+ { host?: string; username?: string; password?: string } | undefined
117
+ > {
118
+ if (!this.isReolinkPluginCamera()) return undefined;
119
+
120
+ try {
121
+ const settings: Setting[] = await this.mixinDevice.getSettings();
122
+ const map = new Map(
123
+ settings.map((s: Setting) => [s.key, s.value?.toString()]),
124
+ );
125
+
126
+ // Non-NVR cameras use "ip", NVR child cameras use "ipAddress"
127
+ const host = map.get("ip") || map.get("ipAddress");
128
+ const username = map.get("username");
129
+ const password = map.get("password");
130
+
131
+ return { host, username, password };
132
+ } catch {
133
+ return undefined;
134
+ }
135
+ }
136
+
137
+ private buildHost(): IntercomHost {
138
+ const self = this;
139
+ const internalCamera = this.getInternalCamera();
140
+
141
+ if (internalCamera) {
142
+ return {
143
+ get blocksPerPayload() {
144
+ return Math.max(
145
+ 1,
146
+ Math.min(
147
+ 8,
148
+ self.storageSettings.values.intercomBlocksPerPayload ?? 1,
149
+ ),
150
+ );
151
+ },
152
+ get outputGain() {
153
+ const v = Number(self.storageSettings.values.intercomGain);
154
+ return Number.isFinite(v) ? Math.max(0.1, Math.min(10, v)) : 1.0;
155
+ },
156
+ get maxBacklogMs() {
157
+ const v = Number(self.storageSettings.values.intercomMaxBacklogMs);
158
+ return Number.isFinite(v) ? Math.max(20, Math.min(5000, v)) : 120;
159
+ },
160
+ get channel() {
161
+ return internalCamera.storageSettings.values.rtspChannel;
162
+ },
163
+ get isBatteryCamera() {
164
+ return internalCamera.isBattery;
165
+ },
166
+ get deviceId() {
167
+ return internalCamera.nativeId;
168
+ },
169
+ get logger() {
170
+ return internalCamera.getBaichuanLogger();
171
+ },
172
+ ensureApi: () => internalCamera.ensureBaichuanClient(),
173
+ withRetry: (fn) => internalCamera.withBaichuanRetry(fn),
174
+ };
175
+ }
176
+
177
+ // External path: use shared client from plugin registry
178
+ return {
179
+ get blocksPerPayload() {
180
+ return Math.max(
181
+ 1,
182
+ Math.min(
183
+ 8,
184
+ self.storageSettings.values.intercomBlocksPerPayload ?? 1,
185
+ ),
186
+ );
187
+ },
188
+ get outputGain() {
189
+ const v = Number(self.storageSettings.values.intercomGain);
190
+ return Number.isFinite(v) ? Math.max(0.1, Math.min(10, v)) : 1.0;
191
+ },
192
+ get maxBacklogMs() {
193
+ const v = Number(self.storageSettings.values.intercomMaxBacklogMs);
194
+ return Number.isFinite(v) ? Math.max(20, Math.min(5000, v)) : 120;
195
+ },
196
+ get channel() {
197
+ return self.storageSettings.values.intercomChannel ?? 0;
198
+ },
199
+ get isBatteryCamera() {
200
+ return self.storageSettings.values.intercomTransport === "udp";
201
+ },
202
+ get deviceId() {
203
+ return self.id;
204
+ },
205
+ get logger() {
206
+ return self.console;
207
+ },
208
+ ensureApi: () => self.ensureExternalApi(),
209
+ withRetry: (fn) => fn(),
210
+ };
211
+ }
212
+
213
+ private async ensureExternalApi(): Promise<ReolinkBaichuanApi> {
214
+ let { intercomIpAddress, intercomUsername, intercomPassword,
215
+ intercomUid, intercomTransport } =
216
+ this.storageSettings.values;
217
+
218
+ // Auto-detect credentials from @scrypted/reolink camera settings
219
+ if (!intercomIpAddress || !intercomUsername || !intercomPassword) {
220
+ const creds = await this.getReolinkPluginCredentials();
221
+ if (creds) {
222
+ intercomIpAddress ||= creds.host;
223
+ intercomUsername ||= creds.username;
224
+ intercomPassword ||= creds.password;
225
+ }
226
+ }
227
+
228
+ if (!intercomIpAddress || !intercomUsername || !intercomPassword) {
229
+ throw new Error(
230
+ "Intercom connection settings incomplete: IP, username, and password are required",
231
+ );
232
+ }
233
+
234
+ const transport: BaichuanTransport =
235
+ intercomTransport === "udp" ? "udp" : "tcp";
236
+
237
+ return await this.provider.plugin.acquireExternalClient(this.id, {
238
+ host: intercomIpAddress,
239
+ username: intercomUsername,
240
+ password: intercomPassword,
241
+ uid: intercomUid,
242
+ transport,
243
+ logger: this.console,
244
+ });
245
+ }
246
+
247
+ async startIntercom(media: MediaObject): Promise<void> {
248
+ const host = this.buildHost();
249
+ this.intercomEngine = new ReolinkBaichuanIntercom(host);
250
+ await this.intercomEngine.start(media);
251
+ }
252
+
253
+ async stopIntercom(): Promise<void> {
254
+ if (this.intercomEngine) {
255
+ await this.intercomEngine.stop();
256
+ this.intercomEngine = undefined;
257
+ }
258
+ }
259
+
260
+ async getMixinSettings(): Promise<Setting[]> {
261
+ const isInternal = !!this.getInternalCamera();
262
+ const isReolinkPlugin = this.isReolinkPluginCamera();
263
+ const hideConnection = isInternal || isReolinkPlugin;
264
+ const settings = await this.storageSettings.getSettings();
265
+
266
+ const connectionKeys = new Set([
267
+ "intercomIpAddress",
268
+ "intercomUsername",
269
+ "intercomPassword",
270
+ "intercomUid",
271
+ "intercomChannel",
272
+ "intercomTransport",
273
+ ]);
274
+
275
+ for (const setting of settings) {
276
+ if (connectionKeys.has(setting.key)) {
277
+ (setting as any).hide = hideConnection;
278
+ }
279
+ }
280
+
281
+ return settings;
282
+ }
283
+
284
+ async putMixinSetting(
285
+ key: string,
286
+ value: SettingValue,
287
+ ): Promise<void> {
288
+ await this.storageSettings.putSetting(key, value);
289
+ }
290
+
291
+ async release(): Promise<void> {
292
+ if (this.intercomEngine) {
293
+ await this.intercomEngine.stop();
294
+ this.intercomEngine = undefined;
295
+ }
296
+ // Release external client if not internal
297
+ if (!this.getInternalCamera()) {
298
+ await this.provider.plugin?.releaseExternalClient(this.id);
299
+ }
300
+ delete this.provider.currentMixinsMap[this.id];
301
+ }
302
+ }
@@ -0,0 +1,130 @@
1
+ import sdk, {
2
+ MixinProvider,
3
+ ScryptedDevice,
4
+ ScryptedDeviceBase,
5
+ ScryptedDeviceType,
6
+ ScryptedInterface,
7
+ Setting,
8
+ Settings,
9
+ SettingValue,
10
+ WritableDeviceState,
11
+ } from "@scrypted/sdk";
12
+ import type ReolinkNativePlugin from "./main";
13
+ import { ReolinkNativeIntercomMixin } from "./intercom-mixin";
14
+
15
+ export const INTERCOM_PROVIDER_NATIVE_ID = "reolink-native-intercom";
16
+
17
+ const AUTO_INCLUDE_TOKEN = "v1";
18
+
19
+ export class ReolinkNativeIntercom
20
+ extends ScryptedDeviceBase
21
+ implements MixinProvider, Settings
22
+ {
23
+ currentMixinsMap: Record<string, ReolinkNativeIntercomMixin> = {};
24
+ plugin: ReolinkNativePlugin;
25
+ private hasEnabledMixin: Record<string, string> = {};
26
+ private pluginsComponent: Promise<any>;
27
+
28
+ constructor(nativeId: string) {
29
+ super(nativeId);
30
+
31
+ try {
32
+ this.hasEnabledMixin = JSON.parse(
33
+ this.storage.getItem("hasEnabledMixin") || "{}",
34
+ );
35
+ } catch {
36
+ this.hasEnabledMixin = {};
37
+ }
38
+
39
+ this.pluginsComponent = sdk.systemManager.getComponent("plugins");
40
+
41
+ // Watch for new device descriptors to auto-enable on newly added devices
42
+ sdk.systemManager.listen((eventSource, eventDetails) => {
43
+ if (
44
+ eventDetails.eventInterface !== ScryptedInterface.ScryptedDevice ||
45
+ eventDetails.property
46
+ )
47
+ return;
48
+ this.maybeEnableMixin(eventSource);
49
+ });
50
+
51
+ // Check all existing devices on startup
52
+ process.nextTick(() => {
53
+ for (const id of Object.keys(sdk.systemManager.getSystemState())) {
54
+ const device = sdk.systemManager.getDeviceById(id);
55
+ this.maybeEnableMixin(device);
56
+ }
57
+ });
58
+ }
59
+
60
+ private async maybeEnableMixin(device: ScryptedDevice) {
61
+ if (!device || device.mixins?.includes(this.id)) return;
62
+
63
+ // Already auto-enabled once with this token
64
+ if (this.hasEnabledMixin[device.id] === AUTO_INCLUDE_TOKEN) return;
65
+
66
+ const match = await this.canMixin(device.type, device.interfaces);
67
+ if (!match) return;
68
+
69
+ // Only auto-enable for cameras provided by our own plugin
70
+ if (!this.plugin?.camerasMap?.has(device.id)) return;
71
+
72
+ this.console.log(`Auto-enabling intercom mixin for ${device.name}`);
73
+ const mixins = (device.mixins || []).slice();
74
+ mixins.push(this.id);
75
+ const plugins = await this.pluginsComponent;
76
+ await plugins.setMixins(device.id, mixins);
77
+
78
+ this.hasEnabledMixin[device.id] = AUTO_INCLUDE_TOKEN;
79
+ this.storage.setItem(
80
+ "hasEnabledMixin",
81
+ JSON.stringify(this.hasEnabledMixin),
82
+ );
83
+ }
84
+
85
+ async canMixin(
86
+ type: ScryptedDeviceType,
87
+ interfaces: string[],
88
+ ): Promise<string[] | null> {
89
+ if (
90
+ (type === ScryptedDeviceType.Camera ||
91
+ type === ScryptedDeviceType.Doorbell) &&
92
+ interfaces.includes(ScryptedInterface.VideoCamera)
93
+ ) {
94
+ return [ScryptedInterface.Intercom, ScryptedInterface.Settings];
95
+ }
96
+ return null;
97
+ }
98
+
99
+ async getMixin(
100
+ mixinDevice: any,
101
+ mixinDeviceInterfaces: ScryptedInterface[],
102
+ mixinDeviceState: WritableDeviceState,
103
+ ): Promise<any> {
104
+ return new ReolinkNativeIntercomMixin(
105
+ {
106
+ mixinDevice,
107
+ mixinDeviceInterfaces,
108
+ mixinDeviceState,
109
+ mixinProviderNativeId: this.nativeId,
110
+ group: "Reolink Native Intercom",
111
+ groupKey: "reolinkNativeIntercom",
112
+ },
113
+ this,
114
+ );
115
+ }
116
+
117
+ async releaseMixin(id: string, mixinDevice: any): Promise<void> {
118
+ const mixin = this.currentMixinsMap[id];
119
+ if (mixin) {
120
+ await mixin.release();
121
+ delete this.currentMixinsMap[id];
122
+ }
123
+ }
124
+
125
+ async getSettings(): Promise<Setting[]> {
126
+ return [];
127
+ }
128
+
129
+ async putSetting(key: string, value: SettingValue): Promise<void> {}
130
+ }