iobroker.eusec 1.1.0 → 1.1.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/build/main.js CHANGED
@@ -1,1481 +1,1237 @@
1
1
  "use strict";
2
- /*
3
- * Created with @iobroker/create-adapter v1.28.0
4
- */
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || function (mod) {
22
- if (mod && mod.__esModule) return mod;
23
- var result = {};
24
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
25
- __setModuleDefault(result, mod);
26
- return result;
27
- };
28
- var __importDefault = (this && this.__importDefault) || function (mod) {
29
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
30
15
  };
31
- Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.euSec = void 0;
33
- const utils = __importStar(require("@iobroker/adapter-core"));
34
- const path = __importStar(require("path"));
35
- const eufy_security_client_1 = require("eufy-security-client");
36
- const i18n_iso_countries_1 = require("i18n-iso-countries");
37
- const i18n_iso_languages_1 = require("@cospired/i18n-iso-languages");
38
- const fs_extra_1 = __importDefault(require("fs-extra"));
39
- const util_1 = __importDefault(require("util"));
40
- const child_process_1 = __importDefault(require("child_process"));
41
- const go2rtc_static_1 = __importDefault(require("go2rtc-static"));
42
- const os_1 = __importDefault(require("os"));
43
- const types_1 = require("./lib/types");
44
- const utils_1 = require("./lib/utils");
45
- const log_1 = require("./lib/log");
46
- const video_1 = require("./lib/video");
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
18
+ mod
19
+ ));
20
+ var utils = __toESM(require("@iobroker/adapter-core"));
21
+ var path = __toESM(require("path"));
22
+ var import_eufy_security_client = require("eufy-security-client");
23
+ var import_i18n_iso_countries = require("i18n-iso-countries");
24
+ var import_i18n_iso_languages = require("@cospired/i18n-iso-languages");
25
+ var import_fs_extra = __toESM(require("fs-extra"));
26
+ var import_util = __toESM(require("util"));
27
+ var import_child_process = __toESM(require("child_process"));
28
+ var import_go2rtc_static = __toESM(require("go2rtc-static"));
29
+ var import_os = __toESM(require("os"));
30
+ var import_types = require("./lib/types");
31
+ var import_utils = require("./lib/utils");
32
+ var import_log = require("./lib/log");
33
+ var import_video = require("./lib/video");
47
34
  class euSec extends utils.Adapter {
48
- constructor(options = {}) {
49
- super({
50
- ...options,
51
- name: "eusec",
35
+ constructor(options = {}) {
36
+ super({
37
+ ...options,
38
+ name: "eusec"
39
+ });
40
+ this.persistentData = {
41
+ version: ""
42
+ };
43
+ this.captchaId = null;
44
+ this.verify_code = false;
45
+ const data_dir = utils.getAbsoluteInstanceDataDir(this);
46
+ this.persistentFile = path.join(data_dir, "adapter.json");
47
+ if (!import_fs_extra.default.existsSync(data_dir))
48
+ import_fs_extra.default.mkdirSync(data_dir);
49
+ this.on("ready", this.onReady.bind(this));
50
+ this.on("stateChange", this.onStateChange.bind(this));
51
+ this.on("unload", this.onUnload.bind(this));
52
+ }
53
+ async onReady() {
54
+ this.logger = new import_log.ioBrokerLogger(this.log);
55
+ await this.setObjectNotExistsAsync("verify_code", {
56
+ type: "state",
57
+ common: {
58
+ name: "2FA verification code",
59
+ type: "string",
60
+ role: "state",
61
+ read: true,
62
+ write: true
63
+ },
64
+ native: {}
65
+ });
66
+ await this.setObjectNotExistsAsync("received_captcha_html", {
67
+ type: "state",
68
+ common: {
69
+ name: "Received captcha image HTML",
70
+ type: "string",
71
+ role: "state",
72
+ read: true,
73
+ write: false
74
+ },
75
+ native: {}
76
+ });
77
+ await this.setObjectNotExistsAsync("captcha", {
78
+ type: "state",
79
+ common: {
80
+ name: "Enter captcha",
81
+ type: "string",
82
+ role: "state",
83
+ read: true,
84
+ write: true
85
+ },
86
+ native: {}
87
+ });
88
+ await this.setObjectNotExistsAsync("info", {
89
+ type: "channel",
90
+ common: {
91
+ name: "info"
92
+ },
93
+ native: {}
94
+ });
95
+ await this.setObjectNotExistsAsync("info.connection", {
96
+ type: "state",
97
+ common: {
98
+ name: "Global connection",
99
+ type: "boolean",
100
+ role: "indicator.connection",
101
+ read: true,
102
+ write: false
103
+ },
104
+ native: {}
105
+ });
106
+ await this.setStateAsync("info.connection", { val: false, ack: true });
107
+ await this.setObjectNotExistsAsync("info.push_connection", {
108
+ type: "state",
109
+ common: {
110
+ name: "Push notification connection",
111
+ type: "boolean",
112
+ role: "indicator.connection",
113
+ read: true,
114
+ write: false
115
+ },
116
+ native: {}
117
+ });
118
+ await this.setStateAsync("info.push_connection", { val: false, ack: true });
119
+ await this.setObjectNotExistsAsync("info.mqtt_connection", {
120
+ type: "state",
121
+ common: {
122
+ name: "MQTT connection",
123
+ type: "boolean",
124
+ role: "indicator.connection",
125
+ read: true,
126
+ write: false
127
+ },
128
+ native: {}
129
+ });
130
+ await this.setStateAsync("info.mqtt_connection", { val: false, ack: true });
131
+ try {
132
+ const connection = await this.getStatesAsync("*.connection");
133
+ if (connection)
134
+ Object.keys(connection).forEach(async (id) => {
135
+ await this.setStateAsync(id, { val: false, ack: true });
52
136
  });
53
- this.persistentData = {
54
- version: ""
55
- };
56
- this.captchaId = null;
57
- this.verify_code = false;
58
- const data_dir = utils.getAbsoluteInstanceDataDir(this);
59
- this.persistentFile = path.join(data_dir, "adapter.json");
60
- if (!fs_extra_1.default.existsSync(data_dir))
61
- fs_extra_1.default.mkdirSync(data_dir);
62
- this.on("ready", this.onReady.bind(this));
63
- this.on("stateChange", this.onStateChange.bind(this));
64
- // this.on("objectChange", this.onObjectChange.bind(this));
65
- // this.on("message", this.onMessage.bind(this));
66
- this.on("unload", this.onUnload.bind(this));
137
+ } catch (error) {
138
+ this.logger.error("Reset connection states - Error", error);
67
139
  }
68
- /**
69
- * Is called when databases are connected and adapter received configuration.
70
- */
71
- async onReady() {
72
- this.logger = new log_1.ioBrokerLogger(this.log);
73
- await this.setObjectNotExistsAsync("verify_code", {
74
- type: "state",
75
- common: {
76
- name: "2FA verification code",
77
- type: "string",
78
- role: "state",
79
- read: true,
80
- write: true,
81
- },
82
- native: {},
83
- });
84
- await this.setObjectNotExistsAsync("received_captcha_html", {
85
- type: "state",
86
- common: {
87
- name: "Received captcha image HTML",
88
- type: "string",
89
- role: "state",
90
- read: true,
91
- write: false,
92
- },
93
- native: {},
94
- });
95
- await this.setObjectNotExistsAsync("captcha", {
96
- type: "state",
97
- common: {
98
- name: "Enter captcha",
99
- type: "string",
100
- role: "state",
101
- read: true,
102
- write: true,
103
- },
104
- native: {},
105
- });
106
- await this.setObjectNotExistsAsync("info", {
107
- type: "channel",
108
- common: {
109
- name: "info"
110
- },
111
- native: {},
112
- });
113
- await this.setObjectNotExistsAsync("info.connection", {
114
- type: "state",
115
- common: {
116
- name: "Global connection",
117
- type: "boolean",
118
- role: "indicator.connection",
119
- read: true,
120
- write: false,
121
- },
122
- native: {},
123
- });
124
- await this.setStateAsync("info.connection", { val: false, ack: true });
125
- await this.setObjectNotExistsAsync("info.push_connection", {
126
- type: "state",
127
- common: {
128
- name: "Push notification connection",
129
- type: "boolean",
130
- role: "indicator.connection",
131
- read: true,
132
- write: false,
133
- },
134
- native: {},
135
- });
136
- await this.setStateAsync("info.push_connection", { val: false, ack: true });
137
- await this.setObjectNotExistsAsync("info.mqtt_connection", {
138
- type: "state",
139
- common: {
140
- name: "MQTT connection",
141
- type: "boolean",
142
- role: "indicator.connection",
143
- read: true,
144
- write: false,
145
- },
146
- native: {},
147
- });
148
- await this.setStateAsync("info.mqtt_connection", { val: false, ack: true });
149
- try {
150
- const connection = await this.getStatesAsync("*.connection");
151
- if (connection)
152
- Object.keys(connection).forEach(async (id) => {
153
- await this.setStateAsync(id, { val: false, ack: true });
154
- });
155
- }
156
- catch (error) {
157
- this.logger.error("Reset connection states - Error", error);
158
- }
159
- try {
160
- const sensorList = [
161
- eufy_security_client_1.PropertyName.DeviceMotionDetected,
162
- eufy_security_client_1.PropertyName.DevicePersonDetected,
163
- eufy_security_client_1.PropertyName.DeviceSoundDetected,
164
- eufy_security_client_1.PropertyName.DeviceCryingDetected,
165
- eufy_security_client_1.PropertyName.DevicePetDetected,
166
- eufy_security_client_1.PropertyName.DeviceRinging
167
- ];
168
- for (const sensorName of sensorList) {
169
- const sensors = await this.getStatesAsync(`*.${(0, utils_1.convertCamelCaseToSnakeCase)(sensorName)}`);
170
- if (sensors)
171
- Object.keys(sensors).forEach(async (id) => {
172
- await this.setStateAsync(id, { val: false, ack: true });
173
- });
174
- }
175
- }
176
- catch (error) {
177
- this.logger.error("Reset sensor states - Error", error);
178
- }
179
- try {
180
- if (fs_extra_1.default.statSync(this.persistentFile).isFile()) {
181
- const fileContent = fs_extra_1.default.readFileSync(this.persistentFile, "utf8");
182
- this.persistentData = JSON.parse(fileContent);
183
- }
184
- }
185
- catch (error) {
186
- this.logger.debug("No stored data from last exit found.", error);
187
- }
188
- this.subscribeStates("verify_code");
189
- this.subscribeStates("captcha");
190
- const systemConfig = await this.getForeignObjectAsync("system.config");
191
- let countryCode = undefined;
192
- let languageCode = undefined;
193
- if (systemConfig) {
194
- countryCode = (0, i18n_iso_countries_1.getAlpha2Code)(systemConfig.common.country, "en");
195
- if ((0, i18n_iso_languages_1.isValid)(systemConfig.common.language))
196
- languageCode = systemConfig.common.language;
197
- }
198
- if (this.config.hostname === "") {
199
- this.config.hostname = os_1.default.hostname();
200
- }
201
- // Handling adapter version update
202
- try {
203
- if (this.persistentData.version !== this.version) {
204
- const currentVersion = Number.parseFloat((0, utils_1.removeLastChar)(this.version, "."));
205
- const previousVersion = this.persistentData.version !== "" && this.persistentData.version !== undefined ? Number.parseFloat((0, utils_1.removeLastChar)(this.persistentData.version, ".")) : 0;
206
- this.logger.debug(`Handling of adapter update - currentVersion: ${currentVersion} previousVersion: ${previousVersion}`);
207
- if (previousVersion < currentVersion) {
208
- await (0, utils_1.handleUpdate)(this, this.logger, previousVersion);
209
- this.persistentData.version = this.version;
210
- this.writePersistentData();
211
- }
212
- }
213
- }
214
- catch (error) {
215
- this.logger.error(`Handling of adapter update - Error:`, error);
216
- }
217
- let connectionType = eufy_security_client_1.P2PConnectionType.QUICKEST;
218
- if (this.config.p2pConnectionType === "only_local") {
219
- connectionType = eufy_security_client_1.P2PConnectionType.ONLY_LOCAL;
220
- }
221
- const config = {
222
- username: this.config.username,
223
- password: this.config.password,
224
- country: countryCode,
225
- language: languageCode,
226
- persistentDir: utils.getAbsoluteInstanceDataDir(this),
227
- eventDurationSeconds: this.config.eventDuration,
228
- p2pConnectionSetup: connectionType,
229
- pollingIntervalMinutes: this.config.pollingInterval,
230
- acceptInvitations: this.config.acceptInvitations,
231
- };
232
- this.eufy = await eufy_security_client_1.EufySecurity.initialize(config, this.logger);
233
- this.eufy.on("station added", (station) => this.onStationAdded(station));
234
- this.eufy.on("device added", (device) => this.onDeviceAdded(device));
235
- this.eufy.on("station removed", (station) => this.onStationRemoved(station));
236
- this.eufy.on("device removed", (device) => this.onDeviceRemoved(device));
237
- this.eufy.on("push message", (messages) => this.handlePushNotification(messages));
238
- this.eufy.on("push connect", () => this.onPushConnect());
239
- this.eufy.on("push close", () => this.onPushClose());
240
- this.eufy.on("mqtt connect", () => this.onMQTTConnect());
241
- this.eufy.on("mqtt close", () => this.onMQTTClose());
242
- this.eufy.on("connect", () => this.onConnect());
243
- this.eufy.on("close", () => this.onClose());
244
- this.eufy.on("device property changed", (device, name, value) => this.onDevicePropertyChanged(device, name, value));
245
- this.eufy.on("station command result", (station, result) => this.onStationCommandResult(station, result));
246
- //this.eufy.on("station download start", (station: Station, device: Device, metadata: StreamMetadata, videostream: Readable, audiostream: Readable) => this.onStationDownloadStart(station, device, metadata, videostream, audiostream));
247
- //this.eufy.on("station download finish", (station: Station, device: Device) => this.onStationDownloadFinish(station, device));
248
- this.eufy.on("station livestream start", (station, device, metadata, videostream, audiostream) => this.onStationLivestreamStart(station, device, metadata, videostream, audiostream));
249
- this.eufy.on("station livestream stop", (station, device) => this.onStationLivestreamStop(station, device));
250
- this.eufy.on("station rtsp url", (station, device, value) => this.onStationRTSPUrl(station, device, value));
251
- this.eufy.on("station property changed", (station, name, value) => this.onStationPropertyChanged(station, name, value));
252
- this.eufy.on("station connect", (station) => this.onStationConnect(station));
253
- this.eufy.on("station close", (station) => this.onStationClose(station));
254
- this.eufy.on("tfa request", () => this.onTFARequest());
255
- this.eufy.on("captcha request", (captchaId, captcha) => this.onCaptchaRequest(captchaId, captcha));
256
- this.eufy.setCameraMaxLivestreamDuration(this.config.maxLivestreamDuration);
257
- await this.eufy.connect();
258
- if (go2rtc_static_1.default) {
259
- const go2rtcConfig = {
260
- "api": {
261
- "listen": `:${this.config.go2rtc_api_port}`
262
- },
263
- "rtsp": {
264
- "listen": `:${this.config.go2rtc_rtsp_port}`
265
- },
266
- "srtp": {
267
- "listen": `:${this.config.go2rtc_srtp_port}`
268
- },
269
- "webrtc": {
270
- "listen": `:${this.config.go2rtc_webrtc_port}`
271
- },
272
- "streams": {}
273
- };
274
- if (this.config.go2rtc_rtsp_username !== "" && this.config.go2rtc_rtsp_password !== "") {
275
- go2rtcConfig.rtsp.username = this.config.go2rtc_rtsp_username;
276
- go2rtcConfig.rtsp.password = this.config.go2rtc_rtsp_password;
277
- }
278
- for (const device of await this.eufy.getDevices()) {
279
- go2rtcConfig.streams[device.getSerial()] = null;
280
- }
281
- const go2rtc = child_process_1.default.spawn(go2rtc_static_1.default, ["-config", JSON.stringify(go2rtcConfig)], { shell: false, detached: false, windowsHide: true });
282
- go2rtc.on("error", (error) => {
283
- this.log.error(`go2rtc error: ${error}`);
284
- });
285
- go2rtc.stdout.setEncoding("utf8");
286
- go2rtc.stdout.on("data", (data) => {
287
- this.log.info(`go2rtc started: ${data}`);
288
- });
289
- go2rtc.stderr.setEncoding("utf8");
290
- go2rtc.stderr.on("data", (data) => {
291
- this.log.error(`go2rtc error: ${data}`);
292
- });
293
- go2rtc.on("close", (exitcode) => {
294
- this.log.info(`go2rtc terminated with exitcode ${exitcode}`);
295
- });
296
- process.on("exit", () => {
297
- go2rtc.kill();
298
- });
299
- }
140
+ try {
141
+ const sensorList = [
142
+ import_eufy_security_client.PropertyName.DeviceMotionDetected,
143
+ import_eufy_security_client.PropertyName.DevicePersonDetected,
144
+ import_eufy_security_client.PropertyName.DeviceSoundDetected,
145
+ import_eufy_security_client.PropertyName.DeviceCryingDetected,
146
+ import_eufy_security_client.PropertyName.DevicePetDetected,
147
+ import_eufy_security_client.PropertyName.DeviceRinging
148
+ ];
149
+ for (const sensorName of sensorList) {
150
+ const sensors = await this.getStatesAsync(`*.${(0, import_utils.convertCamelCaseToSnakeCase)(sensorName)}`);
151
+ if (sensors)
152
+ Object.keys(sensors).forEach(async (id) => {
153
+ await this.setStateAsync(id, { val: false, ack: true });
154
+ });
155
+ }
156
+ } catch (error) {
157
+ this.logger.error("Reset sensor states - Error", error);
300
158
  }
301
- writePersistentData() {
302
- try {
303
- fs_extra_1.default.writeFileSync(this.persistentFile, JSON.stringify(this.persistentData));
304
- }
305
- catch (error) {
306
- this.logger.error(`writePersistentData() - Error: ${error}`);
307
- }
159
+ try {
160
+ if (import_fs_extra.default.statSync(this.persistentFile).isFile()) {
161
+ const fileContent = import_fs_extra.default.readFileSync(this.persistentFile, "utf8");
162
+ this.persistentData = JSON.parse(fileContent);
163
+ }
164
+ } catch (error) {
165
+ this.logger.debug("No stored data from last exit found.", error);
308
166
  }
309
- /**
310
- * Is called when adapter shuts down - callback has to be called under any circumstances!
311
- */
312
- async onUnload(callback) {
313
- try {
314
- this.writePersistentData();
315
- if (this.eufy) {
316
- if (this.eufy.isConnected())
317
- await this.setStateAsync("info.connection", { val: false, ack: true }).catch();
318
- this.eufy.removeAllListeners();
319
- this.eufy.close();
320
- }
321
- callback();
322
- }
323
- catch (e) {
324
- callback();
325
- }
167
+ this.subscribeStates("verify_code");
168
+ this.subscribeStates("captcha");
169
+ const systemConfig = await this.getForeignObjectAsync("system.config");
170
+ let countryCode = void 0;
171
+ let languageCode = void 0;
172
+ if (systemConfig) {
173
+ countryCode = (0, import_i18n_iso_countries.getAlpha2Code)(systemConfig.common.country, "en");
174
+ if ((0, import_i18n_iso_languages.isValid)(systemConfig.common.language))
175
+ languageCode = systemConfig.common.language;
326
176
  }
327
- // If you need to react to object changes, uncomment the following block and the corresponding line in the constructor.
328
- // You also need to subscribe to the objects with `this.subscribeObjects`, similar to `this.subscribeStates`.
329
- // /**
330
- // * Is called if a subscribed object changes
331
- // */
332
- // private onObjectChange(id: string, obj: ioBroker.Object | null | undefined): void {
333
- // if (obj) {
334
- // // The object was changed
335
- // this.log.info(`object ${id} changed: ${JSON.stringify(obj)}`);
336
- // } else {
337
- // // The object was deleted
338
- // this.log.info(`object ${id} deleted`);
339
- // }
340
- // }
341
- /**
342
- * Is called if a subscribed state changes
343
- */
344
- async onStateChange(id, state) {
345
- if (state) {
346
- // don't do anything if the state is acked
347
- if (!id || state.ack) {
348
- this.logger.debug(`state ${id} changed: ${state.val} (ack = ${state.ack}) was already acknowledged, ignore it...`);
349
- return;
350
- }
351
- this.logger.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
352
- const values = id.split(".");
353
- const station_sn = values[2];
354
- const device_type = values[3];
355
- if (station_sn == "verify_code") {
356
- if (this.eufy && this.verify_code) {
357
- this.logger.info(`Verification code received, send it. (verify_code: ${state.val})`);
358
- await this.eufy.connect({ verifyCode: state.val });
359
- this.verify_code = false;
360
- await this.delStateAsync(id);
361
- }
362
- }
363
- else if (station_sn == "captcha") {
364
- if (this.eufy && this.captchaId) {
365
- this.logger.info(`Captcha received, send it. (captcha: ${state.val})`);
366
- await this.eufy.connect({
367
- captcha: {
368
- captchaCode: state.val,
369
- captchaId: this.captchaId
370
- }
371
- });
372
- this.captchaId = null;
373
- await this.delStateAsync(id);
374
- await this.delStateAsync("received_captcha_html");
375
- }
376
- }
377
- else if (device_type == "station") {
378
- try {
379
- const station_state_name = values[4];
380
- if (this.eufy) {
381
- const obj = await this.getObjectAsync(id);
382
- if (obj) {
383
- if (obj.native.name !== undefined) {
384
- await this.eufy.setStationProperty(station_sn, obj.native.name, state.val);
385
- return;
386
- }
387
- }
388
- const station = await this.eufy.getStation(station_sn);
389
- switch (station_state_name) {
390
- case types_1.StationStateID.REBOOT:
391
- await station.rebootHUB();
392
- break;
393
- case types_1.StationStateID.TRIGGER_ALARM_SOUND:
394
- await station.triggerStationAlarmSound(this.config.alarmSoundDuration);
395
- break;
396
- case types_1.StationStateID.RESET_ALARM_SOUND:
397
- await station.resetStationAlarmSound();
398
- break;
399
- }
400
- }
401
- }
402
- catch (error) {
403
- this.logger.error(`station - Error:`, error);
404
- }
405
- }
406
- else {
407
- try {
408
- const device_sn = values[4];
409
- const obj = await this.getObjectAsync(id);
410
- if (obj) {
411
- if (obj.native.name !== undefined) {
412
- try {
413
- await this.eufy.setDeviceProperty(device_sn, obj.native.name, state.val);
414
- }
415
- catch (error) {
416
- this.logger.error(`Error in setting property value (property: ${obj.native.name} value: ${state.val})`, error);
417
- }
418
- return;
419
- }
420
- }
421
- const device_state_name = values[5];
422
- const station = await this.eufy.getStation(station_sn);
423
- const device = await this.eufy.getDevice(device_sn);
424
- switch (device_state_name) {
425
- case types_1.DeviceStateID.START_STREAM:
426
- await this.startLivestream(device_sn);
427
- break;
428
- case types_1.DeviceStateID.STOP_STREAM:
429
- await this.stopLivestream(device_sn);
430
- break;
431
- case types_1.DeviceStateID.TRIGGER_ALARM_SOUND:
432
- await station.triggerDeviceAlarmSound(device, this.config.alarmSoundDuration);
433
- break;
434
- case types_1.DeviceStateID.RESET_ALARM_SOUND:
435
- await station.resetDeviceAlarmSound(device);
436
- break;
437
- case types_1.DeviceStateID.ROTATE_360:
438
- await station.panAndTilt(device, eufy_security_client_1.PanTiltDirection.ROTATE360);
439
- break;
440
- case types_1.DeviceStateID.PAN_LEFT:
441
- await station.panAndTilt(device, eufy_security_client_1.PanTiltDirection.LEFT);
442
- break;
443
- case types_1.DeviceStateID.PAN_RIGHT:
444
- await station.panAndTilt(device, eufy_security_client_1.PanTiltDirection.RIGHT);
445
- break;
446
- case types_1.DeviceStateID.TILT_UP:
447
- await station.panAndTilt(device, eufy_security_client_1.PanTiltDirection.UP);
448
- break;
449
- case types_1.DeviceStateID.TILT_DOWN:
450
- await station.panAndTilt(device, eufy_security_client_1.PanTiltDirection.DOWN);
451
- break;
452
- case types_1.DeviceStateID.CALIBRATE:
453
- if (device.isLock()) {
454
- await station.calibrateLock(device);
455
- }
456
- else {
457
- await station.calibrate(device);
458
- }
459
- break;
460
- case types_1.DeviceStateID.UNLOCK:
461
- await station.unlock(device);
462
- break;
463
- case types_1.DeviceStateID.SET_DEFAULT_ANGLE:
464
- await station.setDefaultAngle(device);
465
- break;
466
- case types_1.DeviceStateID.SET_PRIVACY_ANGLE:
467
- await station.setPrivacyAngle(device);
468
- break;
469
- }
470
- }
471
- catch (error) {
472
- this.logger.error(`cameras - Error:`, error);
473
- }
474
- }
475
- }
476
- else {
477
- // The state was deleted
478
- this.logger.debug(`state ${id} deleted`);
479
- }
177
+ if (this.config.hostname === "") {
178
+ this.config.hostname = import_os.default.hostname();
480
179
  }
481
- // If you need to accept messages in your adapter, uncomment the following block and the corresponding line in the constructor.
482
- // /**
483
- // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
484
- // * Using this method requires "common.message" property to be set to true in io-package.json
485
- // */
486
- // private onMessage(obj: ioBroker.Message): void {
487
- // if (typeof obj === "object" && obj.message) {
488
- // if (obj.command === "send") {
489
- // // e.g. send email or pushover or whatever
490
- // this.log.info("send command");
491
- // // Send response in callback if required
492
- // if (obj.callback) this.sendTo(obj.from, obj.command, "Message received", obj.callback);
493
- // }
494
- // }
495
- // }
496
- getStateCommon(property) {
497
- const state = {
498
- name: property.label,
499
- type: property.type,
500
- role: "state",
501
- read: property.readable,
502
- write: property.writeable,
503
- def: property.default
504
- };
505
- switch (property.type) {
506
- case "number": {
507
- const numberProperty = property;
508
- state.min = numberProperty.min;
509
- state.max = numberProperty.max;
510
- state.states = numberProperty.states;
511
- state.unit = numberProperty.unit;
512
- state.step = numberProperty.steps;
513
- state.role = types_1.RoleMapping[property.name] !== undefined ? types_1.RoleMapping[property.name] : "value";
514
- break;
515
- }
516
- case "string": {
517
- state.role = types_1.RoleMapping[property.name] !== undefined ? types_1.RoleMapping[property.name] : "text";
518
- break;
519
- }
520
- case "boolean": {
521
- state.role = types_1.RoleMapping[property.name] !== undefined ? types_1.RoleMapping[property.name] : (property.writeable ? "switch.enable" : "state");
522
- break;
523
- }
180
+ try {
181
+ if (this.persistentData.version !== this.version) {
182
+ const currentVersion = Number.parseFloat((0, import_utils.removeLastChar)(this.version, "."));
183
+ const previousVersion = this.persistentData.version !== "" && this.persistentData.version !== void 0 ? Number.parseFloat((0, import_utils.removeLastChar)(this.persistentData.version, ".")) : 0;
184
+ this.logger.debug(`Handling of adapter update - currentVersion: ${currentVersion} previousVersion: ${previousVersion}`);
185
+ if (previousVersion < currentVersion) {
186
+ await (0, import_utils.handleUpdate)(this, this.logger, previousVersion);
187
+ this.persistentData.version = this.version;
188
+ this.writePersistentData();
524
189
  }
525
- return state;
190
+ }
191
+ } catch (error) {
192
+ this.logger.error(`Handling of adapter update - Error:`, error);
526
193
  }
527
- async createAndSetState(device, property) {
528
- if (property.name !== eufy_security_client_1.PropertyName.Type && property.name !== eufy_security_client_1.PropertyName.DeviceStationSN) {
529
- const state = this.getStateCommon(property);
530
- const id = device.getStateID((0, utils_1.convertCamelCaseToSnakeCase)(property.name));
531
- const obj = await this.getObjectAsync(id);
532
- if (obj) {
533
- let changed = false;
534
- if (obj.native.name !== undefined && obj.native.name !== property.name) {
535
- obj.native.name = property.name;
536
- changed = true;
537
- }
538
- if (obj.native.key !== undefined && obj.native.key !== property.key) {
539
- obj.native.key = property.key;
540
- changed = true;
541
- }
542
- if (obj.native.commandId !== undefined && obj.native.commandId !== property.commandId) {
543
- obj.native.commandId = property.commandId;
544
- changed = true;
545
- }
546
- if (obj.common !== undefined && !util_1.default.isDeepStrictEqual(obj.common, state)) {
547
- changed = true;
548
- }
549
- if (changed) {
550
- const propertyMetadata = device.getPropertiesMetadata()[property.name];
551
- if (propertyMetadata !== undefined) {
552
- const newState = this.getStateCommon(propertyMetadata);
553
- obj.common = newState;
554
- }
555
- await this.setObjectAsync(id, obj);
556
- }
557
- }
558
- else {
559
- await this.setObjectNotExistsAsync(id, {
560
- type: "state",
561
- common: state,
562
- native: {
563
- key: property.key,
564
- commandId: property.commandId,
565
- name: property.name,
566
- },
567
- });
568
- }
569
- const value = device.getPropertyValue(property.name);
570
- if (value !== undefined)
571
- await (0, utils_1.setStateChangedAsync)(this, id, property.type === "string" && typeof value === "object" ? JSON.stringify(value) : value);
572
- }
194
+ let connectionType = import_eufy_security_client.P2PConnectionType.QUICKEST;
195
+ if (this.config.p2pConnectionType === "only_local") {
196
+ connectionType = import_eufy_security_client.P2PConnectionType.ONLY_LOCAL;
573
197
  }
574
- async onDeviceAdded(device) {
575
- this.logger.debug(`onDeviceAdded - device: ${device.getSerial()}`);
576
- await this.setObjectNotExistsAsync(device.getStateID("", 0), {
577
- type: "channel",
578
- common: {
579
- name: device.getStateChannel()
580
- },
581
- native: {},
582
- });
583
- await this.setObjectNotExistsAsync(device.getStateID("", 1), {
584
- type: "device",
585
- common: {
586
- name: device.getName()
587
- },
588
- native: {},
589
- });
590
- const metadata = device.getPropertiesMetadata();
591
- for (const property of Object.values(metadata)) {
592
- if (property.name !== eufy_security_client_1.PropertyName.DevicePicture)
593
- this.createAndSetState(device, property);
594
- }
595
- if (device.hasProperty(eufy_security_client_1.PropertyName.DevicePicture)) {
596
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.PICTURE_URL), {
597
- type: "state",
598
- common: {
599
- name: "Picture URL",
600
- type: "string",
601
- role: "url",
602
- read: true,
603
- write: false,
604
- },
605
- native: {},
606
- });
607
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.PICTURE_HTML), {
608
- type: "state",
609
- common: {
610
- name: "Picture HTML image",
611
- type: "string",
612
- role: "html",
613
- read: true,
614
- write: false,
615
- },
616
- native: {},
617
- });
618
- }
619
- if (device.hasCommand(eufy_security_client_1.CommandName.DeviceTriggerAlarmSound)) {
620
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.TRIGGER_ALARM_SOUND), {
621
- type: "state",
622
- common: {
623
- name: "Trigger Alarm Sound",
624
- type: "boolean",
625
- role: "button.start",
626
- read: false,
627
- write: true,
628
- },
629
- native: {},
630
- });
631
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.RESET_ALARM_SOUND), {
632
- type: "state",
633
- common: {
634
- name: "Reset Alarm Sound",
635
- type: "boolean",
636
- role: "button.start",
637
- read: false,
638
- write: true,
639
- },
640
- native: {},
641
- });
642
- }
643
- if (device.hasCommand(eufy_security_client_1.CommandName.DevicePanAndTilt)) {
644
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.PAN_LEFT), {
645
- type: "state",
646
- common: {
647
- name: "Pan Left",
648
- type: "boolean",
649
- role: "button.start",
650
- read: false,
651
- write: true,
652
- },
653
- native: {},
654
- });
655
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.PAN_RIGHT), {
656
- type: "state",
657
- common: {
658
- name: "Pan Right",
659
- type: "boolean",
660
- role: "button.start",
661
- read: false,
662
- write: true,
663
- },
664
- native: {},
665
- });
666
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.ROTATE_360), {
667
- type: "state",
668
- common: {
669
- name: "Rotate 360°",
670
- type: "boolean",
671
- role: "button.start",
672
- read: false,
673
- write: true,
674
- },
675
- native: {},
676
- });
677
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.TILT_UP), {
678
- type: "state",
679
- common: {
680
- name: "Tilt Up",
681
- type: "boolean",
682
- role: "button.start",
683
- read: false,
684
- write: true,
685
- },
686
- native: {},
687
- });
688
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.TILT_DOWN), {
689
- type: "state",
690
- common: {
691
- name: "Tilt Down",
692
- type: "boolean",
693
- role: "button.start",
694
- read: false,
695
- write: true,
696
- },
697
- native: {},
698
- });
699
- }
700
- if (device.hasCommand(eufy_security_client_1.CommandName.DeviceLockCalibration)) {
701
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.CALIBRATE), {
702
- type: "state",
703
- common: {
704
- name: "Calibrate Lock",
705
- type: "boolean",
706
- role: "button.start",
707
- read: false,
708
- write: true,
709
- },
710
- native: {},
711
- });
712
- }
713
- if (device.hasCommand(eufy_security_client_1.CommandName.DeviceUnlock)) {
714
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.UNLOCK), {
715
- type: "state",
716
- common: {
717
- name: "Unlock",
718
- type: "boolean",
719
- role: "button.start",
720
- read: false,
721
- write: true,
722
- },
723
- native: {},
724
- });
725
- }
726
- if (device.hasCommand(eufy_security_client_1.CommandName.DeviceSetDefaultAngle)) {
727
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.SET_DEFAULT_ANGLE), {
728
- type: "state",
729
- common: {
730
- name: "Set Default Angle",
731
- type: "boolean",
732
- role: "button.start",
733
- read: false,
734
- write: true,
735
- },
736
- native: {},
737
- });
738
- }
739
- if (device.hasCommand(eufy_security_client_1.CommandName.DeviceSetPrivacyAngle)) {
740
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.SET_PRIVACY_ANGLE), {
741
- type: "state",
742
- common: {
743
- name: "Set Default Angle",
744
- type: "boolean",
745
- role: "button.start",
746
- read: false,
747
- write: true,
748
- },
749
- native: {},
750
- });
751
- }
752
- if (device.hasCommand(eufy_security_client_1.CommandName.DeviceCalibrate)) {
753
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.CALIBRATE), {
754
- type: "state",
755
- common: {
756
- name: "Calibrate",
757
- type: "boolean",
758
- role: "button.start",
759
- read: false,
760
- write: true,
761
- },
762
- native: {},
763
- });
764
- }
765
- if (device.hasCommand(eufy_security_client_1.CommandName.DeviceStartLivestream)) {
766
- // Start Stream
767
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.START_STREAM), {
768
- type: "state",
769
- common: {
770
- name: "Start stream",
771
- type: "boolean",
772
- role: "button.start",
773
- read: false,
774
- write: true,
775
- },
776
- native: {},
777
- });
778
- // Stop Stream
779
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.STOP_STREAM), {
780
- type: "state",
781
- common: {
782
- name: "Stop stream",
783
- type: "boolean",
784
- role: "button.stop",
785
- read: false,
786
- write: true,
787
- },
788
- native: {},
789
- });
790
- // Livestream URL
791
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.LIVESTREAM), {
792
- type: "state",
793
- common: {
794
- name: "Livestream URL",
795
- type: "string",
796
- role: "url",
797
- read: true,
798
- write: false,
799
- },
800
- native: {},
801
- });
802
- // Livestream RTSP URL
803
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.LIVESTREAM_RTSP), {
804
- type: "state",
805
- common: {
806
- name: "Livestream RTSP URL",
807
- type: "string",
808
- role: "url",
809
- read: true,
810
- write: false,
811
- },
812
- native: {},
813
- });
814
- }
815
- if (device.hasProperty(eufy_security_client_1.PropertyName.DeviceRTSPStream)) {
816
- // RTSP Stream URL
817
- await this.setObjectNotExistsAsync(device.getStateID(types_1.DeviceStateID.RTSP_STREAM_URL), {
818
- type: "state",
819
- common: {
820
- name: "RTSP stream URL",
821
- type: "string",
822
- role: "url",
823
- read: true,
824
- write: false
825
- },
826
- native: {},
827
- });
828
- }
829
- //TODO: Deactivated because the decryption of the download has changed.
830
- /*if (device.hasCommand(CommandName.DeviceStartDownload)) {
831
- // Last event video URL
832
- await this.setObjectNotExistsAsync(device.getStateID(DeviceStateID.LAST_EVENT_VIDEO_URL), {
833
- type: "state",
834
- common: {
835
- name: "Last event video URL",
836
- type: "string",
837
- role: "url",
838
- read: true,
839
- write: false,
840
- def: ""
841
- },
842
- native: {},
843
- });
844
-
845
- // Last event picture URL
846
- await this.setObjectNotExistsAsync(device.getStateID(DeviceStateID.LAST_EVENT_PIC_URL), {
847
- type: "state",
848
- common: {
849
- name: "Last event picture URL",
850
- type: "string",
851
- role: "url",
852
- read: true,
853
- write: false,
854
- def: ""
855
- },
856
- native: {},
857
- });
858
-
859
- // Last event picture HTML image
860
- await this.setObjectNotExistsAsync(device.getStateID(DeviceStateID.LAST_EVENT_PIC_HTML), {
861
- type: "state",
862
- common: {
863
- name: "Last event picture HTML image",
864
- type: "string",
865
- role: "html",
866
- read: true,
867
- write: false,
868
- def: ""
869
- },
870
- native: {},
871
- });
872
- }*/
198
+ const config = {
199
+ username: this.config.username,
200
+ password: this.config.password,
201
+ country: countryCode,
202
+ language: languageCode,
203
+ persistentDir: utils.getAbsoluteInstanceDataDir(this),
204
+ eventDurationSeconds: this.config.eventDuration,
205
+ p2pConnectionSetup: connectionType,
206
+ pollingIntervalMinutes: this.config.pollingInterval,
207
+ acceptInvitations: this.config.acceptInvitations
208
+ };
209
+ this.eufy = await import_eufy_security_client.EufySecurity.initialize(config, this.logger);
210
+ this.eufy.on("station added", (station) => this.onStationAdded(station));
211
+ this.eufy.on("device added", (device) => this.onDeviceAdded(device));
212
+ this.eufy.on("station removed", (station) => this.onStationRemoved(station));
213
+ this.eufy.on("device removed", (device) => this.onDeviceRemoved(device));
214
+ this.eufy.on("push message", (messages) => this.handlePushNotification(messages));
215
+ this.eufy.on("push connect", () => this.onPushConnect());
216
+ this.eufy.on("push close", () => this.onPushClose());
217
+ this.eufy.on("mqtt connect", () => this.onMQTTConnect());
218
+ this.eufy.on("mqtt close", () => this.onMQTTClose());
219
+ this.eufy.on("connect", () => this.onConnect());
220
+ this.eufy.on("close", () => this.onClose());
221
+ this.eufy.on("device property changed", (device, name, value) => this.onDevicePropertyChanged(device, name, value));
222
+ this.eufy.on("station command result", (station, result) => this.onStationCommandResult(station, result));
223
+ this.eufy.on("station livestream start", (station, device, metadata, videostream, audiostream) => this.onStationLivestreamStart(station, device, metadata, videostream, audiostream));
224
+ this.eufy.on("station livestream stop", (station, device) => this.onStationLivestreamStop(station, device));
225
+ this.eufy.on("station rtsp url", (station, device, value) => this.onStationRTSPUrl(station, device, value));
226
+ this.eufy.on("station property changed", (station, name, value) => this.onStationPropertyChanged(station, name, value));
227
+ this.eufy.on("station connect", (station) => this.onStationConnect(station));
228
+ this.eufy.on("station close", (station) => this.onStationClose(station));
229
+ this.eufy.on("tfa request", () => this.onTFARequest());
230
+ this.eufy.on("captcha request", (captchaId, captcha) => this.onCaptchaRequest(captchaId, captcha));
231
+ this.eufy.setCameraMaxLivestreamDuration(this.config.maxLivestreamDuration);
232
+ await this.eufy.connect();
233
+ if (import_go2rtc_static.default) {
234
+ const go2rtcConfig = {
235
+ "api": {
236
+ "listen": `:${this.config.go2rtc_api_port}`
237
+ },
238
+ "rtsp": {
239
+ "listen": `:${this.config.go2rtc_rtsp_port}`
240
+ },
241
+ "srtp": {
242
+ "listen": `:${this.config.go2rtc_srtp_port}`
243
+ },
244
+ "webrtc": {
245
+ "listen": `:${this.config.go2rtc_webrtc_port}`
246
+ },
247
+ "streams": {}
248
+ };
249
+ if (this.config.go2rtc_rtsp_username !== "" && this.config.go2rtc_rtsp_password !== "") {
250
+ go2rtcConfig.rtsp.username = this.config.go2rtc_rtsp_username;
251
+ go2rtcConfig.rtsp.password = this.config.go2rtc_rtsp_password;
252
+ }
253
+ for (const device of await this.eufy.getDevices()) {
254
+ go2rtcConfig.streams[device.getSerial()] = null;
255
+ }
256
+ const go2rtc = import_child_process.default.spawn(import_go2rtc_static.default, ["-config", JSON.stringify(go2rtcConfig)], { shell: false, detached: false, windowsHide: true });
257
+ go2rtc.on("error", (error) => {
258
+ this.log.error(`go2rtc error: ${error}`);
259
+ });
260
+ go2rtc.stdout.setEncoding("utf8");
261
+ go2rtc.stdout.on("data", (data) => {
262
+ this.log.info(`go2rtc started: ${data}`);
263
+ });
264
+ go2rtc.stderr.setEncoding("utf8");
265
+ go2rtc.stderr.on("data", (data) => {
266
+ this.log.error(`go2rtc error: ${data}`);
267
+ });
268
+ go2rtc.on("close", (exitcode) => {
269
+ this.log.info(`go2rtc terminated with exitcode ${exitcode}`);
270
+ });
271
+ process.on("exit", () => {
272
+ go2rtc.kill();
273
+ });
873
274
  }
