@apocaliss92/scrypted-reolink-native 0.1.42 → 0.2.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/build-lib.sh +31 -0
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +2 -1
- package/src/baichuan-base.ts +149 -30
- package/src/camera.ts +3004 -89
- package/src/intercom.ts +5 -7
- package/src/main.ts +18 -27
- package/src/multiFocal.ts +194 -172
- package/src/nvr.ts +96 -238
- package/src/presets.ts +2 -2
- package/src/stream-utils.ts +232 -101
- package/src/utils.ts +22 -23
- package/src/camera-battery.ts +0 -336
- package/src/common.ts +0 -2551
package/src/camera-battery.ts
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import type { ReolinkBaichuanApi, SleepStatus } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
|
-
import sdk, {
|
|
3
|
-
type MediaObject,
|
|
4
|
-
RequestPictureOptions,
|
|
5
|
-
ResponsePictureOptions
|
|
6
|
-
} from "@scrypted/sdk";
|
|
7
|
-
import {
|
|
8
|
-
CommonCameraMixin,
|
|
9
|
-
} from "./common";
|
|
10
|
-
import { DebugLogOption } from "./debug-options";
|
|
11
|
-
import type ReolinkNativePlugin from "./main";
|
|
12
|
-
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
13
|
-
import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
14
|
-
|
|
15
|
-
export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
16
|
-
doorbellBinaryTimeout?: NodeJS.Timeout;
|
|
17
|
-
motionDetected: boolean = false;
|
|
18
|
-
motionTimeout: NodeJS.Timeout | undefined;
|
|
19
|
-
private periodicStarted = false;
|
|
20
|
-
private sleepCheckTimer: NodeJS.Timeout | undefined;
|
|
21
|
-
private batteryUpdateTimer: NodeJS.Timeout | undefined;
|
|
22
|
-
private lastBatteryLevel: number | undefined;
|
|
23
|
-
private batteryUpdateInProgress: boolean = false;
|
|
24
|
-
|
|
25
|
-
constructor(
|
|
26
|
-
nativeId: string,
|
|
27
|
-
public plugin: ReolinkNativePlugin,
|
|
28
|
-
nvrDevice?: ReolinkNativeNvrDevice,
|
|
29
|
-
multiFocalDevice?: ReolinkNativeMultiFocalDevice
|
|
30
|
-
) {
|
|
31
|
-
super(nativeId, plugin, {
|
|
32
|
-
type: 'battery',
|
|
33
|
-
nvrDevice,
|
|
34
|
-
multiFocalDevice,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async init(): Promise<void> {
|
|
39
|
-
this.startPeriodicTasks();
|
|
40
|
-
await this.alignAuxDevicesState();
|
|
41
|
-
await this.updateBatteryInfo();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async release(): Promise<void> {
|
|
45
|
-
this.stopPeriodicTasks();
|
|
46
|
-
return this.resetBaichuanClient();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private stopPeriodicTasks(): void {
|
|
50
|
-
if (this.sleepCheckTimer) {
|
|
51
|
-
clearInterval(this.sleepCheckTimer);
|
|
52
|
-
this.sleepCheckTimer = undefined;
|
|
53
|
-
}
|
|
54
|
-
if (this.batteryUpdateTimer) {
|
|
55
|
-
clearInterval(this.batteryUpdateTimer);
|
|
56
|
-
this.batteryUpdateTimer = undefined;
|
|
57
|
-
}
|
|
58
|
-
this.periodicStarted = false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private startPeriodicTasks(): void {
|
|
62
|
-
if (this.periodicStarted) return;
|
|
63
|
-
const logger = this.getBaichuanLogger();
|
|
64
|
-
this.periodicStarted = true;
|
|
65
|
-
|
|
66
|
-
logger.log('Starting periodic tasks for battery camera');
|
|
67
|
-
|
|
68
|
-
if (!this.nvrDevice && !this.multiFocalDevice) {
|
|
69
|
-
this.sleepCheckTimer = setInterval(async () => {
|
|
70
|
-
try {
|
|
71
|
-
const api = this.baichuanApi;
|
|
72
|
-
const channel = this.storageSettings.values.rtspChannel;
|
|
73
|
-
|
|
74
|
-
if (!api) {
|
|
75
|
-
if (!this.sleeping) {
|
|
76
|
-
logger.log('Camera is sleeping: no active Baichuan client');
|
|
77
|
-
this.sleeping = true;
|
|
78
|
-
}
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const sleepStatus = api.getSleepStatus({ channel });
|
|
83
|
-
await this.updateSleepingState(sleepStatus);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
logger.warn('Error checking sleeping state:', e);
|
|
86
|
-
}
|
|
87
|
-
}, 5_000);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Update battery and snapshot every N minutes
|
|
91
|
-
const { batteryUpdateIntervalMinutes = 10 } = this.storageSettings.values;
|
|
92
|
-
const updateIntervalMs = batteryUpdateIntervalMinutes * 60_000;
|
|
93
|
-
this.batteryUpdateTimer = setInterval(() => {
|
|
94
|
-
this.updateBatteryAndSnapshot().catch(() => { });
|
|
95
|
-
}, updateIntervalMs);
|
|
96
|
-
|
|
97
|
-
logger.log(`Periodic tasks started: sleep check every 5s, battery update every ${batteryUpdateIntervalMinutes} minutes`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async updateSleepingState(sleepStatus: SleepStatus): Promise<void> {
|
|
101
|
-
try {
|
|
102
|
-
if (this.isDebugEnabled()) {
|
|
103
|
-
this.getBaichuanLogger().debug('getSleepStatus result:', JSON.stringify(sleepStatus));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (sleepStatus.state === 'sleeping') {
|
|
107
|
-
if (!this.sleeping) {
|
|
108
|
-
this.getBaichuanLogger().log(`Camera is sleeping: ${sleepStatus.reason}`);
|
|
109
|
-
this.sleeping = true;
|
|
110
|
-
}
|
|
111
|
-
} else if (sleepStatus.state === 'awake') {
|
|
112
|
-
// Camera is awake
|
|
113
|
-
const wasSleeping = this.sleeping;
|
|
114
|
-
if (wasSleeping) {
|
|
115
|
-
this.getBaichuanLogger().log(`Camera woke up: ${sleepStatus.reason}`);
|
|
116
|
-
this.sleeping = false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (wasSleeping) {
|
|
120
|
-
this.alignAuxDevicesState().catch(() => { });
|
|
121
|
-
if (this.forceNewSnapshot) {
|
|
122
|
-
this.takePicture().catch(() => { });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
// Unknown state
|
|
127
|
-
this.getBaichuanLogger().debug(`Sleep status unknown: ${sleepStatus.reason}`);
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
// Silently ignore errors in sleep check to avoid spam
|
|
131
|
-
this.getBaichuanLogger().debug('Error in updateSleepingState:', e);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async updateOnlineState(isOnline: boolean): Promise<void> {
|
|
136
|
-
try {
|
|
137
|
-
if (this.isDebugEnabled()) {
|
|
138
|
-
this.getBaichuanLogger().debug('updateOnlineState result:', isOnline);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (isOnline !== this.online) {
|
|
142
|
-
this.online = isOnline;
|
|
143
|
-
}
|
|
144
|
-
} catch (e) {
|
|
145
|
-
// Silently ignore errors in sleep check to avoid spam
|
|
146
|
-
this.getBaichuanLogger().debug('Error in updateOnlineState:', e);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async checkRecordingAction(newBatteryLevel: number) {
|
|
151
|
-
const nvrDeviceId = this.plugin.nvrDeviceId;
|
|
152
|
-
if (nvrDeviceId && this.mixins.includes(nvrDeviceId)) {
|
|
153
|
-
const logger = this.getBaichuanLogger();
|
|
154
|
-
|
|
155
|
-
const settings = await this.thisDevice.getSettings();
|
|
156
|
-
const isRecording = !settings.find(setting => setting.key === 'recording:privacyMode')?.value;
|
|
157
|
-
const { lowThresholdBatteryRecording, highThresholdBatteryRecording } = this.storageSettings.values;
|
|
158
|
-
|
|
159
|
-
if (isRecording && newBatteryLevel < lowThresholdBatteryRecording) {
|
|
160
|
-
logger.log(`Recording is enabled, but battery level is below low threshold (${newBatteryLevel}% < ${lowThresholdBatteryRecording}%), disabling recording`);
|
|
161
|
-
await this.thisDevice.putSetting('recording:privacyMode', true);
|
|
162
|
-
} else if (!isRecording && newBatteryLevel > highThresholdBatteryRecording) {
|
|
163
|
-
logger.log(`Recording is disabled, but battery level is above high threshold (${newBatteryLevel}% > ${highThresholdBatteryRecording}%), enabling recording`);
|
|
164
|
-
await this.thisDevice.putSetting('recording:privacyMode', false);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async updateBatteryInfo() {
|
|
171
|
-
const api = await this.ensureClient();
|
|
172
|
-
const channel = this.storageSettings.values.rtspChannel;
|
|
173
|
-
|
|
174
|
-
const batteryInfo = await api.getBatteryInfo(channel);
|
|
175
|
-
if (this.isDebugEnabled()) {
|
|
176
|
-
this.getBaichuanLogger().debug('getBatteryInfo result:', JSON.stringify(batteryInfo));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (batteryInfo.batteryPercent !== undefined) {
|
|
180
|
-
const oldLevel = this.lastBatteryLevel;
|
|
181
|
-
this.batteryLevel = batteryInfo.batteryPercent;
|
|
182
|
-
this.lastBatteryLevel = batteryInfo.batteryPercent;
|
|
183
|
-
|
|
184
|
-
let shouldCheckRecordingAction = true;
|
|
185
|
-
|
|
186
|
-
// Log only if battery level changed
|
|
187
|
-
if (oldLevel !== undefined && oldLevel !== batteryInfo.batteryPercent) {
|
|
188
|
-
if (batteryInfo.chargeStatus !== undefined) {
|
|
189
|
-
// chargeStatus: "0"=charging, "1"=discharging, "2"=full
|
|
190
|
-
const charging = batteryInfo.chargeStatus === "0" || batteryInfo.chargeStatus === "2";
|
|
191
|
-
this.getBaichuanLogger().log(`Battery level changed: ${oldLevel}% → ${batteryInfo.batteryPercent}% (charging: ${charging})`);
|
|
192
|
-
} else {
|
|
193
|
-
this.getBaichuanLogger().log(`Battery level changed: ${oldLevel}% → ${batteryInfo.batteryPercent}%`);
|
|
194
|
-
}
|
|
195
|
-
} else if (oldLevel === undefined) {
|
|
196
|
-
// First time setting battery level
|
|
197
|
-
if (batteryInfo.chargeStatus !== undefined) {
|
|
198
|
-
const charging = batteryInfo.chargeStatus === "0" || batteryInfo.chargeStatus === "2";
|
|
199
|
-
this.getBaichuanLogger().log(`Battery level set: ${batteryInfo.batteryPercent}% (charging: ${charging})`);
|
|
200
|
-
} else {
|
|
201
|
-
this.getBaichuanLogger().log(`Battery level set: ${batteryInfo.batteryPercent}%`);
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
shouldCheckRecordingAction = false;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (shouldCheckRecordingAction) {
|
|
208
|
-
await this.checkRecordingAction(batteryInfo.batteryPercent);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private async updateBatteryAndSnapshot(): Promise<void> {
|
|
214
|
-
// Prevent multiple simultaneous calls
|
|
215
|
-
if (this.batteryUpdateInProgress) {
|
|
216
|
-
this.getBaichuanLogger().debug('Battery update already in progress, skipping');
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
this.batteryUpdateInProgress = true;
|
|
221
|
-
try {
|
|
222
|
-
const channel = this.storageSettings.values.rtspChannel;
|
|
223
|
-
const updateIntervalMinutes = this.storageSettings.values.batteryUpdateIntervalMinutes ?? 10;
|
|
224
|
-
this.getBaichuanLogger().log(`Force battery update interval started (every ${updateIntervalMinutes} minutes)`);
|
|
225
|
-
|
|
226
|
-
// Ensure we have a client connection
|
|
227
|
-
const api = await this.ensureClient();
|
|
228
|
-
if (!api) {
|
|
229
|
-
this.getBaichuanLogger().warn('Failed to ensure client connection for battery update');
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Check current sleep status
|
|
234
|
-
let sleepStatus = api.getSleepStatus({ channel });
|
|
235
|
-
|
|
236
|
-
// If camera is sleeping, wake it up
|
|
237
|
-
if (sleepStatus.state === 'sleeping') {
|
|
238
|
-
this.getBaichuanLogger().log('Camera is sleeping, waking up for periodic update...');
|
|
239
|
-
try {
|
|
240
|
-
await api.wakeUp(channel, { waitAfterWakeMs: 2000 });
|
|
241
|
-
this.getBaichuanLogger().log('Wake command sent, waiting for camera to wake up...');
|
|
242
|
-
} catch (wakeError) {
|
|
243
|
-
this.getBaichuanLogger().warn('Failed to wake up camera:', wakeError);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Poll until camera is awake (with timeout)
|
|
248
|
-
const wakeTimeoutMs = 30000; // 30 seconds max
|
|
249
|
-
const startWakePoll = Date.now();
|
|
250
|
-
let awake = false;
|
|
251
|
-
|
|
252
|
-
while (Date.now() - startWakePoll < wakeTimeoutMs) {
|
|
253
|
-
await new Promise(resolve => setTimeout(resolve, 1000)); // Check every second
|
|
254
|
-
sleepStatus = api.getSleepStatus({ channel });
|
|
255
|
-
if (sleepStatus.state === 'awake') {
|
|
256
|
-
awake = true;
|
|
257
|
-
this.getBaichuanLogger().log('Camera is now awake');
|
|
258
|
-
this.sleeping = false;
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (!awake) {
|
|
264
|
-
this.getBaichuanLogger().warn('Camera did not wake up within timeout, skipping update');
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
} else if (sleepStatus.state === 'awake') {
|
|
268
|
-
this.sleeping = false;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Now that camera is awake, update all states
|
|
272
|
-
// 1. Update battery info
|
|
273
|
-
try {
|
|
274
|
-
await this.updateBatteryInfo();
|
|
275
|
-
} catch (e) {
|
|
276
|
-
this.getBaichuanLogger().warn('Failed to get battery info during periodic update:', e);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// 2. Align auxiliary devices state
|
|
280
|
-
try {
|
|
281
|
-
await this.alignAuxDevicesState();
|
|
282
|
-
} catch (e) {
|
|
283
|
-
this.getBaichuanLogger().warn('Failed to align auxiliary devices state:', e);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// 3. Update snapshot
|
|
287
|
-
try {
|
|
288
|
-
this.forceNewSnapshot = true;
|
|
289
|
-
await this.takePicture();
|
|
290
|
-
this.getBaichuanLogger().log('Snapshot updated during periodic update');
|
|
291
|
-
} catch (snapshotError) {
|
|
292
|
-
this.getBaichuanLogger().warn('Failed to update snapshot during periodic update:', snapshotError);
|
|
293
|
-
}
|
|
294
|
-
} catch (e) {
|
|
295
|
-
this.getBaichuanLogger().warn('Failed to update battery and snapshot', e);
|
|
296
|
-
} finally {
|
|
297
|
-
this.batteryUpdateInProgress = false;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async resetBaichuanClient(reason?: any): Promise<void> {
|
|
302
|
-
try {
|
|
303
|
-
this.unsubscribedToEvents?.();
|
|
304
|
-
|
|
305
|
-
// Close all stream servers before closing the main connection
|
|
306
|
-
// This ensures streams are properly cleaned up when using shared connection
|
|
307
|
-
if (this.streamManager) {
|
|
308
|
-
const reasonStr = reason?.message || reason?.toString?.() || 'connection reset';
|
|
309
|
-
await this.streamManager.closeAllStreams(reasonStr);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
await this.baichuanApi?.close();
|
|
313
|
-
}
|
|
314
|
-
catch (e) {
|
|
315
|
-
this.getBaichuanLogger().warn('Error closing Baichuan client during reset', e);
|
|
316
|
-
}
|
|
317
|
-
finally {
|
|
318
|
-
this.baichuanApi = undefined;
|
|
319
|
-
this.connectionTime = undefined;
|
|
320
|
-
this.ensureClientPromise = undefined;
|
|
321
|
-
if (this.sleepCheckTimer) {
|
|
322
|
-
clearInterval(this.sleepCheckTimer);
|
|
323
|
-
this.sleepCheckTimer = undefined;
|
|
324
|
-
}
|
|
325
|
-
if (this.batteryUpdateTimer) {
|
|
326
|
-
clearInterval(this.batteryUpdateTimer);
|
|
327
|
-
this.batteryUpdateTimer = undefined;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (reason) {
|
|
332
|
-
const message = reason?.message || reason?.toString?.() || reason;
|
|
333
|
-
this.getBaichuanLogger().warn(`Baichuan client reset requested: ${message}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|