iobroker.eusec 2.0.2 → 3.0.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/LICENSE +1 -1
- package/README.md +25 -10
- package/build/lib/eufyApiCompat.js +63 -0
- package/build/lib/eufyApiCompat.js.map +7 -0
- package/build/lib/go2rtc.js +36 -0
- package/build/lib/go2rtc.js.map +7 -0
- package/build/lib/interfaces.js.map +1 -1
- package/build/lib/log.js.map +2 -2
- package/build/lib/types.js.map +1 -1
- package/build/lib/utils.js +53 -35
- package/build/lib/utils.js.map +2 -2
- package/build/lib/video.js +55 -38
- package/build/lib/video.js.map +2 -2
- package/build/main.js +586 -238
- package/build/main.js.map +2 -2
- package/cleanupCveFix.js +1 -1
- package/io-package.json +17 -29
- package/package.json +24 -35
package/build/main.js
CHANGED
|
@@ -31,20 +31,26 @@ __export(main_exports, {
|
|
|
31
31
|
euSec: () => euSec
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(main_exports);
|
|
34
|
-
var
|
|
35
|
-
var path = __toESM(require("path"));
|
|
34
|
+
var import_adapter_core = require("@iobroker/adapter-core");
|
|
35
|
+
var path = __toESM(require("node:path"));
|
|
36
36
|
var import_eufy_security_client = require("eufy-security-client");
|
|
37
37
|
var import_i18n_iso_countries = require("i18n-iso-countries");
|
|
38
38
|
var import_i18n_iso_languages = require("@cospired/i18n-iso-languages");
|
|
39
|
-
var
|
|
40
|
-
var
|
|
39
|
+
var import_node_util = __toESM(require("node:util"));
|
|
40
|
+
var import_node_child_process = __toESM(require("node:child_process"));
|
|
41
41
|
var import_go2rtc_static = __toESM(require("go2rtc-static"));
|
|
42
42
|
var import_ffmpeg_for_homebridge = __toESM(require("ffmpeg-for-homebridge"));
|
|
43
43
|
var import_types = require("./lib/types");
|
|
44
44
|
var import_utils = require("./lib/utils");
|
|
45
45
|
var import_log = require("./lib/log");
|
|
46
|
+
var import_eufyApiCompat = require("./lib/eufyApiCompat");
|
|
47
|
+
var import_go2rtc = require("./lib/go2rtc");
|
|
46
48
|
var import_video = require("./lib/video");
|
|
47
|
-
|
|
49
|
+
const GO2RTC_RESTART_DELAY_MIN = 1e3;
|
|
50
|
+
const GO2RTC_RESTART_DELAY_MAX = 3e4;
|
|
51
|
+
const GO2RTC_HEALTHY_RUNTIME = 6e4;
|
|
52
|
+
const VIDEO_STREAMING_QUALITY_AUTO_LABEL = "Auto";
|
|
53
|
+
class euSec extends import_adapter_core.Adapter {
|
|
48
54
|
eufy;
|
|
49
55
|
/*private downloadEvent: {
|
|
50
56
|
[index: string]: NodeJS.Timeout;
|
|
@@ -58,6 +64,10 @@ class euSec extends utils.Adapter {
|
|
|
58
64
|
captchaId = null;
|
|
59
65
|
verify_code = false;
|
|
60
66
|
skipInit = false;
|
|
67
|
+
go2rtcProcess = void 0;
|
|
68
|
+
go2rtcRestartTimeout = void 0;
|
|
69
|
+
go2rtcRestarts = 0;
|
|
70
|
+
terminating = false;
|
|
61
71
|
constructor(options = {}) {
|
|
62
72
|
super({
|
|
63
73
|
...options,
|
|
@@ -67,6 +77,10 @@ class euSec extends utils.Adapter {
|
|
|
67
77
|
this.on("stateChange", this.onStateChange.bind(this));
|
|
68
78
|
this.on("message", this.onMessage.bind(this));
|
|
69
79
|
this.on("unload", this.onUnload.bind(this));
|
|
80
|
+
process.on("exit", () => {
|
|
81
|
+
var _a;
|
|
82
|
+
return (_a = this.go2rtcProcess) == null ? void 0 : _a.kill();
|
|
83
|
+
});
|
|
70
84
|
}
|
|
71
85
|
restartAdapter() {
|
|
72
86
|
this.skipInit = true;
|
|
@@ -76,7 +90,6 @@ class euSec extends utils.Adapter {
|
|
|
76
90
|
* Is called when databases are connected and adapter received configuration.
|
|
77
91
|
*/
|
|
78
92
|
async onReady() {
|
|
79
|
-
var _a;
|
|
80
93
|
this.logger = new import_log.ioBrokerLogger(this.log);
|
|
81
94
|
await this.setObjectNotExistsAsync("verify_code", {
|
|
82
95
|
type: "state",
|
|
@@ -156,10 +169,11 @@ class euSec extends utils.Adapter {
|
|
|
156
169
|
await this.setStateAsync("info.mqtt_connection", { val: false, ack: true });
|
|
157
170
|
try {
|
|
158
171
|
const connection = await this.getStatesAsync("*.connection");
|
|
159
|
-
if (connection)
|
|
172
|
+
if (connection) {
|
|
160
173
|
Object.keys(connection).forEach(async (id) => {
|
|
161
174
|
await this.setStateAsync(id, { val: false, ack: true });
|
|
162
175
|
});
|
|
176
|
+
}
|
|
163
177
|
} catch (error) {
|
|
164
178
|
this.logger.error("Reset connection states - Error", error);
|
|
165
179
|
}
|
|
@@ -174,10 +188,11 @@ class euSec extends utils.Adapter {
|
|
|
174
188
|
];
|
|
175
189
|
for (const sensorName of sensorList) {
|
|
176
190
|
const sensors = await this.getStatesAsync(`*.${(0, import_utils.convertCamelCaseToSnakeCase)(sensorName)}`);
|
|
177
|
-
if (sensors)
|
|
191
|
+
if (sensors) {
|
|
178
192
|
Object.keys(sensors).forEach(async (id) => {
|
|
179
193
|
await this.setStateAsync(id, { val: false, ack: true });
|
|
180
194
|
});
|
|
195
|
+
}
|
|
181
196
|
}
|
|
182
197
|
} catch (error) {
|
|
183
198
|
this.logger.error("Reset sensor states - Error", error);
|
|
@@ -206,31 +221,6 @@ class euSec extends utils.Adapter {
|
|
|
206
221
|
if (this.config.hostname === "") {
|
|
207
222
|
this.config.hostname = Object.values(hosts)[0].native.os.hostname;
|
|
208
223
|
}
|
|
209
|
-
const nodeVersion = Object.values(hosts)[0].native.process.versions.node;
|
|
210
|
-
const nodeMajorVersion = nodeVersion.split(".")[0];
|
|
211
|
-
let fixNeeded;
|
|
212
|
-
switch (parseInt(nodeMajorVersion)) {
|
|
213
|
-
// node 18 is no longer supported
|
|
214
|
-
case 20:
|
|
215
|
-
fixNeeded = nodeVersion.localeCompare("20.11.1", void 0, { numeric: true, sensitivity: "base" });
|
|
216
|
-
break;
|
|
217
|
-
// node 21 is and was not supported
|
|
218
|
-
// node 22 and newer do NOT require any fix
|
|
219
|
-
default:
|
|
220
|
-
fixNeeded = -1;
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
if (fixNeeded >= 0) {
|
|
224
|
-
const adapter = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
|
|
225
|
-
if (adapter !== void 0 && adapter !== null) {
|
|
226
|
-
if (!((_a = adapter.common.nodeProcessParams) == null ? void 0 : _a.includes("--security-revert=CVE-2023-46809"))) {
|
|
227
|
-
adapter.common.nodeProcessParams = ["--security-revert=CVE-2023-46809"];
|
|
228
|
-
await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, adapter);
|
|
229
|
-
this.log.warn("Required fix to use livestreaming with this version of Node.js (CVE-2023-46809) applied. Restart of the adapter initiated to activate the fix.");
|
|
230
|
-
this.restartAdapter();
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
224
|
}
|
|
235
225
|
if (!this.skipInit) {
|
|
236
226
|
const systemConfig = await this.getForeignObjectAsync("system.config");
|
|
@@ -238,8 +228,9 @@ class euSec extends utils.Adapter {
|
|
|
238
228
|
let languageCode = void 0;
|
|
239
229
|
if (systemConfig) {
|
|
240
230
|
countryCode = (0, import_i18n_iso_countries.getAlpha2Code)(systemConfig.common.country, "en");
|
|
241
|
-
if ((0, import_i18n_iso_languages.isValid)(systemConfig.common.language))
|
|
231
|
+
if ((0, import_i18n_iso_languages.isValid)(systemConfig.common.language)) {
|
|
242
232
|
languageCode = systemConfig.common.language;
|
|
233
|
+
}
|
|
243
234
|
}
|
|
244
235
|
if (this.config.country !== "iobroker") {
|
|
245
236
|
countryCode = this.config.country;
|
|
@@ -248,7 +239,9 @@ class euSec extends utils.Adapter {
|
|
|
248
239
|
if (this.persistentData.version !== this.version) {
|
|
249
240
|
const currentVersion = Number.parseFloat((0, import_utils.removeLastChar)(this.version, "."));
|
|
250
241
|
const previousVersion = this.persistentData.version !== "" && this.persistentData.version !== void 0 ? Number.parseFloat((0, import_utils.removeLastChar)(this.persistentData.version, ".")) : 0;
|
|
251
|
-
this.logger.debug(
|
|
242
|
+
this.logger.debug(
|
|
243
|
+
`Handling of adapter update - currentVersion: ${currentVersion} previousVersion: ${previousVersion}`
|
|
244
|
+
);
|
|
252
245
|
if (previousVersion < currentVersion) {
|
|
253
246
|
await (0, import_utils.handleUpdate)(this, this.logger, previousVersion, currentVersion);
|
|
254
247
|
this.persistentData.version = this.version;
|
|
@@ -277,6 +270,7 @@ class euSec extends utils.Adapter {
|
|
|
277
270
|
level: this.log.level === "silly" ? import_eufy_security_client.LogLevel.Trace : this.log.level === "debug" ? import_eufy_security_client.LogLevel.Debug : this.log.level === "info" ? import_eufy_security_client.LogLevel.Info : this.log.level === "warn" ? import_eufy_security_client.LogLevel.Warn : this.log.level === "error" ? import_eufy_security_client.LogLevel.Error : import_eufy_security_client.LogLevel.Info
|
|
278
271
|
}
|
|
279
272
|
};
|
|
273
|
+
(0, import_eufyApiCompat.applyEufyApiCompatibility)((message) => this.logger.info(message));
|
|
280
274
|
this.eufy = await import_eufy_security_client.EufySecurity.initialize(config, this.logger);
|
|
281
275
|
this.eufy.on("persistent data", (data) => this.onPersistentData(data));
|
|
282
276
|
this.eufy.on("station added", (station) => this.onStationAdded(station));
|
|
@@ -290,37 +284,57 @@ class euSec extends utils.Adapter {
|
|
|
290
284
|
this.eufy.on("mqtt close", () => this.onMQTTClose());
|
|
291
285
|
this.eufy.on("connect", () => this.onConnect());
|
|
292
286
|
this.eufy.on("close", () => this.onClose());
|
|
293
|
-
this.eufy.on(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
this.eufy.on(
|
|
298
|
-
|
|
287
|
+
this.eufy.on(
|
|
288
|
+
"device property changed",
|
|
289
|
+
(device, name, value) => this.onDevicePropertyChanged(device, name, value)
|
|
290
|
+
);
|
|
291
|
+
this.eufy.on(
|
|
292
|
+
"station command result",
|
|
293
|
+
(station, result) => this.onStationCommandResult(station, result)
|
|
294
|
+
);
|
|
295
|
+
this.eufy.on(
|
|
296
|
+
"station livestream start",
|
|
297
|
+
(station, device, metadata, videostream, audiostream) => this.onStationLivestreamStart(station, device, metadata, videostream, audiostream)
|
|
298
|
+
);
|
|
299
|
+
this.eufy.on(
|
|
300
|
+
"station livestream stop",
|
|
301
|
+
(station, device) => this.onStationLivestreamStop(station, device)
|
|
302
|
+
);
|
|
303
|
+
this.eufy.on(
|
|
304
|
+
"station rtsp url",
|
|
305
|
+
(station, device, value) => this.onStationRTSPUrl(station, device, value)
|
|
306
|
+
);
|
|
307
|
+
this.eufy.on(
|
|
308
|
+
"station property changed",
|
|
309
|
+
(station, name, value) => this.onStationPropertyChanged(station, name, value)
|
|
310
|
+
);
|
|
299
311
|
this.eufy.on("station connect", (station) => this.onStationConnect(station));
|
|
300
312
|
this.eufy.on("station close", (station) => this.onStationClose(station));
|
|
301
313
|
this.eufy.on("tfa request", () => this.onTFARequest());
|
|
302
|
-
this.eufy.on(
|
|
314
|
+
this.eufy.on(
|
|
315
|
+
"captcha request",
|
|
316
|
+
(captchaId, captcha) => this.onCaptchaRequest(captchaId, captcha)
|
|
317
|
+
);
|
|
303
318
|
this.eufy.setCameraMaxLivestreamDuration(this.config.maxLivestreamDuration);
|
|
304
319
|
await this.eufy.connect();
|
|
305
320
|
if (import_go2rtc_static.default) {
|
|
306
321
|
const go2rtcConfig = {
|
|
307
|
-
|
|
308
|
-
|
|
322
|
+
api: {
|
|
323
|
+
listen: `:${this.config.go2rtc_api_port}`
|
|
309
324
|
},
|
|
310
|
-
|
|
311
|
-
|
|
325
|
+
rtsp: {
|
|
326
|
+
listen: `:${this.config.go2rtc_rtsp_port}`
|
|
312
327
|
},
|
|
313
|
-
|
|
314
|
-
|
|
328
|
+
srtp: {
|
|
329
|
+
listen: `:${this.config.go2rtc_srtp_port}`
|
|
315
330
|
},
|
|
316
|
-
|
|
317
|
-
|
|
331
|
+
webrtc: {
|
|
332
|
+
listen: `:${this.config.go2rtc_webrtc_port}`
|
|
318
333
|
},
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"bin": import_ffmpeg_for_homebridge.default !== "" && import_ffmpeg_for_homebridge.default !== void 0 ? import_ffmpeg_for_homebridge.default : "ffmpeg"
|
|
334
|
+
ffmpeg: {
|
|
335
|
+
bin: import_ffmpeg_for_homebridge.default !== "" && import_ffmpeg_for_homebridge.default !== void 0 ? import_ffmpeg_for_homebridge.default : "ffmpeg"
|
|
322
336
|
},
|
|
323
|
-
|
|
337
|
+
streams: {}
|
|
324
338
|
/*"log": {
|
|
325
339
|
"level": "debug", // default level
|
|
326
340
|
"api": "debug",
|
|
@@ -338,24 +352,7 @@ class euSec extends utils.Adapter {
|
|
|
338
352
|
for (const device of await this.eufy.getDevices()) {
|
|
339
353
|
go2rtcConfig.streams[device.getSerial()] = null;
|
|
340
354
|
}
|
|
341
|
-
|
|
342
|
-
go2rtc.on("error", (error) => {
|
|
343
|
-
this.log.error(`go2rtc error: ${error}`);
|
|
344
|
-
});
|
|
345
|
-
go2rtc.stdout.setEncoding("utf8");
|
|
346
|
-
go2rtc.stdout.on("data", (data) => {
|
|
347
|
-
this.log.info(`go2rtc started: ${data}`);
|
|
348
|
-
});
|
|
349
|
-
go2rtc.stderr.setEncoding("utf8");
|
|
350
|
-
go2rtc.stderr.on("data", (data) => {
|
|
351
|
-
this.log.error(`go2rtc error: ${data}`);
|
|
352
|
-
});
|
|
353
|
-
go2rtc.on("close", (exitcode) => {
|
|
354
|
-
this.log.info(`go2rtc terminated with exitcode ${exitcode}`);
|
|
355
|
-
});
|
|
356
|
-
process.on("exit", () => {
|
|
357
|
-
go2rtc.kill();
|
|
358
|
-
});
|
|
355
|
+
this.startGo2rtc(JSON.stringify(go2rtcConfig));
|
|
359
356
|
}
|
|
360
357
|
}
|
|
361
358
|
const channels = await this.getChannelsAsync();
|
|
@@ -369,6 +366,65 @@ class euSec extends utils.Adapter {
|
|
|
369
366
|
}
|
|
370
367
|
}
|
|
371
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Starts go2rtc and keeps it running. Without this, a crashed go2rtc was only reported with
|
|
371
|
+
* this.log.info() and never restarted, so every livestream failed until the adapter itself was
|
|
372
|
+
* restarted - at the default log level "info" the reason was not even visible.
|
|
373
|
+
*
|
|
374
|
+
* @param go2rtcConfig The serialized go2rtc configuration
|
|
375
|
+
*/
|
|
376
|
+
startGo2rtc(go2rtcConfig) {
|
|
377
|
+
if (!import_go2rtc_static.default || this.terminating) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const startedAt = Date.now();
|
|
381
|
+
const go2rtc = import_node_child_process.default.spawn(import_go2rtc_static.default, ["-config", go2rtcConfig], {
|
|
382
|
+
shell: false,
|
|
383
|
+
detached: false,
|
|
384
|
+
windowsHide: true
|
|
385
|
+
});
|
|
386
|
+
this.go2rtcProcess = go2rtc;
|
|
387
|
+
go2rtc.on("error", (error) => {
|
|
388
|
+
this.log.error(`go2rtc error: ${error}`);
|
|
389
|
+
});
|
|
390
|
+
go2rtc.stdout.setEncoding("utf8");
|
|
391
|
+
go2rtc.stdout.on("data", (data) => {
|
|
392
|
+
this.log.info(`go2rtc: ${data}`);
|
|
393
|
+
});
|
|
394
|
+
go2rtc.stderr.setEncoding("utf8");
|
|
395
|
+
go2rtc.stderr.on("data", (data) => {
|
|
396
|
+
this.log.error(`go2rtc error: ${data}`);
|
|
397
|
+
});
|
|
398
|
+
go2rtc.on("close", (exitcode) => {
|
|
399
|
+
this.go2rtcProcess = void 0;
|
|
400
|
+
if (this.terminating) {
|
|
401
|
+
this.log.info(`go2rtc terminated with exitcode ${exitcode}`);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (Date.now() - startedAt >= GO2RTC_HEALTHY_RUNTIME) {
|
|
405
|
+
this.go2rtcRestarts = 0;
|
|
406
|
+
}
|
|
407
|
+
const delay = Math.min(GO2RTC_RESTART_DELAY_MAX, GO2RTC_RESTART_DELAY_MIN * 2 ** this.go2rtcRestarts);
|
|
408
|
+
this.go2rtcRestarts++;
|
|
409
|
+
this.log.error(
|
|
410
|
+
`go2rtc terminated unexpectedly with exitcode ${exitcode} - livestreaming is unavailable, restarting in ${delay / 1e3}s`
|
|
411
|
+
);
|
|
412
|
+
this.go2rtcRestartTimeout = this.setTimeout(() => {
|
|
413
|
+
this.go2rtcRestartTimeout = void 0;
|
|
414
|
+
this.startGo2rtc(go2rtcConfig);
|
|
415
|
+
}, delay);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
stopGo2rtc() {
|
|
419
|
+
if (this.go2rtcRestartTimeout) {
|
|
420
|
+
this.clearTimeout(this.go2rtcRestartTimeout);
|
|
421
|
+
this.go2rtcRestartTimeout = void 0;
|
|
422
|
+
}
|
|
423
|
+
if (this.go2rtcProcess) {
|
|
424
|
+
this.go2rtcProcess.kill();
|
|
425
|
+
this.go2rtcProcess = void 0;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
372
428
|
async writePersistentData() {
|
|
373
429
|
try {
|
|
374
430
|
await this.writeFileAsync(this.namespace, this.persistentFile, JSON.stringify(this.persistentData));
|
|
@@ -382,14 +438,19 @@ class euSec extends utils.Adapter {
|
|
|
382
438
|
});
|
|
383
439
|
}
|
|
384
440
|
/**
|
|
385
|
-
* Is called when adapter shuts down - callback has to be called under any circumstances!
|
|
441
|
+
* Is called when the adapter shuts down - callback has to be called under any circumstances!
|
|
442
|
+
*
|
|
443
|
+
* @param callback
|
|
386
444
|
*/
|
|
387
445
|
async onUnload(callback) {
|
|
388
446
|
try {
|
|
447
|
+
this.terminating = true;
|
|
448
|
+
this.stopGo2rtc();
|
|
389
449
|
await this.writePersistentData();
|
|
390
450
|
if (this.eufy) {
|
|
391
|
-
if (this.eufy.isConnected())
|
|
451
|
+
if (this.eufy.isConnected()) {
|
|
392
452
|
await this.setStateAsync("info.connection", { val: false, ack: true }).catch();
|
|
453
|
+
}
|
|
393
454
|
this.eufy.removeAllListeners();
|
|
394
455
|
this.eufy.close();
|
|
395
456
|
}
|
|
@@ -414,11 +475,16 @@ class euSec extends utils.Adapter {
|
|
|
414
475
|
// }
|
|
415
476
|
/**
|
|
416
477
|
* Is called if a subscribed state changes
|
|
478
|
+
*
|
|
479
|
+
* @param id
|
|
480
|
+
* @param state
|
|
417
481
|
*/
|
|
418
482
|
async onStateChange(id, state) {
|
|
419
483
|
if (state) {
|
|
420
484
|
if (!id || state.ack) {
|
|
421
|
-
this.logger.debug(
|
|
485
|
+
this.logger.debug(
|
|
486
|
+
`state ${id} changed: ${state.val} (ack = ${state.ack}) was already acknowledged, ignore it...`
|
|
487
|
+
);
|
|
422
488
|
return;
|
|
423
489
|
}
|
|
424
490
|
this.logger.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
|
|
@@ -459,13 +525,13 @@ class euSec extends utils.Adapter {
|
|
|
459
525
|
const station = await this.eufy.getStation(station_sn);
|
|
460
526
|
switch (station_state_name) {
|
|
461
527
|
case import_types.StationStateID.REBOOT:
|
|
462
|
-
|
|
528
|
+
station.rebootHUB();
|
|
463
529
|
break;
|
|
464
530
|
case import_types.StationStateID.TRIGGER_ALARM_SOUND:
|
|
465
|
-
|
|
531
|
+
station.triggerStationAlarmSound(this.config.alarmSoundDuration);
|
|
466
532
|
break;
|
|
467
533
|
case import_types.StationStateID.RESET_ALARM_SOUND:
|
|
468
|
-
|
|
534
|
+
station.resetStationAlarmSound();
|
|
469
535
|
break;
|
|
470
536
|
}
|
|
471
537
|
}
|
|
@@ -479,9 +545,16 @@ class euSec extends utils.Adapter {
|
|
|
479
545
|
if (obj) {
|
|
480
546
|
if (obj.native.name !== void 0) {
|
|
481
547
|
try {
|
|
482
|
-
await this.eufy.setDeviceProperty(
|
|
548
|
+
await this.eufy.setDeviceProperty(
|
|
549
|
+
device_sn,
|
|
550
|
+
obj.native.name,
|
|
551
|
+
obj.common.type === "object" ? JSON.parse(state.val) : state.val
|
|
552
|
+
);
|
|
483
553
|
} catch (error) {
|
|
484
|
-
this.logger.error(
|
|
554
|
+
this.logger.error(
|
|
555
|
+
`Error in setting property value (property: ${obj.native.name} value: ${state.val})`,
|
|
556
|
+
error
|
|
557
|
+
);
|
|
485
558
|
}
|
|
486
559
|
return;
|
|
487
560
|
}
|
|
@@ -497,44 +570,44 @@ class euSec extends utils.Adapter {
|
|
|
497
570
|
await this.stopLivestream(device_sn);
|
|
498
571
|
break;
|
|
499
572
|
case import_types.DeviceStateID.TRIGGER_ALARM_SOUND:
|
|
500
|
-
|
|
573
|
+
station.triggerDeviceAlarmSound(device, this.config.alarmSoundDuration);
|
|
501
574
|
break;
|
|
502
575
|
case import_types.DeviceStateID.RESET_ALARM_SOUND:
|
|
503
|
-
|
|
576
|
+
station.resetDeviceAlarmSound(device);
|
|
504
577
|
break;
|
|
505
578
|
case import_types.DeviceStateID.ROTATE_360:
|
|
506
|
-
|
|
579
|
+
station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.ROTATE360);
|
|
507
580
|
break;
|
|
508
581
|
case import_types.DeviceStateID.PAN_LEFT:
|
|
509
|
-
|
|
582
|
+
station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.LEFT);
|
|
510
583
|
break;
|
|
511
584
|
case import_types.DeviceStateID.PAN_RIGHT:
|
|
512
|
-
|
|
585
|
+
station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.RIGHT);
|
|
513
586
|
break;
|
|
514
587
|
case import_types.DeviceStateID.TILT_UP:
|
|
515
|
-
|
|
588
|
+
station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.UP);
|
|
516
589
|
break;
|
|
517
590
|
case import_types.DeviceStateID.TILT_DOWN:
|
|
518
|
-
|
|
591
|
+
station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.DOWN);
|
|
519
592
|
break;
|
|
520
593
|
case import_types.DeviceStateID.CALIBRATE:
|
|
521
594
|
if (device.isLock()) {
|
|
522
|
-
|
|
595
|
+
station.calibrateLock(device);
|
|
523
596
|
} else {
|
|
524
|
-
|
|
597
|
+
station.calibrate(device);
|
|
525
598
|
}
|
|
526
599
|
break;
|
|
527
600
|
case import_types.DeviceStateID.UNLOCK:
|
|
528
|
-
|
|
601
|
+
station.unlock(device);
|
|
529
602
|
break;
|
|
530
603
|
case import_types.DeviceStateID.SET_DEFAULT_ANGLE:
|
|
531
|
-
|
|
604
|
+
station.setDefaultAngle(device);
|
|
532
605
|
break;
|
|
533
606
|
case import_types.DeviceStateID.SET_PRIVACY_ANGLE:
|
|
534
|
-
|
|
607
|
+
station.setPrivacyAngle(device);
|
|
535
608
|
break;
|
|
536
609
|
case import_types.DeviceStateID.OPEN_BOX:
|
|
537
|
-
|
|
610
|
+
station.open(device);
|
|
538
611
|
break;
|
|
539
612
|
}
|
|
540
613
|
} catch (error) {
|
|
@@ -546,8 +619,10 @@ class euSec extends utils.Adapter {
|
|
|
546
619
|
}
|
|
547
620
|
}
|
|
548
621
|
/**
|
|
549
|
-
* Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
|
|
550
|
-
* Using this method requires "common.message" property to be set to true in io-package.json
|
|
622
|
+
* Some message was sent to this instance over the message box. Used by email, pushover, text2speech, ...
|
|
623
|
+
* Using this method requires the "common.message" property to be set to true in io-package.json
|
|
624
|
+
*
|
|
625
|
+
* @param obj
|
|
551
626
|
*/
|
|
552
627
|
async onMessage(obj) {
|
|
553
628
|
if (typeof obj === "object" && obj.message) {
|
|
@@ -559,58 +634,114 @@ class euSec extends utils.Adapter {
|
|
|
559
634
|
const station = await this.eufy.getStation(obj.message.station_sn);
|
|
560
635
|
const device = await this.eufy.getDevice(obj.message.device_sn);
|
|
561
636
|
if (device.hasCommand(import_eufy_security_client.CommandName.DeviceQuickResponse)) {
|
|
562
|
-
|
|
563
|
-
if (obj.callback)
|
|
564
|
-
this.sendTo(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
637
|
+
station.quickResponse(device, obj.message.voice_id);
|
|
638
|
+
if (obj.callback) {
|
|
639
|
+
this.sendTo(
|
|
640
|
+
obj.from,
|
|
641
|
+
obj.command,
|
|
642
|
+
{
|
|
643
|
+
sended: true,
|
|
644
|
+
// because of back compatibility
|
|
645
|
+
sent: true,
|
|
646
|
+
result: "quickResponse command sent"
|
|
647
|
+
},
|
|
648
|
+
obj.callback
|
|
649
|
+
);
|
|
650
|
+
}
|
|
568
651
|
} else {
|
|
569
|
-
if (obj.callback)
|
|
570
|
-
this.sendTo(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
652
|
+
if (obj.callback) {
|
|
653
|
+
this.sendTo(
|
|
654
|
+
obj.from,
|
|
655
|
+
obj.command,
|
|
656
|
+
{
|
|
657
|
+
sended: false,
|
|
658
|
+
// because of back compatibility
|
|
659
|
+
sent: false,
|
|
660
|
+
result: "quickResponse command not supported by specified device"
|
|
661
|
+
},
|
|
662
|
+
obj.callback
|
|
663
|
+
);
|
|
664
|
+
}
|
|
574
665
|
}
|
|
575
666
|
} catch (error) {
|
|
576
667
|
if (error instanceof import_eufy_security_client.StationNotFoundError) {
|
|
577
|
-
if (obj.callback)
|
|
578
|
-
this.sendTo(
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
668
|
+
if (obj.callback) {
|
|
669
|
+
this.sendTo(
|
|
670
|
+
obj.from,
|
|
671
|
+
obj.command,
|
|
672
|
+
{
|
|
673
|
+
sended: false,
|
|
674
|
+
// because of back compatibility
|
|
675
|
+
sent: false,
|
|
676
|
+
result: "quickResponse command not sent because specified station doesn't exists"
|
|
677
|
+
},
|
|
678
|
+
obj.callback
|
|
679
|
+
);
|
|
680
|
+
}
|
|
582
681
|
} else if (error instanceof import_eufy_security_client.DeviceNotFoundError) {
|
|
583
|
-
if (obj.callback)
|
|
584
|
-
this.sendTo(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
682
|
+
if (obj.callback) {
|
|
683
|
+
this.sendTo(
|
|
684
|
+
obj.from,
|
|
685
|
+
obj.command,
|
|
686
|
+
{
|
|
687
|
+
sended: false,
|
|
688
|
+
// because of back compatibility
|
|
689
|
+
sent: false,
|
|
690
|
+
result: "quickResponse command not sent because specified device doesn't exists"
|
|
691
|
+
},
|
|
692
|
+
obj.callback
|
|
693
|
+
);
|
|
694
|
+
}
|
|
588
695
|
} else {
|
|
589
696
|
throw error;
|
|
590
697
|
}
|
|
591
698
|
}
|
|
592
699
|
} else {
|
|
593
|
-
if (obj.callback)
|
|
594
|
-
this.sendTo(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
700
|
+
if (obj.callback) {
|
|
701
|
+
this.sendTo(
|
|
702
|
+
obj.from,
|
|
703
|
+
obj.command,
|
|
704
|
+
{
|
|
705
|
+
sended: false,
|
|
706
|
+
// because of back compatibility
|
|
707
|
+
sent: false,
|
|
708
|
+
result: "quickResponse command not sent because some required parameters are missing"
|
|
709
|
+
},
|
|
710
|
+
obj.callback
|
|
711
|
+
);
|
|
712
|
+
}
|
|
598
713
|
}
|
|
599
714
|
} else if (obj.command === "getQuickResponseVoices") {
|
|
600
715
|
this.log.debug(`getQuickResponseVoices command - message: ${JSON.stringify(obj.message)}`);
|
|
601
716
|
if (typeof obj.message === "object" && typeof obj.message.device_sn === "string" && obj.message.device_sn !== "") {
|
|
602
717
|
const voices = await this.eufy.getApi().getVoices(obj.message.device_sn);
|
|
603
|
-
if (obj.callback)
|
|
604
|
-
this.sendTo(
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
718
|
+
if (obj.callback) {
|
|
719
|
+
this.sendTo(
|
|
720
|
+
obj.from,
|
|
721
|
+
obj.command,
|
|
722
|
+
{
|
|
723
|
+
sended: true,
|
|
724
|
+
// because of back compatibility
|
|
725
|
+
sent: true,
|
|
726
|
+
result: voices
|
|
727
|
+
},
|
|
728
|
+
obj.callback
|
|
729
|
+
);
|
|
730
|
+
}
|
|
608
731
|
} else {
|
|
609
|
-
if (obj.callback)
|
|
610
|
-
this.sendTo(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
732
|
+
if (obj.callback) {
|
|
733
|
+
this.sendTo(
|
|
734
|
+
obj.from,
|
|
735
|
+
obj.command,
|
|
736
|
+
{
|
|
737
|
+
sended: false,
|
|
738
|
+
// because of back compatibility
|
|
739
|
+
sent: false,
|
|
740
|
+
result: "getQuickResponseVoices command not sent because some required parameters are missing"
|
|
741
|
+
},
|
|
742
|
+
obj.callback
|
|
743
|
+
);
|
|
744
|
+
}
|
|
614
745
|
}
|
|
615
746
|
} else if (obj.command === "snooze") {
|
|
616
747
|
this.log.debug(`snooze command - message: ${JSON.stringify(obj.message)}`);
|
|
@@ -619,47 +750,87 @@ class euSec extends utils.Adapter {
|
|
|
619
750
|
const station = await this.eufy.getStation(obj.message.station_sn);
|
|
620
751
|
const device = await this.eufy.getDevice(obj.message.device_sn);
|
|
621
752
|
if (device.hasCommand(import_eufy_security_client.CommandName.DeviceSnooze)) {
|
|
622
|
-
|
|
753
|
+
station.snooze(device, {
|
|
623
754
|
snooze_time: obj.message.snooze_time,
|
|
624
755
|
snooze_chime: obj.message.snooze_chime,
|
|
625
756
|
snooze_homebase: obj.message.snooze_homebase,
|
|
626
757
|
snooze_motion: obj.message.snooze_motion
|
|
627
758
|
});
|
|
628
|
-
if (obj.callback)
|
|
629
|
-
this.sendTo(
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
759
|
+
if (obj.callback) {
|
|
760
|
+
this.sendTo(
|
|
761
|
+
obj.from,
|
|
762
|
+
obj.command,
|
|
763
|
+
{
|
|
764
|
+
sended: true,
|
|
765
|
+
// because of back compatibility
|
|
766
|
+
sent: true,
|
|
767
|
+
result: "snooze command sent"
|
|
768
|
+
},
|
|
769
|
+
obj.callback
|
|
770
|
+
);
|
|
771
|
+
}
|
|
633
772
|
} else {
|
|
634
|
-
if (obj.callback)
|
|
635
|
-
this.sendTo(
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
773
|
+
if (obj.callback) {
|
|
774
|
+
this.sendTo(
|
|
775
|
+
obj.from,
|
|
776
|
+
obj.command,
|
|
777
|
+
{
|
|
778
|
+
sended: false,
|
|
779
|
+
// because of back compatibility
|
|
780
|
+
sent: false,
|
|
781
|
+
result: "snooze command not supported by specified device"
|
|
782
|
+
},
|
|
783
|
+
obj.callback
|
|
784
|
+
);
|
|
785
|
+
}
|
|
639
786
|
}
|
|
640
787
|
} catch (error) {
|
|
641
788
|
if (error instanceof import_eufy_security_client.StationNotFoundError) {
|
|
642
|
-
if (obj.callback)
|
|
643
|
-
this.sendTo(
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
789
|
+
if (obj.callback) {
|
|
790
|
+
this.sendTo(
|
|
791
|
+
obj.from,
|
|
792
|
+
obj.command,
|
|
793
|
+
{
|
|
794
|
+
sended: false,
|
|
795
|
+
// because of back compatibility
|
|
796
|
+
sent: false,
|
|
797
|
+
result: "snooze command not sent because specified station doesn't exists"
|
|
798
|
+
},
|
|
799
|
+
obj.callback
|
|
800
|
+
);
|
|
801
|
+
}
|
|
647
802
|
} else if (error instanceof import_eufy_security_client.DeviceNotFoundError) {
|
|
648
|
-
if (obj.callback)
|
|
649
|
-
this.sendTo(
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
803
|
+
if (obj.callback) {
|
|
804
|
+
this.sendTo(
|
|
805
|
+
obj.from,
|
|
806
|
+
obj.command,
|
|
807
|
+
{
|
|
808
|
+
sended: false,
|
|
809
|
+
// because of back compatibility
|
|
810
|
+
sent: false,
|
|
811
|
+
result: "snooze command not sent because specified device doesn't exists"
|
|
812
|
+
},
|
|
813
|
+
obj.callback
|
|
814
|
+
);
|
|
815
|
+
}
|
|
653
816
|
} else {
|
|
654
817
|
throw error;
|
|
655
818
|
}
|
|
656
819
|
}
|
|
657
820
|
} else {
|
|
658
|
-
if (obj.callback)
|
|
659
|
-
this.sendTo(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
821
|
+
if (obj.callback) {
|
|
822
|
+
this.sendTo(
|
|
823
|
+
obj.from,
|
|
824
|
+
obj.command,
|
|
825
|
+
{
|
|
826
|
+
sended: false,
|
|
827
|
+
// because of back compatibility
|
|
828
|
+
sent: false,
|
|
829
|
+
result: "snooze command not sent because some required parameters are missing"
|
|
830
|
+
},
|
|
831
|
+
obj.callback
|
|
832
|
+
);
|
|
833
|
+
}
|
|
663
834
|
}
|
|
664
835
|
} else if (obj.command === "chime") {
|
|
665
836
|
this.log.debug(`snooze command - message: ${JSON.stringify(obj.message)}`);
|
|
@@ -667,28 +838,55 @@ class euSec extends utils.Adapter {
|
|
|
667
838
|
try {
|
|
668
839
|
const station = await this.eufy.getStation(obj.message.station_sn);
|
|
669
840
|
if (station.hasCommand(import_eufy_security_client.CommandName.StationChime)) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
841
|
+
station.chimeHomebase(
|
|
842
|
+
obj.message.ringtone !== void 0 && typeof obj.message.ringtone === "number" ? obj.message.ringtone : 0
|
|
843
|
+
);
|
|
844
|
+
if (obj.callback) {
|
|
845
|
+
this.sendTo(
|
|
846
|
+
obj.from,
|
|
847
|
+
obj.command,
|
|
848
|
+
{
|
|
849
|
+
sended: true,
|
|
850
|
+
// because of back compatibility
|
|
851
|
+
sent: true,
|
|
852
|
+
result: "chime command sent"
|
|
853
|
+
},
|
|
854
|
+
obj.callback
|
|
855
|
+
);
|
|
856
|
+
}
|
|
676
857
|
} else {
|
|
677
|
-
if (obj.callback)
|
|
678
|
-
this.sendTo(
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
858
|
+
if (obj.callback) {
|
|
859
|
+
this.sendTo(
|
|
860
|
+
obj.from,
|
|
861
|
+
obj.command,
|
|
862
|
+
{
|
|
863
|
+
sended: false,
|
|
864
|
+
// because of back compatibility
|
|
865
|
+
sent: false,
|
|
866
|
+
result: "chime command not supported by specified station"
|
|
867
|
+
},
|
|
868
|
+
obj.callback
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (obj.callback) {
|
|
873
|
+
this.sendTo(obj.from, obj.command, "chime command sent", obj.callback);
|
|
682
874
|
}
|
|
683
|
-
if (obj.callback)
|
|
684
|
-
this.sendTo(obj.from, obj.command, "chime command sended", obj.callback);
|
|
685
875
|
} catch (error) {
|
|
686
876
|
if (error instanceof import_eufy_security_client.StationNotFoundError) {
|
|
687
|
-
if (obj.callback)
|
|
688
|
-
this.sendTo(
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
877
|
+
if (obj.callback) {
|
|
878
|
+
this.sendTo(
|
|
879
|
+
obj.from,
|
|
880
|
+
obj.command,
|
|
881
|
+
{
|
|
882
|
+
sended: false,
|
|
883
|
+
// because of back compatibility
|
|
884
|
+
sent: false,
|
|
885
|
+
result: "snooze command not sent because specified station doesn't exists"
|
|
886
|
+
},
|
|
887
|
+
obj.callback
|
|
888
|
+
);
|
|
889
|
+
}
|
|
692
890
|
} else {
|
|
693
891
|
throw error;
|
|
694
892
|
}
|
|
@@ -697,28 +895,52 @@ class euSec extends utils.Adapter {
|
|
|
697
895
|
} else if (obj.command === "pollRefresh") {
|
|
698
896
|
this.log.debug(`pollRefresh command`);
|
|
699
897
|
await this.eufy.refreshCloudData();
|
|
700
|
-
if (obj.callback)
|
|
701
|
-
this.sendTo(
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
898
|
+
if (obj.callback) {
|
|
899
|
+
this.sendTo(
|
|
900
|
+
obj.from,
|
|
901
|
+
obj.command,
|
|
902
|
+
{
|
|
903
|
+
sended: true,
|
|
904
|
+
// because of back compatibility
|
|
905
|
+
sent: true,
|
|
906
|
+
result: "pollRefresh command sent"
|
|
907
|
+
},
|
|
908
|
+
obj.callback
|
|
909
|
+
);
|
|
910
|
+
}
|
|
705
911
|
} else {
|
|
706
912
|
const errorMessage = `Received unknown message: ${JSON.stringify(obj.message)}`;
|
|
707
913
|
this.log.warn(errorMessage);
|
|
708
|
-
if (obj.callback)
|
|
709
|
-
this.sendTo(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
914
|
+
if (obj.callback) {
|
|
915
|
+
this.sendTo(
|
|
916
|
+
obj.from,
|
|
917
|
+
obj.command,
|
|
918
|
+
{
|
|
919
|
+
sended: false,
|
|
920
|
+
// because of back compatibility
|
|
921
|
+
sent: false,
|
|
922
|
+
result: errorMessage
|
|
923
|
+
},
|
|
924
|
+
obj.callback
|
|
925
|
+
);
|
|
926
|
+
}
|
|
713
927
|
}
|
|
714
928
|
} catch (error) {
|
|
715
929
|
const errorMessage = `Error during processing of received message: ${error instanceof Error ? `${error.name} - ${error.message}` : error}`;
|
|
716
930
|
this.log.error(errorMessage);
|
|
717
|
-
if (obj.callback)
|
|
718
|
-
this.sendTo(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
931
|
+
if (obj.callback) {
|
|
932
|
+
this.sendTo(
|
|
933
|
+
obj.from,
|
|
934
|
+
obj.command,
|
|
935
|
+
{
|
|
936
|
+
sended: false,
|
|
937
|
+
// because of back compatibility
|
|
938
|
+
sent: false,
|
|
939
|
+
result: errorMessage
|
|
940
|
+
},
|
|
941
|
+
obj.callback
|
|
942
|
+
);
|
|
943
|
+
}
|
|
722
944
|
}
|
|
723
945
|
}
|
|
724
946
|
}
|
|
@@ -772,7 +994,7 @@ class euSec extends utils.Adapter {
|
|
|
772
994
|
obj.native.commandId = property.commandId;
|
|
773
995
|
changed = true;
|
|
774
996
|
}
|
|
775
|
-
if (obj.common !== void 0 && !
|
|
997
|
+
if (obj.common !== void 0 && !import_node_util.default.isDeepStrictEqual(obj.common, state)) {
|
|
776
998
|
changed = true;
|
|
777
999
|
}
|
|
778
1000
|
if (changed) {
|
|
@@ -795,8 +1017,13 @@ class euSec extends utils.Adapter {
|
|
|
795
1017
|
});
|
|
796
1018
|
}
|
|
797
1019
|
const value = device.getPropertyValue(property.name);
|
|
798
|
-
if (value !== void 0)
|
|
799
|
-
await (0, import_utils.setStateChangedAsync)(
|
|
1020
|
+
if (value !== void 0) {
|
|
1021
|
+
await (0, import_utils.setStateChangedAsync)(
|
|
1022
|
+
this,
|
|
1023
|
+
id,
|
|
1024
|
+
(property.type === "string" || property.type === "object") && typeof value === "object" ? JSON.stringify(value) : value
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
800
1027
|
}
|
|
801
1028
|
}
|
|
802
1029
|
async onDeviceAdded(device) {
|
|
@@ -817,8 +1044,9 @@ class euSec extends utils.Adapter {
|
|
|
817
1044
|
});
|
|
818
1045
|
const metadata = device.getPropertiesMetadata();
|
|
819
1046
|
for (const property of Object.values(metadata)) {
|
|
820
|
-
if (property.name !== import_eufy_security_client.PropertyName.DevicePicture)
|
|
821
|
-
this.createAndSetState(device, property);
|
|
1047
|
+
if (property.name !== import_eufy_security_client.PropertyName.DevicePicture) {
|
|
1048
|
+
await this.createAndSetState(device, property);
|
|
1049
|
+
}
|
|
822
1050
|
}
|
|
823
1051
|
if (device.hasProperty(import_eufy_security_client.PropertyName.DevicePicture)) {
|
|
824
1052
|
await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.PICTURE_URL), {
|
|
@@ -1063,11 +1291,16 @@ class euSec extends utils.Adapter {
|
|
|
1063
1291
|
});
|
|
1064
1292
|
}
|
|
1065
1293
|
}
|
|
1066
|
-
|
|
1294
|
+
onDeviceRemoved(device) {
|
|
1067
1295
|
this.delObjectAsync(device.getStateID("", 0), { recursive: true }).catch((error) => {
|
|
1068
1296
|
this.logger.error(`Error deleting states of removed device`, error);
|
|
1069
1297
|
});
|
|
1070
|
-
(0, import_utils.removeFiles)(
|
|
1298
|
+
(0, import_utils.removeFiles)(
|
|
1299
|
+
this,
|
|
1300
|
+
device.getStationSerial(),
|
|
1301
|
+
import_types.DataLocation.LAST_EVENT,
|
|
1302
|
+
device.getSerial()
|
|
1303
|
+
).catch((error) => {
|
|
1071
1304
|
this.logger.error(`Error deleting fs contents of removed device`, error);
|
|
1072
1305
|
});
|
|
1073
1306
|
}
|
|
@@ -1101,7 +1334,7 @@ class euSec extends utils.Adapter {
|
|
|
1101
1334
|
await this.setStateAsync(station.getStateID(import_types.StationStateID.CONNECTION), { val: false, ack: true });
|
|
1102
1335
|
const metadata = station.getPropertiesMetadata();
|
|
1103
1336
|
for (const property of Object.values(metadata)) {
|
|
1104
|
-
this.createAndSetState(station, property);
|
|
1337
|
+
await this.createAndSetState(station, property);
|
|
1105
1338
|
}
|
|
1106
1339
|
if (station.hasCommand(import_eufy_security_client.CommandName.StationReboot)) {
|
|
1107
1340
|
await this.setObjectNotExistsAsync(station.getStateID(import_types.StationStateID.REBOOT), {
|
|
@@ -1141,7 +1374,7 @@ class euSec extends utils.Adapter {
|
|
|
1141
1374
|
});
|
|
1142
1375
|
}
|
|
1143
1376
|
}
|
|
1144
|
-
|
|
1377
|
+
onStationRemoved(station) {
|
|
1145
1378
|
this.delObjectAsync(station.getStateID("", 0), { recursive: true }).catch((error) => {
|
|
1146
1379
|
this.logger.error(`Error deleting states of removed station`, error);
|
|
1147
1380
|
});
|
|
@@ -1177,7 +1410,7 @@ class euSec extends utils.Adapter {
|
|
|
1177
1410
|
this.logger.error(`Device: ${device.getSerial()} - Error`, error);
|
|
1178
1411
|
}
|
|
1179
1412
|
}*/
|
|
1180
|
-
|
|
1413
|
+
handlePushNotification(message) {
|
|
1181
1414
|
try {
|
|
1182
1415
|
if (message.device_sn !== void 0) {
|
|
1183
1416
|
}
|
|
@@ -1220,7 +1453,7 @@ class euSec extends utils.Adapter {
|
|
|
1220
1453
|
}
|
|
1221
1454
|
try {
|
|
1222
1455
|
const allDevices = await this.getDevicesAsync();
|
|
1223
|
-
const reg = new RegExp(`^${this.namespace}
|
|
1456
|
+
const reg = new RegExp(`^${this.namespace}\\.[0-9A-Z]+$`);
|
|
1224
1457
|
for (const id of allDevices) {
|
|
1225
1458
|
if (id._id.match(reg)) {
|
|
1226
1459
|
const serial = id._id.replace(`${this.namespace}.`, "");
|
|
@@ -1234,7 +1467,7 @@ class euSec extends utils.Adapter {
|
|
|
1234
1467
|
}
|
|
1235
1468
|
try {
|
|
1236
1469
|
const allDevices = await this.getDevicesAsync();
|
|
1237
|
-
const reg = new RegExp(`^${this.namespace}
|
|
1470
|
+
const reg = new RegExp(`^${this.namespace}\\.[0-9A-Z]+\\.[a-z]+\\.[0-9A-Z]+$`);
|
|
1238
1471
|
for (const id of allDevices) {
|
|
1239
1472
|
if (id._id.match(reg)) {
|
|
1240
1473
|
const values = id._id.split(".");
|
|
@@ -1263,24 +1496,28 @@ class euSec extends utils.Adapter {
|
|
|
1263
1496
|
try {
|
|
1264
1497
|
const device = await this.eufy.getDevice(deviceSerial);
|
|
1265
1498
|
if (!device.hasProperty(object.native.name)) {
|
|
1266
|
-
this.delObjectAsync(stateid);
|
|
1499
|
+
await this.delObjectAsync(stateid);
|
|
1267
1500
|
}
|
|
1268
1501
|
} catch (error) {
|
|
1269
1502
|
if (error instanceof import_eufy_security_client.DeviceNotFoundError) {
|
|
1270
1503
|
} else {
|
|
1271
|
-
this.log.error(
|
|
1504
|
+
this.log.error(
|
|
1505
|
+
`Delete obsolete properties ERROR - device - ${JSON.stringify(error)}`
|
|
1506
|
+
);
|
|
1272
1507
|
}
|
|
1273
1508
|
}
|
|
1274
1509
|
} else {
|
|
1275
1510
|
try {
|
|
1276
1511
|
const station = await this.eufy.getStation(stationSerial);
|
|
1277
1512
|
if (!station.hasProperty(object.native.name)) {
|
|
1278
|
-
this.delObjectAsync(stateid);
|
|
1513
|
+
await this.delObjectAsync(stateid);
|
|
1279
1514
|
}
|
|
1280
1515
|
} catch (error) {
|
|
1281
1516
|
if (error instanceof import_eufy_security_client.StationNotFoundError) {
|
|
1282
1517
|
} else {
|
|
1283
|
-
this.log.error(
|
|
1518
|
+
this.log.error(
|
|
1519
|
+
`Delete obsolete properties ERROR - station - ${JSON.stringify(error)}`
|
|
1520
|
+
);
|
|
1284
1521
|
}
|
|
1285
1522
|
}
|
|
1286
1523
|
}
|
|
@@ -1298,10 +1535,14 @@ class euSec extends utils.Adapter {
|
|
|
1298
1535
|
await this.delFileAsync(this.namespace, content.file);
|
|
1299
1536
|
} else {
|
|
1300
1537
|
const dirContents = await this.readDirAsync(this.namespace, content.file);
|
|
1301
|
-
for (const dir of dirContents.filter(
|
|
1538
|
+
for (const dir of dirContents.filter(
|
|
1539
|
+
(fn) => Object.values(import_types.DataLocation).includes(fn.file) && fn.isDir
|
|
1540
|
+
)) {
|
|
1302
1541
|
const files = await this.readDirAsync(this.namespace, path.join(content.file, dir.file));
|
|
1303
1542
|
let deletedFiles = 0;
|
|
1304
|
-
for (const file of files.filter(
|
|
1543
|
+
for (const file of files.filter(
|
|
1544
|
+
(fn) => !deviceSerials.includes(fn.file.substring(0, 16)) && !fn.isDir
|
|
1545
|
+
)) {
|
|
1305
1546
|
await this.delFileAsync(this.namespace, path.join(content.file, dir.file, file.file));
|
|
1306
1547
|
deletedFiles++;
|
|
1307
1548
|
}
|
|
@@ -1405,11 +1646,15 @@ class euSec extends utils.Adapter {
|
|
|
1405
1646
|
});
|
|
1406
1647
|
await this.setStateAsync("info.mqtt_connection", { val: false, ack: true });
|
|
1407
1648
|
}
|
|
1408
|
-
|
|
1649
|
+
onStationCommandResult(station, result) {
|
|
1409
1650
|
if (result.return_code !== 0 && result.command_type !== import_eufy_security_client.CommandType.P2P_QUERY_STATUS_IN_LOCK && result.command_type !== import_eufy_security_client.CommandType.CMD_STORAGE_INFO_HB3 && result.command_type !== import_eufy_security_client.CommandType.CMD_SDINFO_EX || result.return_code !== 0 && result.return_code !== import_eufy_security_client.ErrorCode.ERROR_DEV_BUSY && result.command_type === import_eufy_security_client.CommandType.CMD_STORAGE_INFO_HB3) {
|
|
1410
|
-
this.logger.error(
|
|
1651
|
+
this.logger.error(
|
|
1652
|
+
`Station: ${station.getSerial()} command ${import_eufy_security_client.CommandType[result.command_type]} failed with error: ${import_eufy_security_client.ErrorCode[result.return_code]} (${result.return_code})`
|
|
1653
|
+
);
|
|
1411
1654
|
} else if (result.return_code !== 0 && result.return_code !== import_eufy_security_client.TFCardStatus.REMOVE && result.return_code !== import_eufy_security_client.TFCardStatus.BUSY && result.command_type === import_eufy_security_client.CommandType.CMD_SDINFO_EX) {
|
|
1412
|
-
this.logger.error(
|
|
1655
|
+
this.logger.error(
|
|
1656
|
+
`Station: ${station.getSerial()} command ${import_eufy_security_client.CommandType[result.command_type]} failed with error: ${import_eufy_security_client.TFCardStatus[result.return_code]} (${result.return_code})`
|
|
1657
|
+
);
|
|
1413
1658
|
}
|
|
1414
1659
|
}
|
|
1415
1660
|
async onStationPropertyChanged(station, name, value) {
|
|
@@ -1418,12 +1663,18 @@ class euSec extends utils.Adapter {
|
|
|
1418
1663
|
const obj = await this.getObjectAsync(state);
|
|
1419
1664
|
if (obj) {
|
|
1420
1665
|
if (obj.native.name !== void 0 && obj.native.name === name) {
|
|
1421
|
-
await (0, import_utils.setStateChangedAsync)(
|
|
1666
|
+
await (0, import_utils.setStateChangedAsync)(
|
|
1667
|
+
this,
|
|
1668
|
+
state,
|
|
1669
|
+
(obj.common.type === "string" || obj.common.type === "object") && typeof value === "object" ? JSON.stringify(value) : value
|
|
1670
|
+
);
|
|
1422
1671
|
return;
|
|
1423
1672
|
}
|
|
1424
1673
|
}
|
|
1425
1674
|
}
|
|
1426
|
-
this.logger.debug(
|
|
1675
|
+
this.logger.debug(
|
|
1676
|
+
`onStationPropertyChanged(): Property "${name}" not implemented in this adapter (station: ${station.getSerial()} value: ${JSON.stringify(value)})`
|
|
1677
|
+
);
|
|
1427
1678
|
}
|
|
1428
1679
|
async onDevicePropertyChanged(device, name, value) {
|
|
1429
1680
|
const states = await this.getStatesAsync(`${device.getStateID("", 1)}.*`);
|
|
@@ -1431,11 +1682,15 @@ class euSec extends utils.Adapter {
|
|
|
1431
1682
|
const obj = await this.getObjectAsync(state);
|
|
1432
1683
|
if (obj) {
|
|
1433
1684
|
if (obj.native.name !== void 0 && obj.native.name === name) {
|
|
1434
|
-
await (0, import_utils.setStateChangedAsync)(
|
|
1685
|
+
await (0, import_utils.setStateChangedAsync)(
|
|
1686
|
+
this,
|
|
1687
|
+
state,
|
|
1688
|
+
(obj.common.type === "string" || obj.common.type === "object") && typeof value === "object" ? JSON.stringify(value) : value
|
|
1689
|
+
);
|
|
1435
1690
|
switch (name) {
|
|
1436
1691
|
case import_eufy_security_client.PropertyName.DeviceRTSPStream:
|
|
1437
1692
|
if (value === false) {
|
|
1438
|
-
this.delStateAsync(device.getStateID(import_types.DeviceStateID.RTSP_STREAM_URL));
|
|
1693
|
+
await this.delStateAsync(device.getStateID(import_types.DeviceStateID.RTSP_STREAM_URL));
|
|
1439
1694
|
}
|
|
1440
1695
|
break;
|
|
1441
1696
|
}
|
|
@@ -1452,14 +1707,49 @@ class euSec extends utils.Adapter {
|
|
|
1452
1707
|
await this.mkdirAsync(this.namespace, filePath);
|
|
1453
1708
|
}
|
|
1454
1709
|
await this.writeFileAsync(this.namespace, path.join(filePath, fileName), picture.data);
|
|
1455
|
-
await this.setStateAsync(
|
|
1456
|
-
|
|
1710
|
+
await this.setStateAsync(
|
|
1711
|
+
device.getStateID(import_types.DeviceStateID.PICTURE_URL),
|
|
1712
|
+
`/files/${this.namespace}/${device.getStationSerial()}/${import_types.DataLocation.LAST_EVENT}/${device.getSerial()}.${picture.type.ext}`,
|
|
1713
|
+
true
|
|
1714
|
+
);
|
|
1715
|
+
await (0, import_utils.setStateChangedAsync)(
|
|
1716
|
+
this,
|
|
1717
|
+
device.getStateID(import_types.DeviceStateID.PICTURE_HTML),
|
|
1718
|
+
(0, import_utils.getImageAsHTML)(picture.data, picture.type.mime)
|
|
1719
|
+
);
|
|
1457
1720
|
} catch (err) {
|
|
1458
1721
|
const error = (0, import_eufy_security_client.ensureError)(err);
|
|
1459
1722
|
this.logger.error("onDevicePropertyChanged - Property picture - Error", error);
|
|
1460
1723
|
}
|
|
1461
1724
|
} else {
|
|
1462
|
-
this.logger.debug(
|
|
1725
|
+
this.logger.debug(
|
|
1726
|
+
`onDevicePropertyChanged(): Property "${name}" not implemented in this adapter (device: ${device.getSerial()} value: ${JSON.stringify(value)})`
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Tells whether a device streams at the "Auto" quality, where the camera is free to change the
|
|
1732
|
+
* resolution mid-stream. The numeric value of "Auto" is not the same for every device family,
|
|
1733
|
+
* so it is resolved through the state label of the property metadata.
|
|
1734
|
+
*
|
|
1735
|
+
* @param device The device to check
|
|
1736
|
+
* @returns true if the streaming quality is one of the "Auto" settings
|
|
1737
|
+
*/
|
|
1738
|
+
isVideoStreamingQualityAuto(device) {
|
|
1739
|
+
var _a;
|
|
1740
|
+
if (!device.hasProperty(import_eufy_security_client.PropertyName.DeviceVideoStreamingQuality)) {
|
|
1741
|
+
return false;
|
|
1742
|
+
}
|
|
1743
|
+
try {
|
|
1744
|
+
const metadata = device.getPropertyMetadata(
|
|
1745
|
+
import_eufy_security_client.PropertyName.DeviceVideoStreamingQuality
|
|
1746
|
+
);
|
|
1747
|
+
const value = device.getPropertyValue(import_eufy_security_client.PropertyName.DeviceVideoStreamingQuality);
|
|
1748
|
+
const label = (_a = metadata == null ? void 0 : metadata.states) == null ? void 0 : _a[value];
|
|
1749
|
+
return typeof label === "string" && label.startsWith(VIDEO_STREAMING_QUALITY_AUTO_LABEL);
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
this.logger.debug(`Device: ${device.getSerial()} - Could not determine the video streaming quality`, error);
|
|
1752
|
+
return false;
|
|
1463
1753
|
}
|
|
1464
1754
|
}
|
|
1465
1755
|
async startLivestream(device_sn) {
|
|
@@ -1468,12 +1758,21 @@ class euSec extends utils.Adapter {
|
|
|
1468
1758
|
const station = await this.eufy.getStation(device.getStationSerial());
|
|
1469
1759
|
if (station.isConnected() || station.isEnergySavingDevice()) {
|
|
1470
1760
|
if (!station.isLiveStreaming(device)) {
|
|
1471
|
-
this.
|
|
1761
|
+
if (this.isVideoStreamingQualityAuto(device)) {
|
|
1762
|
+
this.logger.warn(
|
|
1763
|
+
`The video streaming quality of device ${device_sn} is set to "Auto". The camera may change the resolution while the stream is running, which browsers using MSE cannot follow (green picture or artifacts). Set a fixed quality if you see this.`
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
await this.eufy.startStationLivestream(device_sn);
|
|
1472
1767
|
} else {
|
|
1473
|
-
this.logger.warn(
|
|
1768
|
+
this.logger.warn(
|
|
1769
|
+
`The stream for the device ${device_sn} cannot be started, because it is already streaming!`
|
|
1770
|
+
);
|
|
1474
1771
|
}
|
|
1475
1772
|
} else {
|
|
1476
|
-
this.logger.warn(
|
|
1773
|
+
this.logger.warn(
|
|
1774
|
+
`The stream for the device ${device_sn} cannot be started, because there is no connection to station ${station.getSerial()}!`
|
|
1775
|
+
);
|
|
1477
1776
|
}
|
|
1478
1777
|
} catch (error) {
|
|
1479
1778
|
this.logger.error("Start livestream - Error", error);
|
|
@@ -1488,7 +1787,9 @@ class euSec extends utils.Adapter {
|
|
|
1488
1787
|
if (await this.eufy.isStationConnected(device.getStationSerial()) && station.isLiveStreaming(camera)) {
|
|
1489
1788
|
await this.eufy.stopStationLivestream(device_sn);
|
|
1490
1789
|
} else {
|
|
1491
|
-
this.logger.warn(
|
|
1790
|
+
this.logger.warn(
|
|
1791
|
+
`The stream for the device ${device_sn} cannot be stopped, because it isn't streaming!`
|
|
1792
|
+
);
|
|
1492
1793
|
}
|
|
1493
1794
|
}
|
|
1494
1795
|
} catch (error) {
|
|
@@ -1497,21 +1798,54 @@ class euSec extends utils.Adapter {
|
|
|
1497
1798
|
}
|
|
1498
1799
|
async onStationLivestreamStart(station, device, metadata, videostream, audiostream) {
|
|
1499
1800
|
try {
|
|
1500
|
-
this.setStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM), {
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
this.logger.debug(`Station: ${station.getSerial()} Device: ${device.getSerial()} - Stopping livestream...`, error);
|
|
1801
|
+
await this.setStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM), {
|
|
1802
|
+
val: `${this.config.https ? "https" : "http"}://${this.config.hostname}:${this.config.go2rtc_api_port}/stream.html?src=${device.getSerial()}`,
|
|
1803
|
+
ack: true
|
|
1504
1804
|
});
|
|
1805
|
+
await this.setStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM_RTSP), {
|
|
1806
|
+
val: `rtsp://${this.config.hostname}:${this.config.go2rtc_rtsp_port}/${device.getSerial()}`,
|
|
1807
|
+
ack: true
|
|
1808
|
+
});
|
|
1809
|
+
const results = await (0, import_video.streamToGo2rtc)(
|
|
1810
|
+
device.getSerial(),
|
|
1811
|
+
videostream,
|
|
1812
|
+
audiostream,
|
|
1813
|
+
this.logger,
|
|
1814
|
+
this.config,
|
|
1815
|
+
this.namespace,
|
|
1816
|
+
metadata
|
|
1817
|
+
);
|
|
1818
|
+
if ((0, import_go2rtc.streamToGo2rtcFailed)(results)) {
|
|
1819
|
+
this.logger.warn(
|
|
1820
|
+
`Station: ${station.getSerial()} Device: ${device.getSerial()} - Streaming to go2rtc failed - Stopping livestream...`
|
|
1821
|
+
);
|
|
1822
|
+
await this.eufy.stopStationLivestream(device.getSerial()).catch((error) => {
|
|
1823
|
+
this.logger.error(
|
|
1824
|
+
`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error during stopping livestream...`,
|
|
1825
|
+
error
|
|
1826
|
+
);
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1505
1829
|
} catch (error) {
|
|
1506
|
-
this.logger.error(
|
|
1507
|
-
|
|
1508
|
-
|
|
1830
|
+
this.logger.error(
|
|
1831
|
+
`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error - Stopping livestream...`,
|
|
1832
|
+
error
|
|
1833
|
+
);
|
|
1834
|
+
this.eufy.stopStationLivestream(device.getSerial()).catch((error2) => {
|
|
1835
|
+
this.logger.error(
|
|
1836
|
+
`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error during stopping livestream...`,
|
|
1837
|
+
error2
|
|
1838
|
+
);
|
|
1509
1839
|
});
|
|
1510
1840
|
}
|
|
1511
1841
|
}
|
|
1512
|
-
onStationLivestreamStop(_station, device) {
|
|
1513
|
-
|
|
1514
|
-
|
|
1842
|
+
async onStationLivestreamStop(_station, device) {
|
|
1843
|
+
try {
|
|
1844
|
+
await this.delStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM));
|
|
1845
|
+
await this.delStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM_RTSP));
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
this.log.error(`Cannot delete: ${error}`);
|
|
1848
|
+
}
|
|
1515
1849
|
}
|
|
1516
1850
|
/*private async onStationDownloadFinish(_station: Station, _device: Device): Promise<void> {
|
|
1517
1851
|
//this.logger.trace(`Station: ${station.getSerial()} channel: ${channel}`);
|
|
@@ -1566,8 +1900,8 @@ class euSec extends utils.Adapter {
|
|
|
1566
1900
|
await this.eufy.cancelStationDownload(device.getSerial());
|
|
1567
1901
|
}
|
|
1568
1902
|
}*/
|
|
1569
|
-
onStationRTSPUrl(station, device, value) {
|
|
1570
|
-
(0, import_utils.setStateChangedAsync)(this, device.getStateID(import_types.DeviceStateID.RTSP_STREAM_URL), value);
|
|
1903
|
+
async onStationRTSPUrl(station, device, value) {
|
|
1904
|
+
await (0, import_utils.setStateChangedAsync)(this, device.getStateID(import_types.DeviceStateID.RTSP_STREAM_URL), value);
|
|
1571
1905
|
}
|
|
1572
1906
|
async onStationConnect(station) {
|
|
1573
1907
|
await this.setObjectNotExistsAsync(station.getStateID(import_types.StationStateID.CONNECTION), {
|
|
@@ -1596,16 +1930,30 @@ class euSec extends utils.Adapter {
|
|
|
1596
1930
|
native: {}
|
|
1597
1931
|
});
|
|
1598
1932
|
await this.setStateAsync(station.getStateID(import_types.StationStateID.CONNECTION), { val: false, ack: true });
|
|
1933
|
+
try {
|
|
1934
|
+
for (const device of await this.eufy.getDevicesFromStation(station.getSerial())) {
|
|
1935
|
+
await this.delStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM)).catch(() => {
|
|
1936
|
+
});
|
|
1937
|
+
await this.delStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM_RTSP)).catch(() => {
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
} catch (error) {
|
|
1941
|
+
this.logger.error(`Station: ${station.getSerial()} - Error while clearing livestream states`, error);
|
|
1942
|
+
}
|
|
1599
1943
|
}
|
|
1600
1944
|
onTFARequest() {
|
|
1601
|
-
this.logger.warn(
|
|
1945
|
+
this.logger.warn(
|
|
1946
|
+
`Two factor authentication request received, please enter valid verification code in state ${this.namespace}.verify_code`
|
|
1947
|
+
);
|
|
1602
1948
|
this.verify_code = true;
|
|
1603
1949
|
}
|
|
1604
|
-
onCaptchaRequest(captchaId, captcha) {
|
|
1950
|
+
async onCaptchaRequest(captchaId, captcha) {
|
|
1605
1951
|
this.captchaId = captchaId;
|
|
1606
|
-
this.logger.warn(
|
|
1952
|
+
this.logger.warn(
|
|
1953
|
+
`Captcha authentication request received, please enter valid captcha in state ${this.namespace}.captcha`
|
|
1954
|
+
);
|
|
1607
1955
|
this.logger.warn(`Captcha: <img src="${captcha}">`);
|
|
1608
|
-
this.setStateAsync("received_captcha_html", { val: `<img src="${captcha}">`, ack: true });
|
|
1956
|
+
await this.setStateAsync("received_captcha_html", { val: `<img src="${captcha}">`, ack: true });
|
|
1609
1957
|
}
|
|
1610
1958
|
}
|
|
1611
1959
|
if (require.main !== module) {
|