@apocaliss92/scrypted-reolink-native 0.4.0 → 0.4.1

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.0",
3
+ "version": "0.4.1",
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",
@@ -0,0 +1,150 @@
1
+ import {
2
+ OnOff,
3
+ ScryptedDeviceBase,
4
+ Setting,
5
+ Settings,
6
+ SettingValue,
7
+ } from "@scrypted/sdk";
8
+ import { StorageSettings } from "@scrypted/sdk/storage-settings";
9
+ import type { ReolinkCamera } from "../camera";
10
+
11
+ /**
12
+ * Autotracking: controls PTZ auto-tracking (smart track)
13
+ * This accessory controls the PTZ auto-tracking feature on compatible cameras.
14
+ */
15
+ export class ReolinkCameraAutotracking
16
+ extends ScryptedDeviceBase
17
+ implements OnOff, Settings
18
+ {
19
+ private get logger(): Console {
20
+ return this.camera.getBaichuanLogger();
21
+ }
22
+
23
+ storageSettings = new StorageSettings(this, {
24
+ smartTrackType: {
25
+ title: "Track Types",
26
+ description: "Types of objects to track",
27
+ type: "string",
28
+ defaultValue: "people",
29
+ choices: [
30
+ "people",
31
+ "vehicle",
32
+ "dog_cat",
33
+ "people,vehicle",
34
+ "people,dog_cat",
35
+ "vehicle,dog_cat",
36
+ "people,vehicle,dog_cat",
37
+ ],
38
+ },
39
+ smartTrackObjectStopDelay: {
40
+ title: "Stop Delay",
41
+ description:
42
+ "Seconds to wait before stopping tracking when object stops moving",
43
+ type: "number",
44
+ defaultValue: 20,
45
+ range: [5, 60],
46
+ },
47
+ smartTrackObjectDisappearDelay: {
48
+ title: "Disappear Delay",
49
+ description:
50
+ "Seconds to wait before stopping tracking when object disappears",
51
+ type: "number",
52
+ defaultValue: 10,
53
+ range: [5, 60],
54
+ },
55
+ });
56
+
57
+ constructor(
58
+ public camera: ReolinkCamera,
59
+ nativeId: string,
60
+ ) {
61
+ super(nativeId);
62
+ }
63
+
64
+ async getSettings(): Promise<Setting[]> {
65
+ return this.storageSettings.getSettings();
66
+ }
67
+
68
+ async putSetting(key: string, value: SettingValue): Promise<void> {
69
+ await this.storageSettings.putSetting(key, value);
70
+
71
+ // Always apply settings to camera when changed
72
+ await this.applySettings();
73
+ }
74
+
75
+ private async applySettings(): Promise<void> {
76
+ const channel = this.camera.storageSettings.values.rtspChannel;
77
+ const smartTrackType = this.storageSettings.values.smartTrackType;
78
+ const stopDelay = this.storageSettings.values.smartTrackObjectStopDelay;
79
+ const disappearDelay =
80
+ this.storageSettings.values.smartTrackObjectDisappearDelay;
81
+
82
+ this.logger.log(
83
+ `Autotracking applySettings: trackType=${smartTrackType}, stopDelay=${stopDelay}, disappearDelay=${disappearDelay}`,
84
+ );
85
+
86
+ try {
87
+ await this.camera.withBaichuanRetry(async () => {
88
+ const api = await this.camera.ensureClient();
89
+ await api.setAutotrackingSettings(channel, {
90
+ smartTrackType,
91
+ smartTrackObjectStopDelay: stopDelay,
92
+ smartTrackObjectDisappearDelay: disappearDelay,
93
+ });
94
+ });
95
+ this.logger.log(`Autotracking applySettings: success`);
96
+ } catch (e: any) {
97
+ this.logger.error(
98
+ `Autotracking applySettings: failed`,
99
+ e?.message || String(e),
100
+ );
101
+ throw e;
102
+ }
103
+ }
104
+
105
+ async turnOff(): Promise<void> {
106
+ this.logger.log(`Autotracking toggle: turnOff (device=${this.nativeId})`);
107
+ this.on = false;
108
+ this.camera.auxDeviceCooldowns.autotracking = Date.now();
109
+ try {
110
+ const channel = this.camera.storageSettings.values.rtspChannel;
111
+ await this.camera.withBaichuanRetry(async () => {
112
+ const api = await this.camera.ensureClient();
113
+ await api.setAutotracking(false, channel);
114
+ });
115
+ this.logger.log(
116
+ `Autotracking toggle: turnOff ok (device=${this.nativeId})`,
117
+ );
118
+ } catch (e: any) {
119
+ this.on = true; // Revert on failure
120
+ this.logger.error(
121
+ `Autotracking toggle: turnOff failed (device=${this.nativeId})`,
122
+ e?.message || String(e),
123
+ );
124
+ throw e;
125
+ }
126
+ }
127
+
128
+ async turnOn(): Promise<void> {
129
+ this.logger.log(`Autotracking toggle: turnOn (device=${this.nativeId})`);
130
+ this.on = true;
131
+ this.camera.auxDeviceCooldowns.autotracking = Date.now();
132
+ try {
133
+ const channel = this.camera.storageSettings.values.rtspChannel;
134
+ await this.camera.withBaichuanRetry(async () => {
135
+ const api = await this.camera.ensureClient();
136
+ await api.setAutotracking(true, channel);
137
+ });
138
+ this.logger.log(
139
+ `Autotracking toggle: turnOn ok (device=${this.nativeId})`,
140
+ );
141
+ } catch (e: any) {
142
+ this.on = false; // Revert on failure
143
+ this.logger.error(
144
+ `Autotracking toggle: turnOn failed (device=${this.nativeId})`,
145
+ e?.message || String(e),
146
+ );
147
+ throw e;
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,92 @@
1
+ import { Brightness, OnOff, ScryptedDeviceBase } from "@scrypted/sdk";
2
+ import type { ReolinkCamera } from "../camera";
3
+
4
+ /**
5
+ * Floodlight: direct control (not motion-based)
6
+ * This accessory directly controls the floodlight on/off state and brightness.
7
+ */
8
+ export class ReolinkCameraFloodlight
9
+ extends ScryptedDeviceBase
10
+ implements OnOff, Brightness
11
+ {
12
+ private get logger(): Console {
13
+ return this.camera.getBaichuanLogger();
14
+ }
15
+
16
+ constructor(
17
+ public camera: ReolinkCamera,
18
+ nativeId: string,
19
+ ) {
20
+ super(nativeId);
21
+ }
22
+
23
+ async setBrightness(brightness: number): Promise<void> {
24
+ this.logger.log(
25
+ `Floodlight toggle: setBrightness (device=${this.nativeId} brightness=${brightness})`,
26
+ );
27
+ this.brightness = brightness;
28
+ try {
29
+ const channel = this.camera.storageSettings.values.rtspChannel;
30
+ await this.camera.withBaichuanRetry(async () => {
31
+ const api = await this.camera.ensureClient();
32
+ await api.setWhiteLedState(channel, undefined, brightness);
33
+ // Trust the set operation
34
+ });
35
+ this.logger.log(
36
+ `Floodlight toggle: setBrightness ok (device=${this.nativeId} brightness=${brightness})`,
37
+ );
38
+ } catch (e: any) {
39
+ this.logger.warn(
40
+ `Floodlight toggle: setBrightness failed (device=${this.nativeId} brightness=${brightness})`,
41
+ e?.message || String(e),
42
+ );
43
+ throw e;
44
+ }
45
+ }
46
+
47
+ async turnOff(): Promise<void> {
48
+ this.logger.log(`Floodlight toggle: turnOff (device=${this.nativeId})`);
49
+ this.on = false;
50
+ this.camera.auxDeviceCooldowns.floodlight = Date.now();
51
+ try {
52
+ const channel = this.camera.storageSettings.values.rtspChannel;
53
+ await this.camera.withBaichuanRetry(async () => {
54
+ const api = await this.camera.ensureClient();
55
+ await api.setWhiteLedState(channel, false);
56
+ // Trust the set operation - don't read back immediately as camera may have delay
57
+ });
58
+ this.logger.log(
59
+ `Floodlight toggle: turnOff ok (device=${this.nativeId})`,
60
+ );
61
+ } catch (e: any) {
62
+ this.on = true; // Revert on failure
63
+ this.logger.warn(
64
+ `Floodlight toggle: turnOff failed (device=${this.nativeId})`,
65
+ e?.message || String(e),
66
+ );
67
+ throw e;
68
+ }
69
+ }
70
+
71
+ async turnOn(): Promise<void> {
72
+ this.logger.log(`Floodlight toggle: turnOn (device=${this.nativeId})`);
73
+ this.on = true;
74
+ this.camera.auxDeviceCooldowns.floodlight = Date.now();
75
+ try {
76
+ const channel = this.camera.storageSettings.values.rtspChannel;
77
+ await this.camera.withBaichuanRetry(async () => {
78
+ const api = await this.camera.ensureClient();
79
+ await api.setWhiteLedState(channel, true);
80
+ // Trust the set operation - don't read back immediately as camera may have delay
81
+ });
82
+ this.logger.log(`Floodlight toggle: turnOn ok (device=${this.nativeId})`);
83
+ } catch (e: any) {
84
+ this.on = false; // Revert on failure
85
+ this.logger.warn(
86
+ `Floodlight toggle: turnOn failed (device=${this.nativeId})`,
87
+ e?.message || String(e),
88
+ );
89
+ throw e;
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,7 @@
1
+ // Accessory devices for Reolink cameras
2
+ export { ReolinkCameraMotionSiren } from "./motion-siren";
3
+ export { ReolinkCameraSiren } from "./siren";
4
+ export { ReolinkCameraMotionFloodlight } from "./motion-floodlight";
5
+ export { ReolinkCameraFloodlight } from "./floodlight";
6
+ export { ReolinkCameraPirSensor } from "./pir-sensor";
7
+ export { ReolinkCameraAutotracking } from "./autotracking";
@@ -0,0 +1,171 @@
1
+ import {
2
+ Brightness,
3
+ OnOff,
4
+ ScryptedDeviceBase,
5
+ Setting,
6
+ Settings,
7
+ SettingValue,
8
+ } from "@scrypted/sdk";
9
+ import { StorageSettings } from "@scrypted/sdk/storage-settings";
10
+ import type { ReolinkCamera } from "../camera";
11
+
12
+ /**
13
+ * Motion-floodlight: controls motion detection light (MD light)
14
+ * This accessory controls the floodlight that turns on when motion is detected.
15
+ */
16
+ export class ReolinkCameraMotionFloodlight
17
+ extends ScryptedDeviceBase
18
+ implements OnOff, Brightness, Settings
19
+ {
20
+ private get logger(): Console {
21
+ return this.camera.getBaichuanLogger();
22
+ }
23
+
24
+ storageSettings = new StorageSettings(this, {
25
+ duration: {
26
+ title: "Light Duration",
27
+ description: "How long the light stays on after motion (in seconds)",
28
+ type: "number",
29
+ defaultValue: 180,
30
+ range: [1, 600],
31
+ },
32
+ detectType: {
33
+ title: "Detection Types",
34
+ description: "Types of objects that trigger the light",
35
+ type: "string",
36
+ defaultValue: "people,vehicle,dog_cat",
37
+ choices: [
38
+ "people",
39
+ "vehicle",
40
+ "dog_cat",
41
+ "people,vehicle",
42
+ "people,dog_cat",
43
+ "vehicle,dog_cat",
44
+ "people,vehicle,dog_cat",
45
+ ],
46
+ },
47
+ });
48
+
49
+ constructor(
50
+ public camera: ReolinkCamera,
51
+ nativeId: string,
52
+ ) {
53
+ super(nativeId);
54
+ }
55
+
56
+ async getSettings(): Promise<Setting[]> {
57
+ return this.storageSettings.getSettings();
58
+ }
59
+
60
+ async putSetting(key: string, value: SettingValue): Promise<void> {
61
+ await this.storageSettings.putSetting(key, value);
62
+ // Always apply settings to camera when changed
63
+ await this.applySettings();
64
+ }
65
+
66
+ private async applySettings(): Promise<void> {
67
+ const channel = this.camera.storageSettings.values.rtspChannel;
68
+ const duration = this.storageSettings.values.duration;
69
+ const detectType = this.storageSettings.values.detectType;
70
+
71
+ this.logger.log(
72
+ `Motion-floodlight applySettings: duration=${duration}, detectType=${detectType}`,
73
+ );
74
+
75
+ try {
76
+ await this.camera.withBaichuanRetry(async () => {
77
+ const api = await this.camera.ensureClient();
78
+ await api.setFloodlightSettings(channel, { duration, detectType });
79
+ });
80
+ this.logger.log(`Motion-floodlight applySettings: success`);
81
+ } catch (e: any) {
82
+ this.logger.error(
83
+ `Motion-floodlight applySettings: failed`,
84
+ e?.message || String(e),
85
+ );
86
+ throw e;
87
+ }
88
+ }
89
+
90
+ async setBrightness(brightness: number): Promise<void> {
91
+ this.logger.log(
92
+ `Motion-floodlight toggle: setBrightness (device=${this.nativeId} brightness=${brightness})`,
93
+ );
94
+ this.brightness = brightness;
95
+ try {
96
+ const channel = this.camera.storageSettings.values.rtspChannel;
97
+ await this.camera.withBaichuanRetry(async () => {
98
+ const api = await this.camera.ensureClient();
99
+ await api.setWhiteLedState(channel, undefined, brightness);
100
+ // Trust the set operation
101
+ });
102
+ this.logger.log(
103
+ `Motion-floodlight toggle: setBrightness ok (device=${this.nativeId} brightness=${brightness})`,
104
+ );
105
+ } catch (e: any) {
106
+ this.logger.warn(
107
+ `Motion-floodlight toggle: setBrightness failed (device=${this.nativeId} brightness=${brightness})`,
108
+ e?.message || String(e),
109
+ );
110
+ throw e;
111
+ }
112
+ }
113
+
114
+ async turnOff(): Promise<void> {
115
+ this.logger.log(
116
+ `Motion-floodlight toggle: turnOff (device=${this.nativeId})`,
117
+ );
118
+ this.on = false;
119
+ this.camera.auxDeviceCooldowns.motionFloodlight = Date.now();
120
+ try {
121
+ const channel = this.camera.storageSettings.values.rtspChannel;
122
+ await this.camera.withBaichuanRetry(async () => {
123
+ const api = await this.camera.ensureClient();
124
+ // Use setFloodlightOnMotion (cmdId=290 FloodlightTask) for motion-triggered floodlight
125
+ await api.setFloodlightOnMotion(false, channel);
126
+ // Trust the set operation - don't read back immediately as camera may have delay
127
+ });
128
+ this.logger.log(
129
+ `Motion-floodlight toggle: turnOff ok (device=${this.nativeId})`,
130
+ );
131
+ } catch (e: any) {
132
+ this.on = true; // Revert on failure
133
+ this.logger.warn(
134
+ `Motion-floodlight toggle: turnOff failed (device=${this.nativeId})`,
135
+ e?.message || String(e),
136
+ );
137
+ throw e;
138
+ }
139
+ }
140
+
141
+ async turnOn(): Promise<void> {
142
+ this.logger.log(
143
+ `Motion-floodlight toggle: turnOn (device=${this.nativeId})`,
144
+ );
145
+ this.on = true;
146
+ this.camera.auxDeviceCooldowns.motionFloodlight = Date.now();
147
+ try {
148
+ const channel = this.camera.storageSettings.values.rtspChannel;
149
+ const duration = this.storageSettings.values.duration;
150
+ const detectType = this.storageSettings.values.detectType;
151
+ await this.camera.withBaichuanRetry(async () => {
152
+ const api = await this.camera.ensureClient();
153
+ // Use setFloodlightOnMotion (cmdId=290 FloodlightTask) for motion-triggered floodlight
154
+ await api.setFloodlightOnMotion(true, channel);
155
+ // Apply settings (duration, detectType)
156
+ await api.setFloodlightSettings(channel, { duration, detectType });
157
+ // Trust the set operation - don't read back immediately as camera may have delay
158
+ });
159
+ this.logger.log(
160
+ `Motion-floodlight toggle: turnOn ok (device=${this.nativeId})`,
161
+ );
162
+ } catch (e: any) {
163
+ this.on = false; // Revert on failure
164
+ this.logger.warn(
165
+ `Motion-floodlight toggle: turnOn failed (device=${this.nativeId})`,
166
+ e?.message || String(e),
167
+ );
168
+ throw e;
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,165 @@
1
+ import {
2
+ OnOff,
3
+ ScryptedDeviceBase,
4
+ Setting,
5
+ Settings,
6
+ SettingValue,
7
+ } from "@scrypted/sdk";
8
+ import { StorageSettings } from "@scrypted/sdk/storage-settings";
9
+ import type { ReolinkCamera } from "../camera";
10
+
11
+ /**
12
+ * Motion-siren: controls motion detection alarm (MD alarm)
13
+ * This accessory triggers the siren when motion is detected based on configured detection types.
14
+ */
15
+ export class ReolinkCameraMotionSiren
16
+ extends ScryptedDeviceBase
17
+ implements OnOff, Settings
18
+ {
19
+ private get logger(): Console {
20
+ return this.camera.getBaichuanLogger();
21
+ }
22
+
23
+ storageSettings = new StorageSettings(this, {
24
+ detectTypes: {
25
+ title: "Detection Types",
26
+ description: "Types of motion events that trigger the siren",
27
+ type: "string",
28
+ defaultValue: "MD,people,vehicle,dog_cat",
29
+ choices: [
30
+ "MD",
31
+ "people",
32
+ "vehicle",
33
+ "dog_cat",
34
+ "MD,people",
35
+ "MD,vehicle",
36
+ "MD,dog_cat",
37
+ "people,vehicle",
38
+ "people,dog_cat",
39
+ "vehicle,dog_cat",
40
+ "MD,people,vehicle",
41
+ "MD,people,dog_cat",
42
+ "MD,vehicle,dog_cat",
43
+ "people,vehicle,dog_cat",
44
+ "MD,people,vehicle,dog_cat",
45
+ ],
46
+ },
47
+ });
48
+
49
+ constructor(
50
+ public camera: ReolinkCamera,
51
+ nativeId: string,
52
+ ) {
53
+ super(nativeId);
54
+ }
55
+
56
+ async getSettings(): Promise<Setting[]> {
57
+ return this.storageSettings.getSettings();
58
+ }
59
+
60
+ async putSetting(key: string, value: SettingValue): Promise<void> {
61
+ await this.storageSettings.putSetting(key, value);
62
+ // Always apply settings to camera when changed (only if siren is enabled)
63
+ if (this.on) {
64
+ await this.applySettings();
65
+ }
66
+ }
67
+
68
+ private async applySettings(): Promise<void> {
69
+ const channel = this.camera.storageSettings.values.rtspChannel;
70
+ const typeScheduleList = this.buildTypeScheduleList();
71
+
72
+ this.logger.log(
73
+ `Motion-siren applySettings: detectTypes=${this.storageSettings.values.detectTypes}`,
74
+ );
75
+
76
+ try {
77
+ await this.camera.withBaichuanRetry(async () => {
78
+ const api = await this.camera.ensureClient();
79
+ await api.setSirenOnMotion({ enable: 1, typeScheduleList }, channel);
80
+ });
81
+ this.logger.log(`Motion-siren applySettings: success`);
82
+ } catch (e: any) {
83
+ this.logger.error(
84
+ `Motion-siren applySettings: failed`,
85
+ e?.message || String(e),
86
+ );
87
+ throw e;
88
+ }
89
+ }
90
+
91
+ private buildTypeScheduleList(): Array<{
92
+ type: string;
93
+ valueTable: string;
94
+ }> {
95
+ const detectTypes = this.storageSettings.values.detectTypes || "";
96
+ const types = detectTypes.split(",").map((t: string) => t.trim());
97
+ const allOn = "1".repeat(168);
98
+ const allOff = "0".repeat(168);
99
+
100
+ // Build schedule list for each type
101
+ return [
102
+ { type: "MD", valueTable: types.includes("MD") ? allOn : allOff },
103
+ { type: "people", valueTable: types.includes("people") ? allOn : allOff },
104
+ {
105
+ type: "vehicle",
106
+ valueTable: types.includes("vehicle") ? allOn : allOff,
107
+ },
108
+ {
109
+ type: "dog_cat",
110
+ valueTable: types.includes("dog_cat") ? allOn : allOff,
111
+ },
112
+ ];
113
+ }
114
+
115
+ async turnOff(): Promise<void> {
116
+ this.logger.log(`Motion-siren toggle: turnOff (device=${this.nativeId})`);
117
+ this.on = false;
118
+ this.camera.auxDeviceCooldowns.motionSiren = Date.now();
119
+ try {
120
+ const channel = this.camera.storageSettings.values.rtspChannel;
121
+ await this.camera.withBaichuanRetry(async () => {
122
+ const api = await this.camera.ensureClient();
123
+ // Use setSirenOnMotion (cmdId=231 AudioTask) for motion-triggered siren
124
+ await api.setSirenOnMotion({ enable: 0 }, channel);
125
+ // Trust the set operation - don't read back immediately as camera may have delay
126
+ });
127
+ this.logger.log(
128
+ `Motion-siren toggle: turnOff ok (device=${this.nativeId})`,
129
+ );
130
+ } catch (e: any) {
131
+ this.on = true; // Revert on failure
132
+ this.logger.error(
133
+ `Motion-siren toggle: turnOff failed (device=${this.nativeId})`,
134
+ e?.message || String(e),
135
+ );
136
+ throw e;
137
+ }
138
+ }
139
+
140
+ async turnOn(): Promise<void> {
141
+ this.logger.log(`Motion-siren toggle: turnOn (device=${this.nativeId})`);
142
+ this.on = true;
143
+ this.camera.auxDeviceCooldowns.motionSiren = Date.now();
144
+ try {
145
+ const channel = this.camera.storageSettings.values.rtspChannel;
146
+ const typeScheduleList = this.buildTypeScheduleList();
147
+ await this.camera.withBaichuanRetry(async () => {
148
+ const api = await this.camera.ensureClient();
149
+ // Use setSirenOnMotion (cmdId=231 AudioTask) for motion-triggered siren
150
+ await api.setSirenOnMotion({ enable: 1, typeScheduleList }, channel);
151
+ // Trust the set operation - don't read back immediately as camera may have delay
152
+ });
153
+ this.logger.log(
154
+ `Motion-siren toggle: turnOn ok (device=${this.nativeId})`,
155
+ );
156
+ } catch (e: any) {
157
+ this.on = false; // Revert on failure
158
+ this.logger.error(
159
+ `Motion-siren toggle: turnOn failed (device=${this.nativeId})`,
160
+ e?.message || String(e),
161
+ );
162
+ throw e;
163
+ }
164
+ }
165
+ }