@apocaliss92/scrypted-reolink-native 0.0.3 → 0.0.5
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/camera.ts +203 -352
- package/src/main.ts +11 -15
- package/src/presets.ts +16 -31
- package/src/utils.ts +49 -57
package/src/main.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceInformation, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType,
|
|
1
|
+
import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceInformation, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedNativeId, Setting } from "@scrypted/sdk";
|
|
2
2
|
import { ReolinkNativeCamera } from "./camera";
|
|
3
3
|
import { connectBaichuanWithTcpUdpFallback, maskUid } from "./connect";
|
|
4
|
+
import { getDeviceInterfaces } from "./utils";
|
|
4
5
|
|
|
5
6
|
class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
|
|
6
7
|
devices = new Map<string, ReolinkNativeCamera>();
|
|
@@ -9,17 +10,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
9
10
|
return 'Reolink Native camera';
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
getCameraInterfaces() {
|
|
13
|
-
return [
|
|
14
|
-
ScryptedInterface.Reboot,
|
|
15
|
-
ScryptedInterface.VideoCameraConfiguration,
|
|
16
|
-
ScryptedInterface.Camera,
|
|
17
|
-
ScryptedInterface.AudioSensor,
|
|
18
|
-
ScryptedInterface.MotionSensor,
|
|
19
|
-
ScryptedInterface.VideoTextOverlays,
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
13
|
async getDevice(nativeId: ScryptedNativeId): Promise<ReolinkNativeCamera> {
|
|
24
14
|
if (this.devices.has(nativeId)) {
|
|
25
15
|
return this.devices.get(nativeId);
|
|
@@ -59,19 +49,25 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
59
49
|
const deviceInfo = await api.getInfo();
|
|
60
50
|
const name = deviceInfo?.name;
|
|
61
51
|
const rtspChannel = 0;
|
|
62
|
-
const { abilities, capabilities } = await api.getDeviceCapabilities(rtspChannel);
|
|
52
|
+
const { abilities, capabilities } = await api.getDeviceCapabilities(rtspChannel, { probeAi: false });
|
|
63
53
|
|
|
64
54
|
this.console.log(JSON.stringify({ abilities, capabilities, deviceInfo }));
|
|
65
55
|
|
|
66
56
|
nativeId = deviceInfo.serialNumber;
|
|
57
|
+
const type = capabilities.isDoorbell ? ScryptedDeviceType.DataSource : ScryptedDeviceType.Camera;
|
|
67
58
|
|
|
68
59
|
settings.newCamera ||= name;
|
|
69
60
|
|
|
61
|
+
const interfaces = getDeviceInterfaces({
|
|
62
|
+
capabilities,
|
|
63
|
+
logger: this.console,
|
|
64
|
+
});
|
|
65
|
+
|
|
70
66
|
await sdk.deviceManager.onDeviceDiscovered({
|
|
71
67
|
nativeId,
|
|
72
68
|
name,
|
|
73
|
-
interfaces
|
|
74
|
-
type
|
|
69
|
+
interfaces,
|
|
70
|
+
type,
|
|
75
71
|
providerNativeId: this.nativeId,
|
|
76
72
|
});
|
|
77
73
|
|
package/src/presets.ts
CHANGED
|
@@ -113,54 +113,39 @@ export class ReolinkPtzPresets {
|
|
|
113
113
|
const existing = await client.getPtzPresets(channel);
|
|
114
114
|
const existingIds = new Set(existing.map((p) => p.id));
|
|
115
115
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
122
|
-
const id = presetId ?? (initialId + attempt);
|
|
123
|
-
if (!presetId && existingIds.has(id)) continue;
|
|
124
|
-
|
|
125
|
-
await client.setPtzPreset(channel, id, trimmed);
|
|
116
|
+
const id = presetId ?? this.nextFreePresetId(existing);
|
|
117
|
+
if (presetId === undefined && existingIds.has(id)) {
|
|
118
|
+
// Should not happen because nextFreePresetId returns unused id.
|
|
119
|
+
throw new Error(`PTZ preset id already in use: ${id}`);
|
|
120
|
+
}
|
|
126
121
|
|
|
127
|
-
|
|
128
|
-
lastUpdated = updated;
|
|
122
|
+
await client.setPtzPreset(channel, id, trimmed);
|
|
129
123
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
124
|
+
const updated = await client.getPtzPresets(channel);
|
|
125
|
+
const persisted = updated.some((p) => p.id === id);
|
|
126
|
+
this.setCachedPtzPresets(updated);
|
|
135
127
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (chosenId === undefined) {
|
|
141
|
-
this.setCachedPtzPresets(lastUpdated);
|
|
142
|
-
this.syncEnabledPresetsSettingAndCaps(lastUpdated);
|
|
128
|
+
if (!persisted) {
|
|
129
|
+
this.syncEnabledPresetsSettingAndCaps(updated);
|
|
143
130
|
throw new Error(
|
|
144
|
-
`PTZ preset save did not persist (camera returned an empty/unchanged preset list). Try
|
|
131
|
+
`PTZ preset save did not persist (camera returned an empty/unchanged preset list). Try again.`
|
|
145
132
|
);
|
|
146
133
|
}
|
|
147
134
|
|
|
148
|
-
this.setCachedPtzPresets(lastUpdated);
|
|
149
|
-
|
|
150
135
|
// If the "presets" setting is in use, auto-add the newly created preset so it becomes
|
|
151
136
|
// immediately selectable/visible in the UI. If it's empty/unconfigured, don't start using it.
|
|
152
137
|
const enabled = (this.storageSettings.values.presets ?? []) as string[];
|
|
153
138
|
if (enabled.length) {
|
|
154
|
-
const already = enabled.some((e) => this.parsePresetIdFromSettingValue(e) ===
|
|
139
|
+
const already = enabled.some((e) => this.parsePresetIdFromSettingValue(e) === id);
|
|
155
140
|
if (!already) {
|
|
156
|
-
enabled.push(`${
|
|
141
|
+
enabled.push(`${id}=${trimmed}`);
|
|
157
142
|
this.storageSettings.values.presets = enabled;
|
|
158
143
|
}
|
|
159
144
|
}
|
|
160
145
|
|
|
161
146
|
// Re-apply enabled mapping (so custom names/filters remain effective after setCachedPtzPresets).
|
|
162
|
-
this.syncEnabledPresetsSettingAndCaps(
|
|
163
|
-
return { id
|
|
147
|
+
this.syncEnabledPresetsSettingAndCaps(updated);
|
|
148
|
+
return { id, name: trimmed };
|
|
164
149
|
}
|
|
165
150
|
|
|
166
151
|
/** Overwrite an existing preset with the current PTZ position (and keep its current name). */
|
package/src/utils.ts
CHANGED
|
@@ -1,60 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
export const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
previewQueryTopic,
|
|
35
|
-
ptzPreviewQueryTopic,
|
|
36
|
-
ptzControlTopic,
|
|
37
|
-
ptzPresetControlTopic,
|
|
38
|
-
floodlightControlTopic,
|
|
39
|
-
floodlightTasksControlTopic,
|
|
40
|
-
sirenControlTopic,
|
|
41
|
-
rebootControlTopic,
|
|
42
|
-
ledControlTopic,
|
|
43
|
-
irControlTopic,
|
|
44
|
-
pirControlTopic,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const subscribeToNeolinkTopic = async (client: MqttClient, topic: string, console: Console, cb: (value?: any) => void) => {
|
|
49
|
-
client.subscribe([topic], async (messageTopic, message) => {
|
|
50
|
-
const messageString = message.toString();
|
|
51
|
-
if (messageTopic === topic) {
|
|
52
|
-
cb(messageString);
|
|
1
|
+
import type { DeviceCapabilities } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
|
+
import { ScryptedInterface } from "@scrypted/sdk";
|
|
3
|
+
|
|
4
|
+
export const getDeviceInterfaces = (props: {
|
|
5
|
+
capabilities: DeviceCapabilities,
|
|
6
|
+
logger: Console
|
|
7
|
+
}) => {
|
|
8
|
+
const { capabilities, logger } = props;
|
|
9
|
+
|
|
10
|
+
const interfaces = [
|
|
11
|
+
ScryptedInterface.VideoCamera,
|
|
12
|
+
ScryptedInterface.Settings,
|
|
13
|
+
ScryptedInterface.Reboot,
|
|
14
|
+
ScryptedInterface.VideoCameraConfiguration,
|
|
15
|
+
ScryptedInterface.Camera,
|
|
16
|
+
ScryptedInterface.AudioSensor,
|
|
17
|
+
ScryptedInterface.MotionSensor,
|
|
18
|
+
ScryptedInterface.VideoTextOverlays,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const {
|
|
23
|
+
hasPtz,
|
|
24
|
+
hasSiren,
|
|
25
|
+
hasFloodlight,
|
|
26
|
+
hasPir,
|
|
27
|
+
hasBattery,
|
|
28
|
+
hasIntercom,
|
|
29
|
+
isDoorbell,
|
|
30
|
+
} = capabilities;
|
|
31
|
+
|
|
32
|
+
if (hasPtz) {
|
|
33
|
+
interfaces.push(ScryptedInterface.PanTiltZoom);
|
|
53
34
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
35
|
+
interfaces.push(ScryptedInterface.ObjectDetector);
|
|
36
|
+
if (hasSiren || hasFloodlight || hasPir)
|
|
37
|
+
interfaces.push(ScryptedInterface.DeviceProvider);
|
|
38
|
+
if (hasBattery) {
|
|
39
|
+
interfaces.push(ScryptedInterface.Battery, ScryptedInterface.Sleep);
|
|
40
|
+
}
|
|
41
|
+
if (hasIntercom) {
|
|
42
|
+
interfaces.push(ScryptedInterface.Intercom);
|
|
43
|
+
}
|
|
44
|
+
if (isDoorbell) {
|
|
45
|
+
interfaces.push(ScryptedInterface.BinarySensor);
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
logger.error('Error getting device interfaces', e);
|
|
49
|
+
}
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
client.unsubscribe([topic]);
|
|
51
|
+
return interfaces;
|
|
60
52
|
}
|