@apocaliss92/scrypted-reolink-native 0.4.0 → 0.4.2
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/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/accessories/autotracking.ts +150 -0
- package/src/accessories/floodlight.ts +92 -0
- package/src/accessories/index.ts +7 -0
- package/src/accessories/motion-floodlight.ts +171 -0
- package/src/accessories/motion-siren.ts +165 -0
- package/src/accessories/pir-sensor.ts +138 -0
- package/src/accessories/siren.ts +63 -0
- package/src/camera.ts +166 -450
- package/src/multiFocal.ts +1 -3
- package/src/utils.ts +1 -0
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|