874
- async onDeviceRemoved(device) {
875
- this.delObjectAsync(device.getStateID("", 0), { recursive: true }).catch((error) => {
876
- this.logger.error(`Error deleting states of removed device`, error);
877
- });
878
- (0, utils_1.removeFiles)(this, device.getStationSerial(), types_1.DataLocation.LAST_EVENT, device.getSerial()).catch((error) => {
879
- this.logger.error(`Error deleting fs contents of removed device`, error);
880
- });
275
+ }
276
+ writePersistentData() {
277
+ try {
278
+ import_fs_extra.default.writeFileSync(this.persistentFile, JSON.stringify(this.persistentData));
279
+ } catch (error) {
280
+ this.logger.error(`writePersistentData() - Error: ${error}`);
881
281
  }
882
- async onStationAdded(station) {
883
- this.subscribeStates(`${station.getStateID("", 0)}.*`);
884
- await this.setObjectNotExistsAsync(station.getStateID("", 0), {
885
- type: "device",
886
- common: {
887
- name: station.getName()
888
- },
889
- native: {},
890
- });
891
- await this.setObjectNotExistsAsync(station.getStateID("", 1), {
892
- type: "channel",
893
- common: {
894
- name: station.getStateChannel()
895
- },
896
- native: {},
897
- });
898
- await this.setObjectNotExistsAsync(station.getStateID(types_1.StationStateID.CONNECTION), {
899
- type: "state",
900
- common: {
901
- name: "Connection",
902
- type: "boolean",
903
- role: "indicator.connection",
904
- read: true,
905
- write: false,
906
- },
907
- native: {},
908
- });
909
- await this.setStateAsync(station.getStateID(types_1.StationStateID.CONNECTION), { val: false, ack: true });
910
- const metadata = station.getPropertiesMetadata();
911
- for (const property of Object.values(metadata)) {
912
- this.createAndSetState(station, property);
913
- }
914
- // Reboot station
915
- if (station.hasCommand(eufy_security_client_1.CommandName.StationReboot)) {
916
- await this.setObjectNotExistsAsync(station.getStateID(types_1.StationStateID.REBOOT), {
917
- type: "state",
918
- common: {
919
- name: "Reboot station",
920
- type: "boolean",
921
- role: "button.start",
922
- read: false,
923
- write: true,
924
- },
925
- native: {},
926
- });
282
+ }
283
+ async onUnload(callback) {
284
+ try {
285
+ this.writePersistentData();
286
+ if (this.eufy) {
287
+ if (this.eufy.isConnected())
288
+ await this.setStateAsync("info.connection", { val: false, ack: true }).catch();
289
+ this.eufy.removeAllListeners();
290
+ this.eufy.close();
291
+ }
292
+ callback();
293
+ } catch (e) {
294
+ callback();
295
+ }
296
+ }
297
+ async onStateChange(id, state) {
298
+ if (state) {
299
+ if (!id || state.ack) {
300
+ this.logger.debug(`state ${id} changed: ${state.val} (ack = ${state.ack}) was already acknowledged, ignore it...`);
301
+ return;
302
+ }
303
+ this.logger.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
304
+ const values = id.split(".");
305
+ const station_sn = values[2];
306
+ const device_type = values[3];
307
+ if (station_sn == "verify_code") {
308
+ if (this.eufy && this.verify_code) {
309
+ this.logger.info(`Verification code received, send it. (verify_code: ${state.val})`);
310
+ await this.eufy.connect({ verifyCode: state.val });
311
+ this.verify_code = false;
312
+ await this.delStateAsync(id);
927
313
  }
928
- // Alarm Sound
929
- if (station.hasCommand(eufy_security_client_1.CommandName.StationTriggerAlarmSound)) {
930
- await this.setObjectNotExistsAsync(station.getStateID(types_1.StationStateID.TRIGGER_ALARM_SOUND), {
931
- type: "state",
932
- common: {
933
- name: "Trigger Alarm Sound",
934
- type: "boolean",
935
- role: "button.start",
936
- read: false,
937
- write: true,
938
- },
939
- native: {},
940
- });
941
- await this.setObjectNotExistsAsync(station.getStateID(types_1.StationStateID.RESET_ALARM_SOUND), {
942
- type: "state",
943
- common: {
944
- name: "Reset Alarm Sound",
945
- type: "boolean",
946
- role: "button.start",
947
- read: false,
948
- write: true,
949
- },
950
- native: {},
951
- });
314
+ } else if (station_sn == "captcha") {
315
+ if (this.eufy && this.captchaId) {
316
+ this.logger.info(`Captcha received, send it. (captcha: ${state.val})`);
317
+ await this.eufy.connect({
318
+ captcha: {
319
+ captchaCode: state.val,
320
+ captchaId: this.captchaId
321
+ }
322
+ });
323
+ this.captchaId = null;
324
+ await this.delStateAsync(id);
325
+ await this.delStateAsync("received_captcha_html");
952
326
  }
953
- }
954
- async onStationRemoved(station) {
955
- this.delObjectAsync(station.getStateID("", 0), { recursive: true }).catch((error) => {
956
- this.logger.error(`Error deleting states of removed station`, error);
957
- });
958
- fs_extra_1.default.remove(path.join(utils.getAbsoluteInstanceDataDir(this), station.getSerial())).catch((error) => {
959
- this.logger.error(`Error deleting fs contents of removed station`, error);
960
- });
961
- }
962
- /*private async downloadEventVideo(device: Device, event_time: number, full_path: string | undefined, cipher_id: number | undefined): Promise<void> {
963
- this.logger.debug(`Device: ${device.getSerial()} full_path: ${full_path} cipher_id: ${cipher_id}`);
327
+ } else if (device_type == "station") {
964
328
  try {
965
- if (!isEmpty(full_path) && cipher_id !== undefined) {
966
- const station = await this.eufy.getStation(device.getStationSerial());
967
-
968
- if (station !== undefined) {
969
- if (this.downloadEvent[device.getSerial()])
970
- clearTimeout(this.downloadEvent[device.getSerial()]);
971
-
972
- let videoLength = getVideoClipLength(device);
973
- const time_passed = (new Date().getTime() - new Date(event_time).getTime()) / 1000;
974
-
975
- if (time_passed >= videoLength)
976
- videoLength = 1;
977
- else
978
- videoLength = videoLength - time_passed;
979
-
980
- this.logger.info(`Downloading video event for device ${device.getSerial()} in ${videoLength} seconds...`);
981
- this.downloadEvent[device.getSerial()] = setTimeout(async () => {
982
- station.startDownload(device, full_path!, cipher_id);
983
- }, videoLength * 1000);
984
- }
329
+ const station_state_name = values[4];
330
+ if (this.eufy) {
331
+ const obj = await this.getObjectAsync(id);
332
+ if (obj) {
333
+ if (obj.native.name !== void 0) {
334
+ await this.eufy.setStationProperty(station_sn, obj.native.name, state.val);
335
+ return;
336
+ }
337
+ }
338
+ const station = await this.eufy.getStation(station_sn);
339
+ switch (station_state_name) {
340
+ case import_types.StationStateID.REBOOT:
341
+ await station.rebootHUB();
342
+ break;
343
+ case import_types.StationStateID.TRIGGER_ALARM_SOUND:
344
+ await station.triggerStationAlarmSound(this.config.alarmSoundDuration);
345
+ break;
346
+ case import_types.StationStateID.RESET_ALARM_SOUND:
347
+ await station.resetStationAlarmSound();
348
+ break;
985
349
  }
350
+ }
986
351
  } catch (error) {
987
- this.logger.error(`Device: ${device.getSerial()} - Error`, error);
352
+ this.logger.error(`station - Error:`, error);
988
353
  }
989
- }*/
990
- async handlePushNotification(message) {
354
+ } else {
991
355
  try {
992
- if (message.device_sn !== undefined) {
993
- //TODO: Deactivated because the decryption of the download has changed.
994
- /*const device: Device = await this.eufy.getDevice(message.device_sn);
995
- if ((message.push_count === 1 || message.push_count === undefined) && (message.file_path !== undefined && message.file_path !== "" && message.cipher !== undefined))
996
- if (this.config.autoDownloadVideo)
997
- await this.downloadEventVideo(device, message.event_time, message.file_path, message.cipher);*/
998
- }
999
- }
1000
- catch (error) {
1001
- if (error instanceof eufy_security_client_1.DeviceNotFoundError) {
1002
- //Do nothing
1003
- }
1004
- else {
1005
- this.logger.error("Handling push notification - Error", error);
356
+ const device_sn = values[4];
357
+ const obj = await this.getObjectAsync(id);
358
+ if (obj) {
359
+ if (obj.native.name !== void 0) {
360
+ try {
361
+ await this.eufy.setDeviceProperty(device_sn, obj.native.name, obj.common.type === "object" ? JSON.parse(state.val) : state.val);
362
+ } catch (error) {
363
+ this.logger.error(`Error in setting property value (property: ${obj.native.name} value: ${state.val})`, error);
364
+ }
365
+ return;
1006
366
  }
367
+ }
368
+ const device_state_name = values[5];
369
+ const station = await this.eufy.getStation(station_sn);
370
+ const device = await this.eufy.getDevice(device_sn);
371
+ switch (device_state_name) {
372
+ case import_types.DeviceStateID.START_STREAM:
373
+ await this.startLivestream(device_sn);
374
+ break;
375
+ case import_types.DeviceStateID.STOP_STREAM:
376
+ await this.stopLivestream(device_sn);
377
+ break;
378
+ case import_types.DeviceStateID.TRIGGER_ALARM_SOUND:
379
+ await station.triggerDeviceAlarmSound(device, this.config.alarmSoundDuration);
380
+ break;
381
+ case import_types.DeviceStateID.RESET_ALARM_SOUND:
382
+ await station.resetDeviceAlarmSound(device);
383
+ break;
384
+ case import_types.DeviceStateID.ROTATE_360:
385
+ await station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.ROTATE360);
386
+ break;
387
+ case import_types.DeviceStateID.PAN_LEFT:
388
+ await station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.LEFT);
389
+ break;
390
+ case import_types.DeviceStateID.PAN_RIGHT:
391
+ await station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.RIGHT);
392
+ break;
393
+ case import_types.DeviceStateID.TILT_UP:
394
+ await station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.UP);
395
+ break;
396
+ case import_types.DeviceStateID.TILT_DOWN:
397
+ await station.panAndTilt(device, import_eufy_security_client.PanTiltDirection.DOWN);
398
+ break;
399
+ case import_types.DeviceStateID.CALIBRATE:
400
+ if (device.isLock()) {
401
+ await station.calibrateLock(device);
402
+ } else {
403
+ await station.calibrate(device);
404
+ }
405
+ break;
406
+ case import_types.DeviceStateID.UNLOCK:
407
+ await station.unlock(device);
408
+ break;
409
+ case import_types.DeviceStateID.SET_DEFAULT_ANGLE:
410
+ await station.setDefaultAngle(device);
411
+ break;
412
+ case import_types.DeviceStateID.SET_PRIVACY_ANGLE:
413
+ await station.setPrivacyAngle(device);
414
+ break;
415
+ }
416
+ } catch (error) {
417
+ this.logger.error(`cameras - Error:`, error);
1007
418
  }
419
+ }
420
+ } else {
421
+ this.logger.debug(`state ${id} deleted`);
1008
422
  }
1009
- async onConnect() {
1010
- await this.setObjectNotExistsAsync("info", {
1011
- type: "channel",
1012
- common: {
1013
- name: "info"
1014
- },
1015
- native: {},
1016
- });
1017
- await this.setObjectNotExistsAsync("info.connection", {
1018
- type: "state",
1019
- common: {
1020
- name: "Global connection",
1021
- type: "boolean",
1022
- role: "indicator.connection",
1023
- read: true,
1024
- write: false,
1025
- },
1026
- native: {},
1027
- });
1028
- await this.setStateAsync("info.connection", { val: true, ack: true });
1029
- const stations = await this.eufy.getStations();
1030
- const stationSerials = [];
1031
- for (const station of stations) {
1032
- stationSerials.push(station.getSerial());
1033
- }
1034
- const devices = await this.eufy.getDevices();
1035
- const deviceSerials = [];
1036
- for (const device of devices) {
1037
- deviceSerials.push(device.getSerial());
1038
- }
1039
- // Delete obsolete stations
1040
- try {
1041
- const allDevices = await this.getDevicesAsync();
1042
- const reg = new RegExp(`^${this.namespace}\.[0-9A-Z]+$`);
1043
- allDevices.forEach((id) => {
1044
- if (id._id.match(reg)) {
1045
- const serial = id._id.replace(`${this.namespace}.`, "");
1046
- if (!stationSerials.includes(serial)) {
1047
- this.delObjectAsync(id._id, { recursive: true });
1048
- }
1049
- }
1050
- });
1051
- }
1052
- catch (error) {
1053
- this.log.error(`Delete obsolete stations ERROR - ${JSON.stringify(error)}`);
423
+ }
424
+ getStateCommon(property) {
425
+ const state = {
426
+ name: property.label,
427
+ type: property.type,
428
+ role: "state",
429
+ read: property.readable,
430
+ write: property.writeable,
431
+ def: property.default
432
+ };
433
+ switch (property.type) {
434
+ case "number": {
435
+ const numberProperty = property;
436
+ state.min = numberProperty.min;
437
+ state.max = numberProperty.max;
438
+ state.states = numberProperty.states;
439
+ state.unit = numberProperty.unit;
440
+ state.step = numberProperty.steps;
441
+ state.role = import_types.RoleMapping[property.name] !== void 0 ? import_types.RoleMapping[property.name] : "value";
442
+ break;
443
+ }
444
+ case "string": {
445
+ state.role = import_types.RoleMapping[property.name] !== void 0 ? import_types.RoleMapping[property.name] : "text";
446
+ break;
447
+ }
448
+ case "boolean": {
449
+ state.role = import_types.RoleMapping[property.name] !== void 0 ? import_types.RoleMapping[property.name] : property.writeable ? "switch.enable" : "state";
450
+ break;
451
+ }
452
+ }
453
+ return state;
454
+ }
455
+ async createAndSetState(device, property) {
456
+ if (property.name !== import_eufy_security_client.PropertyName.Type && property.name !== import_eufy_security_client.PropertyName.DeviceStationSN) {
457
+ const state = this.getStateCommon(property);
458
+ const id = device.getStateID((0, import_utils.convertCamelCaseToSnakeCase)(property.name));
459
+ const obj = await this.getObjectAsync(id);
460
+ if (obj) {
461
+ let changed = false;
462
+ if (obj.native.name !== void 0 && obj.native.name !== property.name) {
463
+ obj.native.name = property.name;
464
+ changed = true;
1054
465
  }
1055
- // Delete obsolete devices
1056
- try {
1057
- const allDevices = await this.getDevicesAsync();
1058
- const reg = new RegExp(`^${this.namespace}\.[0-9A-Z]+\.[a-z]+\.[0-9A-Z]+$`);
1059
- allDevices.forEach((id) => {
1060
- if (id._id.match(reg)) {
1061
- const serial = id._id.split(".")[4];
1062
- if (!deviceSerials.includes(serial)) {
1063
- this.delObjectAsync(id._id, { recursive: true });
1064
- }
1065
- }
1066
- });
466
+ if (obj.native.key !== void 0 && obj.native.key !== property.key) {
467
+ obj.native.key = property.key;
468
+ changed = true;
1067
469
  }
1068
- catch (error) {
1069
- this.log.error(`Delete obsolete devices ERROR - ${JSON.stringify(error)}`);
470
+ if (obj.native.commandId !== void 0 && obj.native.commandId !== property.commandId) {
471
+ obj.native.commandId = property.commandId;
472
+ changed = true;
1070
473
  }
1071
- // Delete obsolete properties
1072
- try {
1073
- const all = await this.getStatesAsync("*");
1074
- if (all) {
1075
- Object.keys(all).forEach(async (stateid) => {
1076
- var _a;
1077
- const object = await this.getObjectAsync(stateid);
1078
- if (((_a = object === null || object === void 0 ? void 0 : object.native) === null || _a === void 0 ? void 0 : _a.name) !== undefined) {
1079
- const tmp = stateid.split(".");
1080
- if (tmp.length >= 5) {
1081
- const stationSerial = tmp[2];
1082
- const deviceSerial = tmp[4];
1083
- if (deviceSerial.match(/^[A-Z0-9]+/)) {
1084
- // Device
1085
- try {
1086
- const device = await this.eufy.getDevice(deviceSerial);
1087
- if (!device.hasProperty(object.native.name)) {
1088
- this.delObjectAsync(stateid);
1089
- }
1090
- }
1091
- catch (error) {
1092
- if (error instanceof eufy_security_client_1.DeviceNotFoundError) {
1093
- }
1094
- else {
1095
- this.log.error(`Delete obsolete properties ERROR - device - ${JSON.stringify(error)}`);
1096
- }
1097
- }
1098
- }
1099
- else {
1100
- // Station
1101
- try {
1102
- const station = await this.eufy.getStation(stationSerial);
1103
- if (!station.hasProperty(object.native.name)) {
1104
- this.delObjectAsync(stateid);
1105
- }
1106
- }
1107
- catch (error) {
1108
- if (error instanceof eufy_security_client_1.StationNotFoundError) {
1109
- }
1110
- else {
1111
- this.log.error(`Delete obsolete properties ERROR - station - ${JSON.stringify(error)}`);
1112
- }
1113
- }
1114
- }
1115
- }
1116
- }
1117
- });
1118
- }
474
+ if (obj.common !== void 0 && !import_util.default.isDeepStrictEqual(obj.common, state)) {
475
+ changed = true;
1119
476
  }
1120
- catch (error) {
1121
- this.log.error(`Delete obsolete properties ERROR - ${JSON.stringify(error)}`);
477
+ if (changed) {
478
+ const propertyMetadata = device.getPropertiesMetadata()[property.name];
479
+ if (propertyMetadata !== void 0) {
480
+ const newState = this.getStateCommon(propertyMetadata);
481
+ obj.common = newState;
482
+ }
483
+ await this.setObjectAsync(id, obj);
1122
484
  }
1123
- // Delete obsolete directories/files
1124
- new Promise(async (resolve, reject) => {
1125
- try {
1126
- const dir_path = path.join(utils.getAbsoluteInstanceDataDir(this));
1127
- if (fs_extra_1.default.existsSync(dir_path)) {
1128
- for (const content of fs_extra_1.default.readdirSync(dir_path).filter(fn => fn.match("^T[0-9A-Z]+$") !== null)) {
1129
- if (!stationSerials.includes(content)) {
1130
- fs_extra_1.default.removeSync(path.join(dir_path, content));
1131
- }
1132
- else {
1133
- for (const dir of fs_extra_1.default.readdirSync(path.join(dir_path, content))) {
1134
- if (dir === types_1.DataLocation.LIVESTREAM || dir === types_1.DataLocation.LAST_LIVESTREAM || dir === types_1.DataLocation.TEMP) {
1135
- fs_extra_1.default.removeSync(path.join(dir_path, content, dir));
1136
- }
1137
- else {
1138
- const files = fs_extra_1.default.readdirSync(path.join(dir_path, content, dir));
1139
- let deletedFiles = 0;
1140
- for (const file of files) {
1141
- if (!deviceSerials.includes(file.substring(0, 16))) {
1142
- fs_extra_1.default.removeSync(path.join(dir_path, content, dir, file));
1143
- deletedFiles++;
1144
- }
1145
- }
1146
- if (deletedFiles === files.length) {
1147
- fs_extra_1.default.removeSync(path.join(dir_path, content, dir));
1148
- }
1149
- }
1150
- }
1151
- }
1152
- }
1153
- }
1154
- resolve();
1155
- }
1156
- catch (error) {
1157
- reject(error);
1158
- }
1159
- }).catch(error => {
1160
- this.log.error(`Delete obsolete directories/files ERROR - ${JSON.stringify(error)}`);
485
+ } else {
486
+ await this.setObjectNotExistsAsync(id, {
487
+ type: "state",
488
+ common: state,
489
+ native: {
490
+ key: property.key,
491
+ commandId: property.commandId,
492
+ name: property.name
493
+ }
1161
494
  });
495
+ }
496
+ const value = device.getPropertyValue(property.name);
497
+ if (value !== void 0)
498
+ await (0, import_utils.setStateChangedAsync)(this, id, (property.type === "string" || property.type === "object") && typeof value === "object" ? JSON.stringify(value) : value);
1162
499
  }
1163
- async onClose() {
1164
- await this.setStateAsync("info.connection", { val: false, ack: true }).catch();
500
+ }
501
+ async onDeviceAdded(device) {
502
+ this.logger.debug(`onDeviceAdded - device: ${device.getSerial()}`);
503
+ await this.setObjectNotExistsAsync(device.getStateID("", 0), {
504
+ type: "channel",
505
+ common: {
506
+ name: device.getStateChannel()
507
+ },
508
+ native: {}
509
+ });
510
+ await this.setObjectNotExistsAsync(device.getStateID("", 1), {
511
+ type: "device",
512
+ common: {
513
+ name: device.getName()
514
+ },
515
+ native: {}
516
+ });
517
+ const metadata = device.getPropertiesMetadata();
518
+ for (const property of Object.values(metadata)) {
519
+ if (property.name !== import_eufy_security_client.PropertyName.DevicePicture)
520
+ this.createAndSetState(device, property);
1165
521
  }
1166
- getPersistentData() {
1167
- return this.persistentData;
522
+ if (device.hasProperty(import_eufy_security_client.PropertyName.DevicePicture)) {
523
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.PICTURE_URL), {
524
+ type: "state",
525
+ common: {
526
+ name: "Picture URL",
527
+ type: "string",
528
+ role: "url",
529
+ read: true,
530
+ write: false
531
+ },
532
+ native: {}
533
+ });
534
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.PICTURE_HTML), {
535
+ type: "state",
536
+ common: {
537
+ name: "Picture HTML image",
538
+ type: "string",
539
+ role: "html",
540
+ read: true,
541
+ write: false
542
+ },
543
+ native: {}
544
+ });
1168
545
  }
1169
- async onPushConnect() {
1170
- await this.setObjectNotExistsAsync("info", {
1171
- type: "channel",
1172
- common: {
1173
- name: "info"
1174
- },
1175
- native: {},
1176
- });
1177
- await this.setObjectNotExistsAsync("info.push_connection", {
1178
- type: "state",
1179
- common: {
1180
- name: "Push notification connection",
1181
- type: "boolean",
1182
- role: "indicator.connection",
1183
- read: true,
1184
- write: false,
1185
- },
1186
- native: {},
1187
- });
1188
- await this.setStateAsync("info.push_connection", { val: true, ack: true });
546
+ if (device.hasCommand(import_eufy_security_client.CommandName.DeviceTriggerAlarmSound)) {
547
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.TRIGGER_ALARM_SOUND), {
548
+ type: "state",
549
+ common: {
550
+ name: "Trigger Alarm Sound",
551
+ type: "boolean",
552
+ role: "button.start",
553
+ read: false,
554
+ write: true
555
+ },
556
+ native: {}
557
+ });
558
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.RESET_ALARM_SOUND), {
559
+ type: "state",
560
+ common: {
561
+ name: "Reset Alarm Sound",
562
+ type: "boolean",
563
+ role: "button.start",
564
+ read: false,
565
+ write: true
566
+ },
567
+ native: {}
568
+ });
1189
569
  }
1190
- async onPushClose() {
1191
- await this.setObjectNotExistsAsync("info", {
1192
- type: "channel",
1193
- common: {
1194
- name: "info"
1195
- },
1196
- native: {},
1197
- });
1198
- await this.setObjectNotExistsAsync("info.push_connection", {
1199
- type: "state",
1200
- common: {
1201
- name: "Push notification connection",
1202
- type: "boolean",
1203
- role: "indicator.connection",
1204
- read: true,
1205
- write: false,
1206
- },
1207
- native: {},
1208
- });
1209
- await this.setStateAsync("info.push_connection", { val: false, ack: true });
570
+ if (device.hasCommand(import_eufy_security_client.CommandName.DevicePanAndTilt)) {
571
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.PAN_LEFT), {
572
+ type: "state",
573
+ common: {
574
+ name: "Pan Left",
575
+ type: "boolean",
576
+ role: "button.start",
577
+ read: false,
578
+ write: true
579
+ },
580
+ native: {}
581
+ });
582
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.PAN_RIGHT), {
583
+ type: "state",
584
+ common: {
585
+ name: "Pan Right",
586
+ type: "boolean",
587
+ role: "button.start",
588
+ read: false,
589
+ write: true
590
+ },
591
+ native: {}
592
+ });
593
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.ROTATE_360), {
594
+ type: "state",
595
+ common: {
596
+ name: "Rotate 360\xB0",
597
+ type: "boolean",
598
+ role: "button.start",
599
+ read: false,
600
+ write: true
601
+ },
602
+ native: {}
603
+ });
604
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.TILT_UP), {
605
+ type: "state",
606
+ common: {
607
+ name: "Tilt Up",
608
+ type: "boolean",
609
+ role: "button.start",
610
+ read: false,
611
+ write: true
612
+ },
613
+ native: {}
614
+ });
615
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.TILT_DOWN), {
616
+ type: "state",
617
+ common: {
618
+ name: "Tilt Down",
619
+ type: "boolean",
620
+ role: "button.start",
621
+ read: false,
622
+ write: true
623
+ },
624
+ native: {}
625
+ });
1210
626
  }
1211
- async onMQTTConnect() {
1212
- await this.setObjectNotExistsAsync("info", {
1213
- type: "channel",
1214
- common: {
1215
- name: "info"
1216
- },
1217
- native: {},
1218
- });
1219
- await this.setObjectNotExistsAsync("info.mqtt_connection", {
1220
- type: "state",
1221
- common: {
1222
- name: "MQTT connection",
1223
- type: "boolean",
1224
- role: "indicator.connection",
1225
- read: true,
1226
- write: false,
1227
- },
1228
- native: {},
1229
- });
1230
- await this.setStateAsync("info.mqtt_connection", { val: true, ack: true });
627
+ if (device.hasCommand(import_eufy_security_client.CommandName.DeviceLockCalibration)) {
628
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.CALIBRATE), {
629
+ type: "state",
630
+ common: {
631
+ name: "Calibrate Lock",
632
+ type: "boolean",
633
+ role: "button.start",
634
+ read: false,
635
+ write: true
636
+ },
637
+ native: {}
638
+ });
1231
639
  }
1232
- async onMQTTClose() {
1233
- await this.setObjectNotExistsAsync("info", {
1234
- type: "channel",
1235
- common: {
1236
- name: "info"
1237
- },
1238
- native: {},
1239
- });
1240
- await this.setObjectNotExistsAsync("info.mqtt_connection", {
1241
- type: "state",
1242
- common: {
1243
- name: "MQTT connection",
1244
- type: "boolean",
1245
- role: "indicator.connection",
1246
- read: true,
1247
- write: false,
1248
- },
1249
- native: {},
1250
- });
1251
- await this.setStateAsync("info.mqtt_connection", { val: false, ack: true });
640
+ if (device.hasCommand(import_eufy_security_client.CommandName.DeviceUnlock)) {
641
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.UNLOCK), {
642
+ type: "state",
643
+ common: {
644
+ name: "Unlock",
645
+ type: "boolean",
646
+ role: "button.start",
647
+ read: false,
648
+ write: true
649
+ },
650
+ native: {}
651
+ });
1252
652
  }
1253
- async onStationCommandResult(station, result) {
1254
- if (result.return_code !== 0 && result.command_type === eufy_security_client_1.CommandType.CMD_START_REALTIME_MEDIA) {
1255
- this.logger.debug(`Station: ${station.getSerial()} command ${eufy_security_client_1.CommandType[result.command_type]} failed with error: ${eufy_security_client_1.ErrorCode[result.return_code]} (${result.return_code}) fallback to RTMP livestream...`);
1256
- try {
1257
- const device = await this.eufy.getStationDevice(station.getSerial(), result.channel);
1258
- if (device.isCamera())
1259
- this.eufy.startCloudLivestream(device.getSerial());
1260
- }
1261
- catch (error) {
1262
- this.logger.error(`Station: ${station.getSerial()} command ${eufy_security_client_1.CommandType[result.command_type]} RTMP fallback failed - Error ${error}`);
1263
- }
1264
- }
1265
- else if (result.return_code !== 0 && result.command_type !== eufy_security_client_1.CommandType.P2P_QUERY_STATUS_IN_LOCK) {
1266
- this.logger.error(`Station: ${station.getSerial()} command ${eufy_security_client_1.CommandType[result.command_type]} failed with error: ${eufy_security_client_1.ErrorCode[result.return_code]} (${result.return_code})`);
1267
- }
653
+ if (device.hasCommand(import_eufy_security_client.CommandName.DeviceSetDefaultAngle)) {
654
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.SET_DEFAULT_ANGLE), {
655
+ type: "state",
656
+ common: {
657
+ name: "Set Default Angle",
658
+ type: "boolean",
659
+ role: "button.start",
660
+ read: false,
661
+ write: true
662
+ },
663
+ native: {}
664
+ });
1268
665
  }
1269
- async onStationPropertyChanged(station, name, value) {
1270
- const states = await this.getStatesAsync(`${station.getStateID("", 1)}.*`);
1271
- for (const state in states) {
1272
- const obj = await this.getObjectAsync(state);
1273
- if (obj) {
1274
- if (obj.native.name !== undefined && obj.native.name === name) {
1275
- await (0, utils_1.setStateChangedAsync)(this, state, obj.common.type === "string" && typeof value === "object" ? JSON.stringify(value) : value);
1276
- return;
1277
- }
1278
- }
1279
- }
1280
- this.logger.debug(`onStationPropertyChanged(): Property "${name}" not implemented in this adapter (station: ${station.getSerial()} value: ${JSON.stringify(value)})`);
666
+ if (device.hasCommand(import_eufy_security_client.CommandName.DeviceSetPrivacyAngle)) {
667
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.SET_PRIVACY_ANGLE), {
668
+ type: "state",
669
+ common: {
670
+ name: "Set Default Angle",
671
+ type: "boolean",
672
+ role: "button.start",
673
+ read: false,
674
+ write: true
675
+ },
676
+ native: {}
677
+ });
1281
678
  }
1282
- async onDevicePropertyChanged(device, name, value) {
1283
- const states = await this.getStatesAsync(`${device.getStateID("", 1)}.*`);
1284
- for (const state in states) {
1285
- const obj = await this.getObjectAsync(state);
1286
- if (obj) {
1287
- if (obj.native.name !== undefined && obj.native.name === name) {
1288
- await (0, utils_1.setStateChangedAsync)(this, state, obj.common.type === "string" && typeof value === "object" ? JSON.stringify(value) : value);
1289
- switch (name) {
1290
- case eufy_security_client_1.PropertyName.DeviceRTSPStream:
1291
- if (value === false) {
1292
- this.delStateAsync(device.getStateID(types_1.DeviceStateID.RTSP_STREAM_URL));
1293
- }
1294
- break;
1295
- }
1296
- return;
1297
- }
1298
- }
1299
- }
1300
- if (name === eufy_security_client_1.PropertyName.DevicePicture) {
1301
- try {
1302
- const picture = value;
1303
- const fileName = `${device.getSerial()}.${picture.type.ext}`;
1304
- const filePath = path.join(utils.getAbsoluteInstanceDataDir(this), device.getStationSerial(), types_1.DataLocation.LAST_EVENT);
1305
- if (!fs_extra_1.default.existsSync(filePath)) {
1306
- fs_extra_1.default.mkdirSync(filePath, { mode: 0o775, recursive: true });
1307
- }
1308
- await fs_extra_1.default.writeFile(path.join(filePath, fileName), picture.data);
1309
- await (0, utils_1.setStateChangedAsync)(this, device.getStateID(types_1.DeviceStateID.PICTURE_URL), `/${this.namespace}/${device.getStationSerial()}/${types_1.DataLocation.LAST_EVENT}/${device.getSerial()}.${picture.type.ext}`);
1310
- await (0, utils_1.setStateChangedAsync)(this, device.getStateID(types_1.DeviceStateID.PICTURE_HTML), (0, utils_1.getImageAsHTML)(picture.data, picture.type.mime));
1311
- }
1312
- catch (error) {
1313
- this.logger.error("onDevicePropertyChanged - Property picture - Error", error);
1314
- }
679
+ if (device.hasCommand(import_eufy_security_client.CommandName.DeviceCalibrate)) {
680
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.CALIBRATE), {
681
+ type: "state",
682
+ common: {
683
+ name: "Calibrate",
684
+ type: "boolean",
685
+ role: "button.start",
686
+ read: false,
687
+ write: true
688
+ },
689
+ native: {}
690
+ });
691
+ }
692
+ if (device.hasCommand(import_eufy_security_client.CommandName.DeviceStartLivestream)) {
693
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.START_STREAM), {
694
+ type: "state",
695
+ common: {
696
+ name: "Start stream",
697
+ type: "boolean",
698
+ role: "button.start",
699
+ read: false,
700
+ write: true
701
+ },
702
+ native: {}
703
+ });
704
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.STOP_STREAM), {
705
+ type: "state",
706
+ common: {
707
+ name: "Stop stream",
708
+ type: "boolean",
709
+ role: "button.stop",
710
+ read: false,
711
+ write: true
712
+ },
713
+ native: {}
714
+ });
715
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM), {
716
+ type: "state",
717
+ common: {
718
+ name: "Livestream URL",
719
+ type: "string",
720
+ role: "url",
721
+ read: true,
722
+ write: false
723
+ },
724
+ native: {}
725
+ });
726
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM_RTSP), {
727
+ type: "state",
728
+ common: {
729
+ name: "Livestream RTSP URL",
730
+ type: "string",
731
+ role: "url",
732
+ read: true,
733
+ write: false
734
+ },
735
+ native: {}
736
+ });
737
+ }
738
+ if (device.hasProperty(import_eufy_security_client.PropertyName.DeviceRTSPStream)) {
739
+ await this.setObjectNotExistsAsync(device.getStateID(import_types.DeviceStateID.RTSP_STREAM_URL), {
740
+ type: "state",
741
+ common: {
742
+ name: "RTSP stream URL",
743
+ type: "string",
744
+ role: "url",
745
+ read: true,
746
+ write: false
747
+ },
748
+ native: {}
749
+ });
750
+ }
751
+ }
752
+ async onDeviceRemoved(device) {
753
+ this.delObjectAsync(device.getStateID("", 0), { recursive: true }).catch((error) => {
754
+ this.logger.error(`Error deleting states of removed device`, error);
755
+ });
756
+ (0, import_utils.removeFiles)(this, device.getStationSerial(), import_types.DataLocation.LAST_EVENT, device.getSerial()).catch((error) => {
757
+ this.logger.error(`Error deleting fs contents of removed device`, error);
758
+ });
759
+ }
760
+ async onStationAdded(station) {
761
+ this.subscribeStates(`${station.getStateID("", 0)}.*`);
762
+ await this.setObjectNotExistsAsync(station.getStateID("", 0), {
763
+ type: "device",
764
+ common: {
765
+ name: station.getName()
766
+ },
767
+ native: {}
768
+ });
769
+ await this.setObjectNotExistsAsync(station.getStateID("", 1), {
770
+ type: "channel",
771
+ common: {
772
+ name: station.getStateChannel()
773
+ },
774
+ native: {}
775
+ });
776
+ await this.setObjectNotExistsAsync(station.getStateID(import_types.StationStateID.CONNECTION), {
777
+ type: "state",
778
+ common: {
779
+ name: "Connection",
780
+ type: "boolean",
781
+ role: "indicator.connection",
782
+ read: true,
783
+ write: false
784
+ },
785
+ native: {}
786
+ });
787
+ await this.setStateAsync(station.getStateID(import_types.StationStateID.CONNECTION), { val: false, ack: true });
788
+ const metadata = station.getPropertiesMetadata();
789
+ for (const property of Object.values(metadata)) {
790
+ this.createAndSetState(station, property);
791
+ }
792
+ if (station.hasCommand(import_eufy_security_client.CommandName.StationReboot)) {
793
+ await this.setObjectNotExistsAsync(station.getStateID(import_types.StationStateID.REBOOT), {
794
+ type: "state",
795
+ common: {
796
+ name: "Reboot station",
797
+ type: "boolean",
798
+ role: "button.start",
799
+ read: false,
800
+ write: true
801
+ },
802
+ native: {}
803
+ });
804
+ }
805
+ if (station.hasCommand(import_eufy_security_client.CommandName.StationTriggerAlarmSound)) {
806
+ await this.setObjectNotExistsAsync(station.getStateID(import_types.StationStateID.TRIGGER_ALARM_SOUND), {
807
+ type: "state",
808
+ common: {
809
+ name: "Trigger Alarm Sound",
810
+ type: "boolean",
811
+ role: "button.start",
812
+ read: false,
813
+ write: true
814
+ },
815
+ native: {}
816
+ });
817
+ await this.setObjectNotExistsAsync(station.getStateID(import_types.StationStateID.RESET_ALARM_SOUND), {
818
+ type: "state",
819
+ common: {
820
+ name: "Reset Alarm Sound",
821
+ type: "boolean",
822
+ role: "button.start",
823
+ read: false,
824
+ write: true
825
+ },
826
+ native: {}
827
+ });
828
+ }
829
+ }
830
+ async onStationRemoved(station) {
831
+ this.delObjectAsync(station.getStateID("", 0), { recursive: true }).catch((error) => {
832
+ this.logger.error(`Error deleting states of removed station`, error);
833
+ });
834
+ import_fs_extra.default.remove(path.join(utils.getAbsoluteInstanceDataDir(this), station.getSerial())).catch((error) => {
835
+ this.logger.error(`Error deleting fs contents of removed station`, error);
836
+ });
837
+ }
838
+ async handlePushNotification(message) {
839
+ try {
840
+ if (message.device_sn !== void 0) {
841
+ }
842
+ } catch (error) {
843
+ if (error instanceof import_eufy_security_client.DeviceNotFoundError) {
844
+ } else {
845
+ this.logger.error("Handling push notification - Error", error);
846
+ }
847
+ }
848
+ }
849
+ async onConnect() {
850
+ await this.setObjectNotExistsAsync("info", {
851
+ type: "channel",
852
+ common: {
853
+ name: "info"
854
+ },
855
+ native: {}
856
+ });
857
+ await this.setObjectNotExistsAsync("info.connection", {
858
+ type: "state",
859
+ common: {
860
+ name: "Global connection",
861
+ type: "boolean",
862
+ role: "indicator.connection",
863
+ read: true,
864
+ write: false
865
+ },
866
+ native: {}
867
+ });
868
+ await this.setStateAsync("info.connection", { val: true, ack: true });
869
+ const stations = await this.eufy.getStations();
870
+ const stationSerials = [];
871
+ for (const station of stations) {
872
+ stationSerials.push(station.getSerial());
873
+ }
874
+ const devices = await this.eufy.getDevices();
875
+ const deviceSerials = [];
876
+ for (const device of devices) {
877
+ deviceSerials.push(device.getSerial());
878
+ }
879
+ try {
880
+ const allDevices = await this.getDevicesAsync();
881
+ const reg = new RegExp(`^${this.namespace}.[0-9A-Z]+$`);
882
+ for (const id of allDevices) {
883
+ if (id._id.match(reg)) {
884
+ const serial = id._id.replace(`${this.namespace}.`, "");
885
+ if (!stationSerials.includes(serial)) {
886
+ await this.delObjectAsync(id._id, { recursive: true });
887
+ }
1315
888
  }
1316
- else {
1317
- this.logger.debug(`onDevicePropertyChanged(): Property "${name}" not implemented in this adapter (device: ${device.getSerial()} value: ${JSON.stringify(value)})`);
889
+ }
890
+ } catch (error) {
891
+ this.log.error(`Delete obsolete stations ERROR - ${JSON.stringify(error)}`);
892
+ }
893
+ try {
894
+ const allDevices = await this.getDevicesAsync();
895
+ const reg = new RegExp(`^${this.namespace}.[0-9A-Z]+.[a-z]+.[0-9A-Z]+$`);
896
+ for (const id of allDevices) {
897
+ if (id._id.match(reg)) {
898
+ const serial = id._id.split(".")[4];
899
+ if (!deviceSerials.includes(serial)) {
900
+ await this.delObjectAsync(id._id, { recursive: true });
901
+ }
1318
902
  }
903
+ }
904
+ } catch (error) {
905
+ this.log.error(`Delete obsolete devices ERROR - ${JSON.stringify(error)}`);
1319
906
  }
1320
- async startLivestream(device_sn) {
1321
- try {
1322
- const device = await this.eufy.getDevice(device_sn);
1323
- const station = await this.eufy.getStation(device.getStationSerial());
1324
- if (station.isConnected() || station.isEnergySavingDevice()) {
1325
- if (!station.isLiveStreaming(device)) {
1326
- this.eufy.startStationLivestream(device_sn);
907
+ try {
908
+ const all = await this.getStatesAsync("*");
909
+ if (all) {
910
+ Object.keys(all).forEach(async (stateid) => {
911
+ var _a;
912
+ const object = await this.getObjectAsync(stateid);
913
+ if (((_a = object == null ? void 0 : object.native) == null ? void 0 : _a.name) !== void 0) {
914
+ const tmp = stateid.split(".");
915
+ if (tmp.length >= 5) {
916
+ const stationSerial = tmp[2];
917
+ const deviceSerial = tmp[4];
918
+ if (deviceSerial.match(/^[A-Z0-9]+/)) {
919
+ try {
920
+ const device = await this.eufy.getDevice(deviceSerial);
921
+ if (!device.hasProperty(object.native.name)) {
922
+ this.delObjectAsync(stateid);
923
+ }
924
+ } catch (error) {
925
+ if (error instanceof import_eufy_security_client.DeviceNotFoundError) {
926
+ } else {
927
+ this.log.error(`Delete obsolete properties ERROR - device - ${JSON.stringify(error)}`);
928
+ }
1327
929
  }
1328
- else {
1329
- this.logger.warn(`The stream for the device ${device_sn} cannot be started, because it is already streaming!`);
930
+ } else {
931
+ try {
932
+ const station = await this.eufy.getStation(stationSerial);
933
+ if (!station.hasProperty(object.native.name)) {
934
+ this.delObjectAsync(stateid);
935
+ }
936
+ } catch (error) {
937
+ if (error instanceof import_eufy_security_client.StationNotFoundError) {
938
+ } else {
939
+ this.log.error(`Delete obsolete properties ERROR - station - ${JSON.stringify(error)}`);
940
+ }
1330
941
  }
942
+ }
1331
943
  }
1332
- else {
1333
- this.logger.warn(`The stream for the device ${device_sn} cannot be started, because there is no connection to station ${station.getSerial()}!`);
1334
- }
1335
- }
1336
- catch (error) {
1337
- this.logger.error("Start livestream - Error", error);
1338
- }
944
+ }
945
+ });
946
+ }
947
+ } catch (error) {
948
+ this.log.error(`Delete obsolete properties ERROR - ${JSON.stringify(error)}`);
1339
949
  }
1340
- async stopLivestream(device_sn) {
1341
- try {
1342
- const device = await this.eufy.getDevice(device_sn);
1343
- const station = await this.eufy.getStation(device.getStationSerial());
1344
- if (device.isCamera()) {
1345
- const camera = device;
1346
- if (await this.eufy.isStationConnected(device.getStationSerial()) && station.isLiveStreaming(camera)) {
1347
- await this.eufy.stopStationLivestream(device_sn);
1348
- }
1349
- else {
1350
- this.logger.warn(`The stream for the device ${device_sn} cannot be stopped, because it isn't streaming!`);
950
+ new Promise(async (resolve, reject) => {
951
+ try {
952
+ const dir_path = path.join(utils.getAbsoluteInstanceDataDir(this));
953
+ if (import_fs_extra.default.existsSync(dir_path)) {
954
+ for (const content of import_fs_extra.default.readdirSync(dir_path).filter((fn) => fn.match("^T[0-9A-Z]+$") !== null)) {
955
+ if (!stationSerials.includes(content)) {
956
+ import_fs_extra.default.removeSync(path.join(dir_path, content));
957
+ } else {
958
+ for (const dir of import_fs_extra.default.readdirSync(path.join(dir_path, content))) {
959
+ if (dir === import_types.DataLocation.LIVESTREAM || dir === import_types.DataLocation.LAST_LIVESTREAM || dir === import_types.DataLocation.TEMP) {
960
+ import_fs_extra.default.removeSync(path.join(dir_path, content, dir));
961
+ } else {
962
+ const files = import_fs_extra.default.readdirSync(path.join(dir_path, content, dir));
963
+ let deletedFiles = 0;
964
+ for (const file of files) {
965
+ if (!deviceSerials.includes(file.substring(0, 16))) {
966
+ import_fs_extra.default.removeSync(path.join(dir_path, content, dir, file));
967
+ deletedFiles++;
968
+ }
969
+ }
970
+ if (deletedFiles === files.length) {
971
+ import_fs_extra.default.removeSync(path.join(dir_path, content, dir));
972
+ }
1351
973
  }
974
+ }
1352
975
  }
976
+ }
1353
977
  }
1354
- catch (error) {
1355
- this.logger.error("Stop livestream - Error", error);
1356
- }
978
+ resolve();
979
+ } catch (error) {
980
+ reject(error);
981
+ }
982
+ }).catch((error) => {
983
+ this.log.error(`Delete obsolete directories/files ERROR - ${JSON.stringify(error)}`);
984
+ });
985
+ }
986
+ async onClose() {
987
+ await this.setStateAsync("info.connection", { val: false, ack: true }).catch();
988
+ }
989
+ getPersistentData() {
990
+ return this.persistentData;
991
+ }
992
+ async onPushConnect() {
993
+ await this.setObjectNotExistsAsync("info", {
994
+ type: "channel",
995
+ common: {
996
+ name: "info"
997
+ },
998
+ native: {}
999
+ });
1000
+ await this.setObjectNotExistsAsync("info.push_connection", {
1001
+ type: "state",
1002
+ common: {
1003
+ name: "Push notification connection",
1004
+ type: "boolean",
1005
+ role: "indicator.connection",
1006
+ read: true,
1007
+ write: false
1008
+ },
1009
+ native: {}
1010
+ });
1011
+ await this.setStateAsync("info.push_connection", { val: true, ack: true });
1012
+ }
1013
+ async onPushClose() {
1014
+ await this.setObjectNotExistsAsync("info", {
1015
+ type: "channel",
1016
+ common: {
1017
+ name: "info"
1018
+ },
1019
+ native: {}
1020
+ });
1021
+ await this.setObjectNotExistsAsync("info.push_connection", {
1022
+ type: "state",
1023
+ common: {
1024
+ name: "Push notification connection",
1025
+ type: "boolean",
1026
+ role: "indicator.connection",
1027
+ read: true,
1028
+ write: false
1029
+ },
1030
+ native: {}
1031
+ });
1032
+ await this.setStateAsync("info.push_connection", { val: false, ack: true });
1033
+ }
1034
+ async onMQTTConnect() {
1035
+ await this.setObjectNotExistsAsync("info", {
1036
+ type: "channel",
1037
+ common: {
1038
+ name: "info"
1039
+ },
1040
+ native: {}
1041
+ });
1042
+ await this.setObjectNotExistsAsync("info.mqtt_connection", {
1043
+ type: "state",
1044
+ common: {
1045
+ name: "MQTT connection",
1046
+ type: "boolean",
1047
+ role: "indicator.connection",
1048
+ read: true,
1049
+ write: false
1050
+ },
1051
+ native: {}
1052
+ });
1053
+ await this.setStateAsync("info.mqtt_connection", { val: true, ack: true });
1054
+ }
1055
+ async onMQTTClose() {
1056
+ await this.setObjectNotExistsAsync("info", {
1057
+ type: "channel",
1058
+ common: {
1059
+ name: "info"
1060
+ },
1061
+ native: {}
1062
+ });
1063
+ await this.setObjectNotExistsAsync("info.mqtt_connection", {
1064
+ type: "state",
1065
+ common: {
1066
+ name: "MQTT connection",
1067
+ type: "boolean",
1068
+ role: "indicator.connection",
1069
+ read: true,
1070
+ write: false
1071
+ },
1072
+ native: {}
1073
+ });
1074
+ await this.setStateAsync("info.mqtt_connection", { val: false, ack: true });
1075
+ }
1076
+ async onStationCommandResult(station, result) {
1077
+ if (result.return_code !== 0 && result.command_type === import_eufy_security_client.CommandType.CMD_START_REALTIME_MEDIA) {
1078
+ this.logger.debug(`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}) fallback to RTMP livestream...`);
1079
+ try {
1080
+ const device = await this.eufy.getStationDevice(station.getSerial(), result.channel);
1081
+ if (device.isCamera())
1082
+ this.eufy.startCloudLivestream(device.getSerial());
1083
+ } catch (error) {
1084
+ this.logger.error(`Station: ${station.getSerial()} command ${import_eufy_security_client.CommandType[result.command_type]} RTMP fallback failed - Error ${error}`);
1085
+ }
1086
+ } else if (result.return_code !== 0 && result.command_type !== import_eufy_security_client.CommandType.P2P_QUERY_STATUS_IN_LOCK) {
1087
+ this.logger.error(`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})`);
1357
1088
  }
1358
- async onStationLivestreamStart(station, device, metadata, videostream, audiostream) {
1359
- try {
1360
- this.setStateAsync(device.getStateID(types_1.DeviceStateID.LIVESTREAM), { val: `${this.config.https ? "https" : "http"}://${this.config.hostname}:${this.config.go2rtc_api_port}/stream.html?src=${device.getSerial()}`, ack: true });
1361
- this.setStateAsync(device.getStateID(types_1.DeviceStateID.LIVESTREAM_RTSP), { val: `rtsp://${this.config.hostname}:${this.config.go2rtc_rtsp_port}/${device.getSerial()}`, ack: true });
1362
- await (0, video_1.ffmpegStreamToGo2rtc)(this.config, this.namespace, device.getSerial(), metadata, videostream, audiostream, this.logger)
1363
- .catch(async (error) => {
1364
- this.logger.error(`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error - Stopping livestream...`, error);
1365
- await this.eufy.stopStationLivestream(device.getSerial());
1366
- });
1089
+ }
1090
+ async onStationPropertyChanged(station, name, value) {
1091
+ const states = await this.getStatesAsync(`${station.getStateID("", 1)}.*`);
1092
+ for (const state in states) {
1093
+ const obj = await this.getObjectAsync(state);
1094
+ if (obj) {
1095
+ if (obj.native.name !== void 0 && obj.native.name === name) {
1096
+ await (0, import_utils.setStateChangedAsync)(this, state, (obj.common.type === "string" || obj.common.type === "object") && typeof value === "object" ? JSON.stringify(value) : value);
1097
+ return;
1367
1098
  }
1368
- catch (error) {
1369
- this.logger.error(`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error - Stopping livestream...`, error);
1370
- await this.eufy.stopStationLivestream(device.getSerial());
1371
- }
1372
- }
1373
- onStationLivestreamStop(_station, device) {
1374
- this.delStateAsync(device.getStateID(types_1.DeviceStateID.LIVESTREAM));
1375
- this.delStateAsync(device.getStateID(types_1.DeviceStateID.LIVESTREAM_RTSP));
1099
+ }
1376
1100
  }
1377
- /*private async onStationDownloadFinish(_station: Station, _device: Device): Promise<void> {
1378
- //this.logger.trace(`Station: ${station.getSerial()} channel: ${channel}`);
1379
- }*/
1380
- /*private async onStationDownloadStart(station: Station, device: Device, metadata: StreamMetadata, videostream: Readable, audiostream: Readable): Promise<void> {
1381
- try {
1382
- //TODO: Deactivated because the decryption of the download has changed.
1383
- await removeFiles(this, station.getSerial(), DataLocation.TEMP, device.getSerial()).catch();
1384
- const file_path = getDataFilePath(this, station.getSerial(), DataLocation.TEMP, `${device.getSerial()}${STREAM_FILE_NAME_EXT}`);
1385
-
1386
- await ffmpegStreamToHls(this.config, this.namespace, metadata, videostream, audiostream, file_path, this.logger)
1387
- .then(async () => {
1388
- if (fse.pathExistsSync(file_path)) {
1389
- await removeFiles(this, station.getSerial(), DataLocation.LAST_EVENT, device.getSerial());
1390
- return true;
1391
- }
1392
- return false;
1393
- })
1394
- .then(async (result) => {
1395
- if (result)
1396
- await moveFiles(this, station.getSerial(), device.getSerial(), DataLocation.TEMP, DataLocation.LAST_EVENT);
1397
- return result;
1398
- })
1399
- .then(async (result) => {
1400
- if (result) {
1401
- const filename_without_ext = getDataFilePath(this, station.getSerial(), DataLocation.LAST_EVENT, device.getSerial());
1402
- setStateAsync(this, device.getStateID(DeviceStateID.LAST_EVENT_VIDEO_URL), "Last captured video URL", `/${this.namespace}/${station.getSerial()}/${DataLocation.LAST_EVENT}/${device.getSerial()}${STREAM_FILE_NAME_EXT}`, "url");
1403
- if (fse.pathExistsSync(`${filename_without_ext}${STREAM_FILE_NAME_EXT}`))
1404
- await ffmpegPreviewImage(this.config, `${filename_without_ext}${STREAM_FILE_NAME_EXT}`, `${filename_without_ext}${IMAGE_FILE_JPEG_EXT}`, this.logger)
1405
- .then(() => {
1406
- setStateAsync(this, device.getStateID(DeviceStateID.LAST_EVENT_PIC_URL), "Last event picture URL", `/${this.namespace}/${station.getSerial()}/${DataLocation.LAST_EVENT}/${device.getSerial()}${IMAGE_FILE_JPEG_EXT}`, "url");
1407
- try {
1408
- if (fse.existsSync(`${filename_without_ext}${IMAGE_FILE_JPEG_EXT}`)) {
1409
- const image_data = getImageAsHTML(fse.readFileSync(`${filename_without_ext}${IMAGE_FILE_JPEG_EXT}`));
1410
- setStateAsync(this, device.getStateID(DeviceStateID.LAST_EVENT_PIC_HTML), "Last event picture HTML image", image_data, "html");
1411
- }
1412
- } catch (error) {
1413
- this.logger.error(`Station: ${station.getSerial()} device: ${device.getSerial()} - Error`, error);
1414
- }
1415
- })
1416
- .catch((error) => {
1417
- this.logger.error(`ffmpegPreviewImage - station: ${station.getSerial()} device: ${device.getSerial()} - Error`, error);
1418
- });
1419
- }
1420
- })
1421
- .catch(async (error) => {
1422
- this.logger.error(`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error - Cancelling download...`, error);
1423
- await this.eufy.cancelStationDownload(device.getSerial());
1424
- });
1425
- } catch(error) {
1426
- this.logger.error(`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error - Cancelling download...`, error);
1427
- await this.eufy.cancelStationDownload(device.getSerial());
1101
+ this.logger.debug(`onStationPropertyChanged(): Property "${name}" not implemented in this adapter (station: ${station.getSerial()} value: ${JSON.stringify(value)})`);
1102
+ }
1103
+ async onDevicePropertyChanged(device, name, value) {
1104
+ const states = await this.getStatesAsync(`${device.getStateID("", 1)}.*`);
1105
+ for (const state in states) {
1106
+ const obj = await this.getObjectAsync(state);
1107
+ if (obj) {
1108
+ if (obj.native.name !== void 0 && obj.native.name === name) {
1109
+ await (0, import_utils.setStateChangedAsync)(this, state, (obj.common.type === "string" || obj.common.type === "object") && typeof value === "object" ? JSON.stringify(value) : value);
1110
+ switch (name) {
1111
+ case import_eufy_security_client.PropertyName.DeviceRTSPStream:
1112
+ if (value === false) {
1113
+ this.delStateAsync(device.getStateID(import_types.DeviceStateID.RTSP_STREAM_URL));
1114
+ }
1115
+ break;
1116
+ }
1117
+ return;
1428
1118
  }
1429
- }*/
1430
- onStationRTSPUrl(station, device, value) {
1431
- (0, utils_1.setStateChangedAsync)(this, device.getStateID(types_1.DeviceStateID.RTSP_STREAM_URL), value);
1119
+ }
1432
1120
  }
1433
- async onStationConnect(station) {
1434
- await this.setObjectNotExistsAsync(station.getStateID(types_1.StationStateID.CONNECTION), {
1435
- type: "state",
1436
- common: {
1437
- name: "Connection",
1438
- type: "boolean",
1439
- role: "indicator.connection",
1440
- read: true,
1441
- write: false,
1442
- },
1443
- native: {},
1444
- });
1445
- await this.setStateAsync(station.getStateID(types_1.StationStateID.CONNECTION), { val: true, ack: true });
1121
+ if (name === import_eufy_security_client.PropertyName.DevicePicture) {
1122
+ try {
1123
+ const picture = value;
1124
+ const fileName = `${device.getSerial()}.${picture.type.ext}`;
1125
+ const filePath = path.join(utils.getAbsoluteInstanceDataDir(this), device.getStationSerial(), import_types.DataLocation.LAST_EVENT);
1126
+ if (!import_fs_extra.default.existsSync(filePath)) {
1127
+ import_fs_extra.default.mkdirSync(filePath, { mode: 509, recursive: true });
1128
+ }
1129
+ await import_fs_extra.default.writeFile(path.join(filePath, fileName), picture.data);
1130
+ await (0, import_utils.setStateChangedAsync)(this, device.getStateID(import_types.DeviceStateID.PICTURE_URL), `/${this.namespace}/${device.getStationSerial()}/${import_types.DataLocation.LAST_EVENT}/${device.getSerial()}.${picture.type.ext}`);
1131
+ await (0, import_utils.setStateChangedAsync)(this, device.getStateID(import_types.DeviceStateID.PICTURE_HTML), (0, import_utils.getImageAsHTML)(picture.data, picture.type.mime));
1132
+ } catch (err) {
1133
+ const error = (0, import_eufy_security_client.ensureError)(err);
1134
+ this.logger.error("onDevicePropertyChanged - Property picture - Error", error);
1135
+ }
1136
+ } else {
1137
+ this.logger.debug(`onDevicePropertyChanged(): Property "${name}" not implemented in this adapter (device: ${device.getSerial()} value: ${JSON.stringify(value)})`);
1446
1138
  }
1447
- async onStationClose(station) {
1448
- await this.setObjectNotExistsAsync(station.getStateID(types_1.StationStateID.CONNECTION), {
1449
- type: "state",
1450
- common: {
1451
- name: "Connection",
1452
- type: "boolean",
1453
- role: "indicator.connection",
1454
- read: true,
1455
- write: false,
1456
- },
1457
- native: {},
1458
- });
1459
- await this.setStateAsync(station.getStateID(types_1.StationStateID.CONNECTION), { val: false, ack: true });
1139
+ }
1140
+ async startLivestream(device_sn) {
1141
+ try {
1142
+ const device = await this.eufy.getDevice(device_sn);
1143
+ const station = await this.eufy.getStation(device.getStationSerial());
1144
+ if (station.isConnected() || station.isEnergySavingDevice()) {
1145
+ if (!station.isLiveStreaming(device)) {
1146
+ this.eufy.startStationLivestream(device_sn);
1147
+ } else {
1148
+ this.logger.warn(`The stream for the device ${device_sn} cannot be started, because it is already streaming!`);
1149
+ }
1150
+ } else {
1151
+ this.logger.warn(`The stream for the device ${device_sn} cannot be started, because there is no connection to station ${station.getSerial()}!`);
1152
+ }
1153
+ } catch (error) {
1154
+ this.logger.error("Start livestream - Error", error);
1460
1155
  }
1461
- onTFARequest() {
1462
- this.logger.warn(`Two factor authentication request received, please enter valid verification code in state ${this.namespace}.verify_code`);
1463
- this.verify_code = true;
1156
+ }
1157
+ async stopLivestream(device_sn) {
1158
+ try {
1159
+ const device = await this.eufy.getDevice(device_sn);
1160
+ const station = await this.eufy.getStation(device.getStationSerial());
1161
+ if (device.isCamera()) {
1162
+ const camera = device;
1163
+ if (await this.eufy.isStationConnected(device.getStationSerial()) && station.isLiveStreaming(camera)) {
1164
+ await this.eufy.stopStationLivestream(device_sn);
1165
+ } else {
1166
+ this.logger.warn(`The stream for the device ${device_sn} cannot be stopped, because it isn't streaming!`);
1167
+ }
1168
+ }
1169
+ } catch (error) {
1170
+ this.logger.error("Stop livestream - Error", error);
1464
1171
  }
1465
- onCaptchaRequest(captchaId, captcha) {
1466
- this.captchaId = captchaId;
1467
- this.logger.warn(`Captcha authentication request received, please enter valid captcha in state ${this.namespace}.captcha`);
1468
- this.logger.warn(`Captcha: <img src="${captcha}">`);
1469
- this.setStateAsync("received_captcha_html", { val: `<img src="${captcha}">`, ack: true });
1172
+ }
1173
+ async onStationLivestreamStart(station, device, metadata, videostream, audiostream) {
1174
+ try {
1175
+ this.setStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM), { val: `${this.config.https ? "https" : "http"}://${this.config.hostname}:${this.config.go2rtc_api_port}/stream.html?src=${device.getSerial()}`, ack: true });
1176
+ this.setStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM_RTSP), { val: `rtsp://${this.config.hostname}:${this.config.go2rtc_rtsp_port}/${device.getSerial()}`, ack: true });
1177
+ await (0, import_video.ffmpegStreamToGo2rtc)(this.config, this.namespace, device.getSerial(), metadata, videostream, audiostream, this.logger).catch(async (error) => {
1178
+ this.logger.error(`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error - Stopping livestream...`, error);
1179
+ await this.eufy.stopStationLivestream(device.getSerial());
1180
+ });
1181
+ } catch (error) {
1182
+ this.logger.error(`Station: ${station.getSerial()} Device: ${device.getSerial()} - Error - Stopping livestream...`, error);
1183
+ await this.eufy.stopStationLivestream(device.getSerial());
1470
1184
  }
1185
+ }
1186
+ onStationLivestreamStop(_station, device) {
1187
+ this.delStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM));
1188
+ this.delStateAsync(device.getStateID(import_types.DeviceStateID.LIVESTREAM_RTSP));
1189
+ }
1190
+ onStationRTSPUrl(station, device, value) {
1191
+ (0, import_utils.setStateChangedAsync)(this, device.getStateID(import_types.DeviceStateID.RTSP_STREAM_URL), value);
1192
+ }
1193
+ async onStationConnect(station) {
1194
+ await this.setObjectNotExistsAsync(station.getStateID(import_types.StationStateID.CONNECTION), {
1195
+ type: "state",
1196
+ common: {
1197
+ name: "Connection",
1198
+ type: "boolean",
1199
+ role: "indicator.connection",
1200
+ read: true,
1201
+ write: false
1202
+ },
1203
+ native: {}
1204
+ });
1205
+ await this.setStateAsync(station.getStateID(import_types.StationStateID.CONNECTION), { val: true, ack: true });
1206
+ }
1207
+ async onStationClose(station) {
1208
+ await this.setObjectNotExistsAsync(station.getStateID(import_types.StationStateID.CONNECTION), {
1209
+ type: "state",
1210
+ common: {
1211
+ name: "Connection",
1212
+ type: "boolean",
1213
+ role: "indicator.connection",
1214
+ read: true,
1215
+ write: false
1216
+ },
1217
+ native: {}
1218
+ });
1219
+ await this.setStateAsync(station.getStateID(import_types.StationStateID.CONNECTION), { val: false, ack: true });
1220
+ }
1221
+ onTFARequest() {
1222
+ this.logger.warn(`Two factor authentication request received, please enter valid verification code in state ${this.namespace}.verify_code`);
1223
+ this.verify_code = true;
1224
+ }
1225
+ onCaptchaRequest(captchaId, captcha) {
1226
+ this.captchaId = captchaId;
1227
+ this.logger.warn(`Captcha authentication request received, please enter valid captcha in state ${this.namespace}.captcha`);
1228
+ this.logger.warn(`Captcha: <img src="${captcha}">`);
1229
+ this.setStateAsync("received_captcha_html", { val: `<img src="${captcha}">`, ack: true });
1230
+ }
1471
1231
  }
1472
- exports.euSec = euSec;
1473
- if (module.parent) {
1474
- // Export the constructor in compact mode
1475
- module.exports = (options) => new euSec(options);
1476
- }
1477
- else {
1478
- // otherwise start the instance directly
1479
- (() => new euSec())();
1232
+ if (require.main !== module) {
1233
+ module.exports = (options) => new euSec(options);
1234
+ } else {
1235
+ (() => new euSec())();
1480
1236
  }
1481
- //# sourceMappingURL=main.js.map
1237
+ //# sourceMappingURL=main.js.map