iobroker.device-watcher 2.15.13 → 2.15.15

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/main.js CHANGED
@@ -14,2468 +14,2552 @@ let isUnloaded = false;
14
14
  const adapterUpdateListDP = 'admin.*.info.updatesJson';
15
15
 
16
16
  class DeviceWatcher extends utils.Adapter {
17
- constructor(options) {
18
- super({
19
- ...options,
20
- name: adapterName,
21
- useFormatDate: true,
22
- });
23
-
24
- // instances and adapters
25
- // raw arrays
26
- this.listInstanceRaw = new Map();
27
- this.adapterUpdatesJsonRaw = [];
28
- this.listErrorInstanceRaw = [];
29
-
30
- // user arrays
31
- this.listAllInstances = [];
32
- this.listAllActiveInstances = [];
33
- this.listDeactivatedInstances = [];
34
- this.listAdapterUpdates = [];
35
- this.listErrorInstance = [];
36
-
37
- //counts
38
- this.countAllInstances = 0;
39
- this.countAllActiveInstances = 0;
40
- this.countDeactivatedInstances = 0;
41
- this.countAdapterUpdates = 0;
42
- this.countErrorInstance = 0;
43
-
44
- // devices
45
- // raw arrays
46
- this.listAllDevicesRaw = new Map();
47
- this.batteryLowPoweredRaw = [];
48
- this.offlineDevicesRaw = [];
49
- this.upgradableDevicesRaw = [];
50
-
51
- // arrays
52
- this.listAllDevicesUserRaw = [];
53
- this.listAllDevices = [];
54
- this.offlineDevices = [];
55
- this.linkQualityDevices = [];
56
- this.batteryPowered = [];
57
- this.batteryLowPowered = [];
58
- this.selAdapter = [];
59
- this.adapterSelected = [];
60
- this.upgradableList = [];
61
-
62
- // counts
63
- this.offlineDevicesCount = 0;
64
- this.deviceCounter = 0;
65
- this.linkQualityCount = 0;
66
- this.batteryPoweredCount = 0;
67
- this.lowBatteryPoweredCount = 0;
68
- this.upgradableDevicesCount = 0;
69
-
70
- // Blacklist
71
- // Instances
72
- this.blacklistInstancesLists = [];
73
- this.blacklistInstancesNotify = [];
74
-
75
- // Devices
76
- this.blacklistLists = [];
77
- this.blacklistAdapterLists = [];
78
- this.blacklistNotify = [];
79
-
80
- // Timelist instances
81
- this.userTimeInstancesList = new Map();
82
-
83
- // Interval timer
84
- this.refreshDataTimeout = null;
85
-
86
- // Check if main function is running
87
- this.mainRunning = false;
88
-
89
- // Pending rescan flag (set if a new device was detected while main() was running)
90
- this.pendingRescan = false;
91
-
92
- this.on('ready', this.onReady.bind(this));
93
- this.on('stateChange', this.onStateChange.bind(this));
94
- this.on('objectChange', this.onObjectChange.bind(this));
95
- this.on('message', this.onMessage.bind(this));
96
- this.on('unload', this.onUnload.bind(this));
97
- }
98
-
99
- /**
100
- * onReady
101
- */
102
- async onReady() {
103
- this.log.debug(`Adapter ${adapterName} was started`);
104
-
105
- // set user language
106
- if (this.config.userSelectedLanguage === '') {
107
- if (this.language !== undefined && this.language !== null) {
108
- this.config.userSelectedLanguage = this.language;
109
- } else {
110
- this.config.userSelectedLanguage = 'de';
111
- }
112
- }
113
- this.log.debug(`Set language to ${this.config.userSelectedLanguage}`);
114
-
115
- this.configCreateInstanceList = this.config.checkAdapterInstances;
116
- this.configListOnlyBattery = this.config.listOnlyBattery;
117
- this.configCreateOwnFolder = this.config.createOwnFolder;
118
- this.configCreateHtmlList = this.config.createHtmlList;
119
-
120
- try {
121
- // create list with enabled adapters for monitor devices
122
- for (const device of Object.values(this.config.tableDevices)) {
123
- if (device.enabled) {
124
- for (const [_adapterName, adapter] of Object.entries(adapterArray)) {
125
- if (String(adapter.adapterKey).toLowerCase() === String(device.adapterKey).toLowerCase()) {
126
- this.selAdapter.push(adapter);
127
- this.adapterSelected.push(adapter.adapterKey);
128
- break;
129
- }
130
- }
131
- }
132
- }
133
-
134
- // Check if an adapter to monitor devices is selected.
135
- if (this.adapterSelected.length >= 1) {
136
- this.log.debug(JSON.stringify(this.selAdapter));
137
- this.log.info(`Number of selected adapters to monitor devices: ${this.adapterSelected.length}. Loading data from: ${this.adapterSelected.join(', ')} ...`);
138
- } else {
139
- this.log.info(`No adapters selected to monitor devices.`);
140
- }
141
-
142
- // create Blacklist
143
- await crud.createBlacklist(this);
144
-
145
- // create user defined list with time of error for instances
146
- await crud.createTimeListInstances(this);
147
-
148
- //create datapoints for each adapter if selected
149
- for (const [id] of Object.entries(adapterArray)) {
150
- try {
151
- if (!this.configCreateOwnFolder) {
152
- await crud.deleteDPsForEachAdapter(this, id);
153
- await crud.deleteHtmlListDatapoints(this, id);
154
- } else {
155
- const adapter = adapterArray[id];
156
-
157
- if (this.adapterSelected.includes(adapter.adapterKey)) {
158
- await crud.createDPsForEachAdapter(this, id);
159
- // create HTML list datapoints
160
- if (!this.configCreateHtmlList) {
161
- await crud.deleteHtmlListDatapoints(this, id);
162
- } else {
163
- await crud.createHtmlListDatapoints(this, id);
164
- }
165
- this.log.debug(`Created datapoints for ${tools.capitalize(id)}`);
166
- }
167
- }
168
- } catch (error) {
169
- this.log.error(`[onReady - create and fill datapoints for each adapter] - ${error}`);
170
- }
171
- }
172
-
173
- // create HTML list datapoints
174
- if (!this.configCreateHtmlList) {
175
- await crud.deleteHtmlListDatapoints(this);
176
- await crud.deleteHtmlListDatapointsInstances(this);
177
- } else {
178
- await crud.createHtmlListDatapoints(this);
179
- if (this.config.checkAdapterInstances) {
180
- await crud.createHtmlListDatapointsInstances(this);
181
- }
182
- }
183
- if (!this.config.checkAdapterInstances) {
184
- await crud.deleteHtmlListDatapointsInstances(this);
185
- }
186
-
187
- // instances and adapters
188
- if (this.configCreateInstanceList) {
189
- // instances
190
- await crud.createDPsForInstances(this);
191
- await this.getAllInstanceData();
192
- // adapter updates
193
- await crud.createAdapterUpdateData(this, adapterUpdateListDP);
194
- } else {
195
- await crud.deleteDPsForInstances(this);
196
- }
197
-
198
- await this.main();
199
-
200
- // update last contact data in interval
201
- await this.refreshData();
202
-
203
- // send overview for low battery devices
204
- if (this.config.checkSendBatteryMsgDaily) {
205
- await this.sendScheduleNotifications('lowBatteryDevices');
206
- }
207
-
208
- // send overview of offline devices
209
- if (this.config.checkSendOfflineMsgDaily) {
210
- await this.sendScheduleNotifications('offlineDevices');
211
- }
212
-
213
- // send overview of upgradeable devices
214
- if (this.config.checkSendUpgradeMsgDaily) {
215
- await this.sendScheduleNotifications('updateDevices');
216
- }
217
-
218
- // send overview of updatable adapters
219
- if (this.config.checkSendAdapterUpdateMsgDaily) {
220
- await this.sendScheduleNotifications('updateAdapter');
221
- }
222
-
223
- // send overview of deactivated instances
224
- if (this.config.checkSendInstanceDeactivatedDaily) {
225
- await this.sendScheduleNotifications('deactivatedInstance');
226
- }
227
-
228
- // send overview of instances with error
229
- if (this.config.checkSendInstanceFailedDaily) {
230
- await this.sendScheduleNotifications('errorInstance');
231
- }
232
- } catch (error) {
233
- this.log.error(`[onReady] - ${error}`);
234
- this.terminate ? this.terminate(15) : process.exit(15);
235
- }
236
- } // <-- onReady end
237
-
238
- /**
239
- * main function
240
- */
241
- async main() {
242
- this.log.debug(`Function started main`);
243
- this.mainRunning = true;
244
-
245
- // cancel run if no adapter is selected
246
- if (this.adapterSelected.length === 0) {
247
- return;
248
- }
249
-
250
- // fill counts and lists of all selected adapter
251
- try {
252
- for (let i = 0; i < this.selAdapter.length; i++) {
253
- await crud.createData(this, i);
254
- await crud.createLists(this);
255
- }
256
- await crud.writeDatapoints(this); // fill the datapoints
257
- this.log.debug(`Created and filled data for all adapters`);
258
- } catch (error) {
259
- this.log.error(`[main - create data of all adapter] - ${error}`);
260
- }
261
-
262
- // fill datapoints for each adapter if selected
263
- if (this.configCreateOwnFolder) {
264
- try {
265
- for (const [id] of Object.entries(adapterArray)) {
266
- const adapter = adapterArray[id];
267
-
268
- if (this.adapterSelected.includes(adapter.adapterKey)) {
269
- for (const deviceData of this.listAllDevicesRaw.values()) {
270
- // list device only if selected adapter matched with device
271
- if (!deviceData.adapterID.includes(id)) {
272
- continue;
273
- }
274
- await crud.createLists(this, id);
275
- }
276
- await crud.writeDatapoints(this, id); // fill the datapoints
277
- this.log.debug(`Created and filled data for ${tools.capitalize(id)}`);
278
- }
279
- }
280
- } catch (error) {
281
- this.log.error(`[main - create and fill datapoints for each adapter] - ${error}`);
282
- }
283
- }
284
- this.mainRunning = false;
285
- this.log.debug(`Function finished: ${this.main.name}`);
286
-
287
- // If a new device was detected while main() was running, trigger a rescan now
288
- if (this.pendingRescan) {
289
- this.pendingRescan = false;
290
- this.log.info(`[main] Pending rescan detected – restarting main() for new device`);
291
- await this.main();
292
- }
293
- } //<--End of main function
294
-
295
- // If you need to react to object changes, uncomment the following block and the corresponding line in the constructor.
296
- // You also need to subscribe to the objects with `this.subscribeObjects`, similar to `this.subscribeStates`.
297
- //
298
-
299
- async onObjectChange(id, obj) {
300
- if (obj) {
301
- try {
302
- // The object was changed
303
- //this.log.debug(`object ${id} changed: ${JSON.stringify(obj)}`);
304
-
305
- if (this.config.checkAdapterInstances && id.startsWith('system.adapter.')) {
306
- //read new instance data and add it to the lists
307
- if (typeof id === 'string' && /\d$/.test(id)) {
308
- await this.getInstanceData(id);
309
- }
310
- } else {
311
- if (Array.from(this.listAllDevicesRaw.values()).some((obj) => obj.mainSelector === id)) {
312
- if (!this.mainRunning) {
313
- await this.main();
314
- } else {
315
- this.pendingRescan = true;
316
- }
317
- } else {
318
- // Check if the changed object belongs to a monitored adapter (new device)
319
- const belongsToMonitoredAdapter = this.adapterSelected.some((adapterKey) =>
320
- id.toLowerCase().startsWith(`${adapterKey.toLowerCase() }.`)
321
- );
322
-
323
- if (belongsToMonitoredAdapter) {
324
- if (!this.mainRunning) {
325
- this.log.info(`[onObjectChange] New device detected: ${id} – triggering rescan`);
326
- await this.main();
327
- } else {
328
- this.log.debug(`[onObjectChange] main() is running – rescan for ${id} queued`);
329
- this.pendingRescan = true;
330
- }
331
- }
332
- }
333
- }
334
- } catch (error) {
335
- this.log.error(`Issue at object change: ${error}`);
336
- }
337
- } else {
338
- try {
339
- // The object was deleted
340
- this.log.debug(`object ${id} deleted`);
341
-
342
- // delete instance data in map
343
- if (this.listInstanceRaw.has(id)) {
344
- this.listInstanceRaw.delete(id);
345
- }
346
-
347
- // delete device data in map
348
- if (this.listAllDevicesRaw.has(id)) {
349
- this.listAllDevicesRaw.delete(id);
350
- }
351
- // also remove all child devices if a parent/adapter object was deleted
352
- const idPrefix = `${id }.`;
353
- for (const key of this.listAllDevicesRaw.keys()) {
354
- if (key.startsWith(idPrefix)) {
355
- this.log.debug(`[onObjectChange] removing child device from map: ${key}`);
356
- this.listAllDevicesRaw.delete(key);
357
- }
358
- }
359
-
360
- //unsubscribe of Objects and states
361
- this.unsubscribeForeignObjects(id);
362
- this.unsubscribeForeignStates(id);
363
- } catch (error) {
364
- this.log.error(`Issue at object deletion: ${error}`);
365
- }
366
- }
367
- }
368
-
369
- async onStateChange(id, state) {
370
- if (state) {
371
- // this.log.debug(`State changed: ${id} changed ${state.val}`);
372
- try {
373
- /*=============================================
374
- = Instances / Adapter =
375
- =============================================*/
376
- if (this.config.checkAdapterInstances) {
377
- // Adapter Update data
378
- if (id.endsWith('updatesJson')) {
379
- await this.renewAdapterUpdateData(id);
380
- }
381
- // Instanz data
382
- if (Array.from(this.listInstanceRaw.values()).some((obj) => Object.values(obj).includes(id))) {
383
- await this.renewInstanceData(id, state);
384
- }
385
- }
386
-
387
- /*=============================================
388
- = Devices =
389
- =============================================*/
390
- if (Array.from(this.listAllDevicesRaw.values()).some((obj) => Object.values(obj).includes(id))) {
391
- await this.renewDeviceData(id, state);
392
-
393
- // Update lists and datapoints immediately after device data change
394
- await crud.createLists(this);
395
- await crud.writeDatapoints(this);
396
-
397
- // Also update per-adapter folder if configured
398
- if (this.configCreateOwnFolder) {
399
- for (const [adId] of Object.entries(adapterArray)) {
400
- const adapter = adapterArray[adId];
401
- if (this.adapterSelected.includes(adapter.adapterKey)) {
402
- await crud.createLists(this, adId);
403
- await crud.writeDatapoints(this, adId);
404
- }
405
- }
406
- }
407
- }
408
- } catch (error) {
409
- this.log.error(`Issue at state change: ${id}`);
410
- }
411
- } else {
412
- // The state was deleted
413
- this.log.debug(`state ${id} deleted`);
414
- }
415
- }
416
-
417
- onMessage(obj) {
418
- const devices = [];
419
- const instances = [];
420
- const instancesTime = [];
421
- let countDevices = 0;
422
- let countInstances = 0;
423
-
424
- switch (obj.command) {
425
- case 'devicesList':
426
- if (obj.message) {
427
- try {
428
- for (const deviceData of this.listAllDevicesRaw.values()) {
429
- const label = `${deviceData.Adapter}: ${deviceData.Device}`;
430
- const valueObjectDevices = {
431
- deviceName: deviceData.Device,
432
- adapter: deviceData.Adapter,
433
- path: deviceData.Path,
434
- };
435
- devices[countDevices] = { label: label, value: JSON.stringify(valueObjectDevices) };
436
- countDevices++;
437
- }
438
- const sortDevices = devices.slice(0);
439
- sortDevices.sort(function (a, b) {
440
- const x = a.label;
441
- const y = b.label;
442
- return x < y ? -1 : x > y ? 1 : 0;
443
- });
444
- this.sendTo(obj.from, obj.command, sortDevices, obj.callback);
445
- } catch (error) {
446
- this.log.error(`[onMessage - deviceList for blacklisttable] - ${error}`);
447
- }
448
- }
449
- break;
450
-
451
- case 'instancesList':
452
- if (obj.message) {
453
- try {
454
- for (const [instance, instanceData] of this.listInstanceRaw) {
455
- const label = `${instanceData.Adapter}: ${instance}`;
456
- const valueObjectInstances = {
457
- adapter: instanceData.Adapter,
458
- instanceID: instance,
459
- };
460
- instances[countInstances] = { label: label, value: JSON.stringify(valueObjectInstances) };
461
- countInstances++;
462
- }
463
- const sortInstances = instances.slice(0);
464
- sortInstances.sort(function (a, b) {
465
- const x = a.label;
466
- const y = b.label;
467
- return x < y ? -1 : x > y ? 1 : 0;
468
- });
469
- this.sendTo(obj.from, obj.command, sortInstances, obj.callback);
470
- } catch (error) {
471
- this.log.error(`[onMessage - instanceList] - ${error}`);
472
- }
473
- }
474
- break;
475
- case 'instancesListTime':
476
- if (obj.message) {
477
- try {
478
- for (const [instance, instanceData] of this.listInstanceRaw) {
479
- const label = `${instanceData.Adapter}: ${instance}`;
480
- const valueObjectInstances = {
481
- adapter: instanceData.Adapter,
482
- instanceName: instance,
483
- };
484
- instancesTime[countInstances] = { label: label, value: JSON.stringify(valueObjectInstances) };
485
- countInstances++;
486
- }
487
- const sortInstances = instancesTime.slice(0);
488
- sortInstances.sort(function (a, b) {
489
- const x = a.label;
490
- const y = b.label;
491
- return x < y ? -1 : x > y ? 1 : 0;
492
- });
493
- this.sendTo(obj.from, obj.command, sortInstances, obj.callback);
494
- } catch (error) {
495
- this.log.error(`[onMessage - instanceList] - ${error}`);
496
- }
497
- }
498
- break;
499
- default:
500
- this.log.warn(`[onMessage] Unknown command: ${obj.command}`);
501
- break;
502
- }
503
- }
504
-
505
- /**
506
- * refresh data with interval
507
- * is neccessary to refresh lastContact data, especially of devices without state changes
508
- */
509
- async refreshData() {
510
- if (isUnloaded) {
511
- return;
512
- } // cancel run if unloaded was called.
513
- const nextTimeout = this.config.updateinterval * 1000;
514
-
515
- // devices data
516
- await tools.checkLastContact(this);
517
- await crud.createLists(this);
518
- await crud.writeDatapoints(this);
519
-
520
- // devices data in own adapter folder
521
- if (this.configCreateOwnFolder) {
522
- for (const [id] of Object.entries(adapterArray)) {
523
- const adapter = adapterArray[id];
524
-
525
- if (this.adapterSelected.includes(adapter.adapterKey)) {
526
- await crud.createLists(this, id);
527
- await crud.writeDatapoints(this, id);
528
- this.log.debug(`Created and filled data for ${tools.capitalize(id)}`);
529
- }
530
- }
531
- }
532
-
533
- // instance and adapter data
534
- if (this.configCreateInstanceList) {
535
- await this.createInstanceList();
536
- await this.writeInstanceDPs();
537
- }
538
-
539
- // Clear existing timeout
540
- if (this.refreshDataTimeout) {
541
- this.clearTimeout(this.refreshDataTimeout);
542
- this.refreshDataTimeout = null;
543
- }
544
-
545
- this.refreshDataTimeout = this.setTimeout(async () => {
546
- this.log.debug('Updating Data');
547
- await this.refreshData();
548
- }, nextTimeout);
549
- } // <-- refreshData end
550
-
551
- /*=============================================
552
- = functions to get data =
553
- =============================================*/
554
-
555
- /**
556
- * @param {object} id - deviceID
557
- * @param {object} i - each Device
558
- */
559
- async getDeviceName(id, i) {
560
- try {
561
- //id = id.replace(/[\]\\[.*,;'"`<>\\\s?]/g, '-');
562
-
563
- const currDeviceString = id.slice(0, id.lastIndexOf('.') + 1 - 1);
564
- const shortCurrDeviceString = currDeviceString.slice(0, currDeviceString.lastIndexOf('.') + 1 - 1);
565
- const shortshortCurrDeviceString = shortCurrDeviceString.slice(0, shortCurrDeviceString.lastIndexOf('.') + 1 - 1);
566
-
567
- // Get device name
568
- const deviceObject = await this.getForeignObjectAsync(currDeviceString);
569
- const shortDeviceObject = await this.getForeignObjectAsync(shortCurrDeviceString);
570
- const shortshortDeviceObject = await this.getForeignObjectAsync(shortshortCurrDeviceString);
571
- let deviceName;
572
- let folderName;
573
- let deviceID;
574
-
575
- switch (this.selAdapter[i].adapterID) {
576
- case 'fullybrowser':
577
- deviceName = `${await tools.getInitValue(this, currDeviceString + this.selAdapter[i].id)} ${await tools.getInitValue(this, currDeviceString + this.selAdapter[i].id2)}`;
578
- break;
579
-
580
- // Get ID with short currDeviceString from objectjson
581
- case 'hueExt':
582
- case 'hmrpc':
583
- case 'matter':
584
- case 'nukiExt':
585
- case 'wled':
586
- case 'mqttNuki':
587
- case 'loqedSmartLock':
588
- case 'viessmann':
589
- case 'homekitController':
590
- case 'ring':
591
- if (shortDeviceObject && typeof shortDeviceObject === 'object' && shortDeviceObject.common) {
592
- deviceName = shortDeviceObject.common.name;
593
- }
594
- break;
595
-
596
- // Get ID with short short currDeviceString from objectjson (HMiP Devices)
597
- case 'hmiP':
598
- if (shortshortDeviceObject && typeof shortshortDeviceObject === 'object' && shortshortDeviceObject.common) {
599
- deviceName = shortshortDeviceObject.common.name;
600
- }
601
- break;
602
-
603
- // Get ID with short currDeviceString from datapoint
604
- case 'mihomeVacuum':
605
- case 'roomba':
606
- folderName = shortCurrDeviceString.slice(shortCurrDeviceString.lastIndexOf('.') + 1);
607
- deviceID = await tools.getInitValue(this, shortCurrDeviceString + this.selAdapter[i].id);
608
- deviceName = `I${folderName} ${deviceID}`;
609
- break;
610
-
611
- //Get ID of foldername
612
- case 'tado':
613
- case 'wifilight':
614
- case 'fullybrowserV3':
615
- case 'sonoff':
616
- deviceName = currDeviceString.slice(currDeviceString.lastIndexOf('.') + 1);
617
- break;
618
-
619
- // Format Device name
620
- case 'sureflap':
621
- if (deviceObject && typeof deviceObject === 'object' && deviceObject.common) {
622
- deviceName = deviceObject.common.name
623
- .replace(/'/g, '')
624
- .replace(/\(\d+\)/g, '')
625
- .trim()
626
- .replace('Hub', 'Hub -')
627
- .replace('Device', 'Device -');
628
- }
629
- break;
630
-
631
- //Get ID of foldername
632
- case 'yeelight':
633
- deviceName = shortCurrDeviceString.slice(shortCurrDeviceString.lastIndexOf('.') + 1);
634
- break;
635
-
636
- // Get ID with main selektor from objectjson
637
- default:
638
- if (this.selAdapter[i].id !== 'none' || this.selAdapter[i].id !== undefined) {
639
- deviceName = await tools.getInitValue(this, currDeviceString + this.selAdapter[i].id);
640
- }
641
- if (deviceName === null || deviceName === undefined) {
642
- if (deviceObject && typeof deviceObject === 'object' && deviceObject.common) {
643
- deviceName = deviceObject.common.name;
644
- }
645
- }
646
- break;
647
- }
648
- return deviceName;
649
- } catch (error) {
650
- this.log.error(`[getDeviceName] - ${error}`);
651
- }
652
- }
653
-
654
- /**
655
- * calculate Signalstrength
656
- *
657
- * @param {object} deviceQualityState - State value
658
- * @param {object} adapterID - adapter name
659
- */
660
- async calculateSignalStrength(deviceQualityState, adapterID) {
661
- let linkQuality;
662
- let linkQualityRaw;
663
- let mqttNukiValue;
664
-
665
- if (deviceQualityState != null) {
666
- const { val } = deviceQualityState;
667
-
668
- if (typeof val === 'number') {
669
- if (this.config.trueState) {
670
- linkQuality = val;
671
- } else {
672
- switch (adapterID) {
673
- case 'roomba':
674
- case 'sonoff':
675
- case 'smartgarden':
676
- linkQuality = `${val}%`;
677
- linkQualityRaw = val;
678
- break;
679
- case 'lupusec':
680
- case 'fullybrowserV3':
681
- linkQuality = val;
682
- break;
683
- default:
684
- if (val <= -255) {
685
- linkQuality = ' - ';
686
- } else if (val < 0) {
687
- linkQualityRaw = Math.min(Math.max(2 * (val + 100), 0), 100);
688
- linkQuality = `${linkQualityRaw}%`;
689
- } else if (val >= 0) {
690
- linkQualityRaw = parseFloat(((100 / 255) * val).toFixed(0));
691
- linkQuality = `${linkQualityRaw}%`;
692
- }
693
- break;
694
- }
695
- }
696
- } else if (typeof val === 'string') {
697
- switch (adapterID) {
698
- case 'netatmo':
699
- linkQuality = val;
700
- break;
701
- case 'nukiExt':
702
- linkQuality = ' - ';
703
- break;
704
- case 'mqttNuki':
705
- linkQuality = val;
706
- mqttNukiValue = parseInt(linkQuality);
707
- if (this.config.trueState) {
708
- linkQuality = val;
709
- } else if (mqttNukiValue < 0) {
710
- linkQualityRaw = Math.min(Math.max(2 * (mqttNukiValue + 100), 0), 100);
711
- linkQuality = `${linkQualityRaw}%`;
712
- }
713
- break;
714
- }
715
- }
716
- } else {
717
- linkQuality = ' - ';
718
- }
719
- return [linkQuality, linkQualityRaw];
720
- }
721
-
722
- /**
723
- * get battery data
724
- *
725
- * @param {object} deviceBatteryState - State value
726
- * @param {object} deviceLowBatState - State value
727
- * @param {object} faultReportingState - State value
728
- * @param {object} adapterID - adapter name
729
- */
730
- async getBatteryData(deviceBatteryState, deviceLowBatState, faultReportingState, adapterID) {
731
- let batteryHealth = '-';
732
- let isBatteryDevice = false;
733
- let batteryHealthRaw;
734
- let batteryHealthUnitRaw;
735
-
736
- switch (adapterID) {
737
- case 'lupusec':
738
- if (deviceBatteryState === undefined) {
739
- if (deviceLowBatState === 1) {
740
- batteryHealth = 'ok';
741
- isBatteryDevice = true;
742
- } else {
743
- batteryHealth = 'low';
744
- isBatteryDevice = true;
745
- }
746
- }
747
- break;
748
- case 'hmrpc':
749
- if (deviceBatteryState === undefined) {
750
- if (faultReportingState !== undefined && faultReportingState !== 6) {
751
- batteryHealth = 'ok';
752
- isBatteryDevice = true;
753
- } else if (deviceLowBatState !== undefined && deviceLowBatState !== 1) {
754
- batteryHealth = 'ok';
755
- isBatteryDevice = true;
756
- } else if (deviceLowBatState !== undefined) {
757
- batteryHealth = 'low';
758
- isBatteryDevice = true;
759
- }
760
- } else if (deviceBatteryState !== 0 && deviceBatteryState < 6) {
761
- batteryHealth = `${deviceBatteryState}V`;
762
- batteryHealthRaw = deviceBatteryState;
763
- batteryHealthUnitRaw = 'V';
764
- isBatteryDevice = true;
765
- }
766
- break;
767
- case 'xsense':
768
- if (deviceBatteryState === undefined) {
769
- // do nothin brdge has no battery
770
- isBatteryDevice = false;
771
- } else if (deviceBatteryState >= 0) {
772
- batteryHealthRaw = deviceBatteryState;
773
- batteryHealthUnitRaw = '';
774
- isBatteryDevice = true;
775
- switch (batteryHealthRaw) {
776
- case 1:
777
- batteryHealth = 'low';
778
- break;
779
- case 2:
780
- batteryHealth = 'medium';
781
- break;
782
- case 3:
783
- batteryHealth = 'ok';
784
- break;
785
- default:
786
- batteryHealth = 'error';
787
- }
788
- }
789
- break;
790
- default:
791
- if (deviceBatteryState === undefined) {
792
- if (deviceLowBatState !== undefined) {
793
- // Explicit OK states: false, 'NORMAL', 0 (some adapters use 0=ok)
794
- if (
795
- deviceLowBatState === false ||
796
- deviceLowBatState === 'NORMAL' ||
797
- deviceLowBatState === 0
798
- ) {
799
- batteryHealth = 'ok';
800
- isBatteryDevice = true;
801
- } else {
802
- // true, 1, any other string != 'NORMAL' → low
803
- batteryHealth = 'low';
804
- isBatteryDevice = true;
805
- }
806
- }
807
- } else {
808
- if (typeof deviceBatteryState === 'string') {
809
- if (deviceBatteryState === 'high' || deviceBatteryState === 'medium') {
810
- batteryHealth = 'ok';
811
- isBatteryDevice = true;
812
- } else if (deviceBatteryState === 'low') {
813
- batteryHealth = 'low';
814
- isBatteryDevice = true;
815
- }
816
- } else {
817
- batteryHealth = `${deviceBatteryState}%`;
818
- batteryHealthRaw = deviceBatteryState;
819
- batteryHealthUnitRaw = '%';
820
- isBatteryDevice = true;
821
- }
822
- }
823
- break;
824
- }
825
-
826
- return [batteryHealth, isBatteryDevice, batteryHealthRaw, batteryHealthUnitRaw];
827
- }
828
-
829
- /**
830
- * set low bat indicator
831
- *
832
- * @param {object} deviceBatteryState
833
- * @param {object} deviceLowBatState
834
- * @param {object} faultReportState
835
- * @param {object} adapterID
836
- */
837
-
838
- async setLowbatIndicator(deviceBatteryState, deviceLowBatState, faultReportState, adapterID) {
839
- let lowBatIndicator = false;
840
-
841
- if (deviceLowBatState !== undefined || faultReportState !== undefined) {
842
- switch (adapterID) {
843
- case 'hmrpc':
844
- if (deviceLowBatState === 1 || deviceLowBatState === true || faultReportState === 6) {
845
- lowBatIndicator = true;
846
- }
847
- break;
848
- default:
849
- if (typeof deviceLowBatState === 'boolean' && deviceLowBatState === true) {
850
- // true = low bat
851
- lowBatIndicator = true;
852
- } else if (typeof deviceLowBatState === 'string' && deviceLowBatState !== 'NORMAL') {
853
- // any string other than 'NORMAL' = low bat
854
- lowBatIndicator = true;
855
- } else if (typeof deviceLowBatState === 'number' && deviceLowBatState === 1) {
856
- // 1 = low bat (0 = ok), consistent with getBatteryData
857
- lowBatIndicator = true;
858
- }
859
- }
860
- } else if (typeof deviceBatteryState === 'number' && deviceBatteryState < this.config.minWarnBatterie) {
861
- lowBatIndicator = true;
862
- } else if (typeof deviceBatteryState === 'string' && deviceBatteryState === 'low') {
863
- lowBatIndicator = true;
864
- }
865
-
866
- return lowBatIndicator;
867
- }
868
-
869
- /**
870
- * get Last Contact
871
- *
872
- * @param {object} selector - Selector
873
- */
874
- async getLastContact(selector) {
875
- const lastContact = tools.getTimestamp(selector);
876
-
877
- let lastContactString;
878
-
879
- if (lastContact >= 3600) {
880
- lastContactString = `${(lastContact / 3600).toFixed(1)} ${translations.hours[this.config.userSelectedLanguage]}`;
881
- } else if (lastContact >= 60) {
882
- lastContactString = `${Math.round(lastContact / 60)} ${translations.minits[this.config.userSelectedLanguage]}`;
883
- } else {
884
- lastContactString = `${Math.round(lastContact)} ${translations.secs[this.config.userSelectedLanguage]}`;
885
- }
886
-
887
- return lastContactString;
888
- }
889
-
890
- async getOnlineState(timeSelector, adapterID, treeDP, linkQuality, deviceUnreachState, deviceStateSelectorHMRPC, rssiPeerSelectorHMRPC) {
891
- let lastContactString;
892
- let lastContact;
893
- let deviceState = 'Online';
894
- let linkQualitySet = linkQuality ?? '0%';
895
-
896
- try {
897
- const deviceTimeSelector = await this.getForeignStateAsync(timeSelector);
898
- const deviceUnreachSelector = await this.getForeignStateAsync(treeDP);
899
-
900
- const lastDeviceUnreachStateChange = deviceUnreachSelector?.lc != null ? tools.getTimestamp(deviceUnreachSelector.lc) : tools.getTimestamp(deviceTimeSelector?.ts ?? Date.now());
901
-
902
- // ignore disabled device from zigbee2MQTT
903
- if (adapterID === 'zigbee2MQTT') {
904
- const is_device_disabled = await tools.isDisabledDevice(this, treeDP.substring(0, treeDP.lastIndexOf('.')));
905
-
906
- if (is_device_disabled) {
907
- return [null, 'disabled', ' - '];
908
- }
909
- }
910
-
911
- if (adapterID === 'hmrpc') {
912
- const deviceState = await this.getForeignStateAsync(deviceStateSelectorHMRPC);
913
- const rssiPeer = await this.getForeignStateAsync(rssiPeerSelectorHMRPC);
914
-
915
- if (linkQuality !== ' - ' && deviceTimeSelector) {
916
- const ts = deviceUnreachState === 1 ? deviceTimeSelector.lc : deviceTimeSelector.ts;
917
- lastContactString = await this.getLastContact(ts);
918
- } else if (deviceState) {
919
- lastContactString = await this.getLastContact(deviceState.ts);
920
- } else if (rssiPeer) {
921
- lastContactString = await this.getLastContact(rssiPeer.ts);
922
- }
923
- } else if (deviceTimeSelector) {
924
- const ts = !deviceUnreachState ? deviceTimeSelector.lc : deviceTimeSelector.ts;
925
- lastContactString = await this.getLastContact(ts);
926
- }
927
-
928
- if (deviceTimeSelector) {
929
- lastContact = tools.getTimestamp(deviceTimeSelector.ts);
930
- }
931
-
932
- const gefundenerAdapter = Object.values(adapterArray).find((adapter) => adapter.adapterID === adapterID);
933
- if (!gefundenerAdapter) {
934
- this.log.warn(`[getOnlineState] - adapter not found in adapterArray for adapterID: ${adapterID}`);
935
- return [lastContactString ?? ' - ', deviceState, linkQualitySet];
936
- }
937
- const device = Object.values(this.config.tableDevices).find((adapter) => adapter.adapterKey === gefundenerAdapter.adapterKey);
938
- if (!device) {
939
- this.log.warn(`[getOnlineState] - device config not found for adapterKey: ${gefundenerAdapter.adapterKey}`);
940
- return [lastContactString ?? ' - ', deviceState, linkQualitySet];
941
- }
942
- const maxSecondDevicesOffline = device.maxSecondDevicesOffline;
943
-
944
- switch (adapterID) {
945
- case 'hmrpc': {
946
- if (maxSecondDevicesOffline <= 0) {
947
- if (deviceUnreachState === 1) {
948
- deviceState = 'Offline'; //set online state to offline
949
- if (linkQuality !== ' - ') {
950
- linkQualitySet = '0%';
951
- } // set linkQuality to nothing
952
- }
953
- } else if (lastDeviceUnreachStateChange > maxSecondDevicesOffline && deviceUnreachState === 1) {
954
- deviceState = 'Offline'; //set online state to offline
955
- if (linkQuality !== ' - ') {
956
- linkQualitySet = '0%';
957
- } // set linkQuality to nothing
958
- }
959
- break;
960
- }
961
- case 'proxmox': {
962
- if (maxSecondDevicesOffline <= 0) {
963
- if (deviceUnreachState !== 'running' && deviceUnreachState !== 'online') {
964
- deviceState = 'Offline'; //set online state to offline
965
- if (linkQuality !== ' - ') {
966
- linkQualitySet = '0%';
967
- } // set linkQuality to nothing
968
- }
969
- } else if (lastDeviceUnreachStateChange > maxSecondDevicesOffline && deviceUnreachState !== 'running' && deviceUnreachState !== 'online') {
970
- deviceState = 'Offline'; //set online state to offline
971
- if (linkQuality !== ' - ') {
972
- linkQualitySet = '0%';
973
- } // set linkQuality to nothing
974
- }
975
- break;
976
- }
977
- case 'hmiP':
978
- case 'maxcube': {
979
- if (maxSecondDevicesOffline <= 0) {
980
- if (deviceUnreachState) {
981
- deviceState = 'Offline'; //set online state to offline
982
- if (linkQuality !== ' - ') {
983
- linkQualitySet = '0%';
984
- } // set linkQuality to nothing
985
- }
986
- } else if (lastDeviceUnreachStateChange > maxSecondDevicesOffline && deviceUnreachState) {
987
- deviceState = 'Offline'; //set online state to offline
988
- if (linkQuality !== ' - ') {
989
- linkQualitySet = '0%';
990
- } // set linkQuality to nothing
991
- }
992
- break;
993
- }
994
- case 'apcups':
995
- case 'hue':
996
- case 'hueExt':
997
- case 'matter':
998
- case 'ping':
999
- case 'deconz':
1000
- case 'shelly':
1001
- case 'sonoff':
1002
- case 'tradfri':
1003
- case 'unifi':
1004
- case 'zigbee':
1005
- case 'zigbee2MQTT': {
1006
- if (maxSecondDevicesOffline <= 0) {
1007
- if (!deviceUnreachState) {
1008
- deviceState = 'Offline'; //set online state to offline
1009
- if (linkQuality !== ' - ') {
1010
- linkQualitySet = '0%';
1011
- } // set linkQuality to nothing
1012
- }
1013
- } else if (!deviceUnreachState && lastDeviceUnreachStateChange > maxSecondDevicesOffline) {
1014
- deviceState = 'Offline'; //set online state to offline
1015
- if (linkQuality !== ' - ') {
1016
- linkQualitySet = '0%';
1017
- } // set linkQuality to nothing
1018
- }
1019
- break;
1020
- }
1021
- case 'mqttClientZigbee2Mqtt': {
1022
- if (maxSecondDevicesOffline <= 0) {
1023
- if (deviceUnreachState !== 'online') {
1024
- deviceState = 'Offline'; //set online state to offline
1025
- if (linkQuality !== ' - ') {
1026
- linkQualitySet = '0%';
1027
- } // set linkQuality to nothing
1028
- }
1029
- } else if (deviceUnreachState !== 'online' && lastDeviceUnreachStateChange > maxSecondDevicesOffline) {
1030
- deviceState = 'Offline'; //set online state to offline
1031
- if (linkQuality !== ' - ') {
1032
- linkQualitySet = '0%';
1033
- }
1034
- }
1035
- break;
1036
- }
1037
- case 'mihome': {
1038
- const offlineByTime = maxSecondDevicesOffline <= 0 || (lastContact && lastContact > maxSecondDevicesOffline);
1039
- const offlineByState = deviceUnreachState !== undefined ? !deviceUnreachState && offlineByTime : offlineByTime;
1040
-
1041
- if (offlineByState) {
1042
- deviceState = 'Offline';
1043
- if (linkQuality !== ' - ') {
1044
- linkQualitySet = '0%';
1045
- }
1046
- }
1047
- break;
1048
- }
1049
- case 'smartgarden': {
1050
- if (maxSecondDevicesOffline <= 0) {
1051
- if (deviceUnreachState === 'OFFLINE') {
1052
- deviceState = 'Offline'; //set online state to offline
1053
- if (linkQuality !== ' - ') {
1054
- linkQualitySet = '0%';
1055
- } // set linkQuality to nothing
1056
- }
1057
- } else if (deviceUnreachState === 'OFFLINE' && lastDeviceUnreachStateChange > maxSecondDevicesOffline) {
1058
- deviceState = 'Offline'; //set online state to offline
1059
- if (linkQuality !== ' - ') {
1060
- linkQualitySet = '0%';
1061
- } // set linkQuality to nothing
1062
- }
1063
- break;
1064
- }
1065
- default: {
1066
- // Gerät gilt als offline, wenn es unerreichbar ist und keine Wartezeit definiert ist, oder wenn der letzte Kontakt zu lange her ist als Wartezeit
1067
- let shouldBeOffline = false;
1068
-
1069
- if (maxSecondDevicesOffline <= 0) {
1070
- if (!deviceUnreachState) {
1071
- shouldBeOffline = true;
1072
- }
1073
- } else if (lastContact && lastContact > maxSecondDevicesOffline) {
1074
- shouldBeOffline = true;
1075
- }
1076
-
1077
- if (shouldBeOffline) {
1078
- deviceState = 'Offline'; // Gerät auf offline setzen
1079
- if (linkQuality !== ' - ') {
1080
- linkQualitySet = '0%';
1081
- }
1082
- }
1083
- }
1084
- }
1085
-
1086
- return [lastContactString, deviceState, linkQualitySet];
1087
- } catch (error) {
1088
- this.log.error(`[getLastContact] - ${error}`);
1089
- }
1090
- }
1091
-
1092
- /**
1093
- * @param {any} adapterID
1094
- * @param {string | number | boolean | null} deviceUpdateSelector
1095
- */
1096
- async checkDeviceUpdate(adapterID, deviceUpdateSelector) {
1097
- let isUpgradable = false;
1098
-
1099
- switch (adapterID) {
1100
- case 'hmiP':
1101
- isUpgradable = deviceUpdateSelector === 'UPDATE_AVAILABLE';
1102
- break;
1103
-
1104
- case 'ring':
1105
- isUpgradable = deviceUpdateSelector !== 'Up to Date';
1106
- break;
1107
-
1108
- default:
1109
- if (typeof deviceUpdateSelector === 'boolean') {
1110
- isUpgradable = deviceUpdateSelector;
1111
- }
1112
- break;
1113
- }
1114
-
1115
- return isUpgradable;
1116
- }
1117
-
1118
- /**
1119
- * fill the lists for user
1120
- *
1121
- * @param {object} device
1122
- */
1123
- async theLists(device) {
1124
- // Raw List with all devices for user
1125
- if (device.Status !== 'disabled') {
1126
- // Deduplication: some adapters (e.g. hmrpc with multiple channels, hue-extended with
1127
- // devices appearing under both lights and sensors) create multiple Map entries for the
1128
- // same physical device. Use Path as unique key to prevent duplicate list entries.
1129
- const lang = this.config.userSelectedLanguage;
1130
- const alreadyInUserRaw = this.listAllDevicesUserRaw.some((d) => d.Device === device.Device && d.Adapter === device.Adapter);
1131
- if (!alreadyInUserRaw) {
1132
- this.listAllDevicesUserRaw.push({
1133
- Device: device.Device,
1134
- Adapter: device.Adapter,
1135
- Instance: device.instance,
1136
- 'Instance connected': device.instanceDeviceConnected,
1137
- isBatteryDevice: device.isBatteryDevice,
1138
- Battery: device.Battery,
1139
- BatteryRaw: device.BatteryRaw,
1140
- BatteryUnitRaw: device.BatteryUnitRaw,
1141
- isLowBat: device.LowBat,
1142
- 'Signal strength': device.SignalStrength,
1143
- 'Signal strength Raw': device.SignalStrengthRaw,
1144
- 'Last contact': device.LastContact,
1145
- 'Update Available': device.Upgradable,
1146
- Status: device.Status,
1147
- });
1148
- }
1149
-
1150
- // List with all devices
1151
- const alreadyInAll = this.listAllDevices.some(
1152
- (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1153
- );
1154
- if (!alreadyInAll) {
1155
- this.listAllDevices.push({
1156
- [translations.Device[lang]]: device.Device,
1157
- [translations.Adapter[lang]]: device.Adapter,
1158
- [translations.Battery[lang]]: device.Battery,
1159
- [translations.Signal_strength[lang]]: device.SignalStrength,
1160
- [translations.Last_Contact[lang]]: device.LastContact,
1161
- [translations.Status[lang]]: device.Status,
1162
- });
1163
- }
1164
-
1165
- // LinkQuality lists
1166
- if (device.SignalStrength !== ' - ') {
1167
- const alreadyInLQ = this.linkQualityDevices.some(
1168
- (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1169
- );
1170
- if (!alreadyInLQ) {
1171
- this.linkQualityDevices.push({
1172
- [translations.Device[lang]]: device.Device,
1173
- [translations.Adapter[lang]]: device.Adapter,
1174
- [translations.Signal_strength[lang]]: device.SignalStrength,
1175
- });
1176
- }
1177
- }
1178
-
1179
- // Battery lists
1180
- if (device.isBatteryDevice) {
1181
- const alreadyInBat = this.batteryPowered.some(
1182
- (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1183
- );
1184
- if (!alreadyInBat) {
1185
- this.batteryPowered.push({
1186
- [translations.Device[lang]]: device.Device,
1187
- [translations.Adapter[lang]]: device.Adapter,
1188
- [translations.Battery[lang]]: device.Battery,
1189
- [translations.Status[lang]]: device.Status,
1190
- });
1191
- }
1192
- }
1193
-
1194
- // Low Bat lists
1195
- if (device.LowBat && device.Status !== 'Offline') {
1196
- const alreadyInLowBat = this.batteryLowPowered.some(
1197
- (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1198
- );
1199
- if (!alreadyInLowBat) {
1200
- this.batteryLowPowered.push({
1201
- [translations.Device[lang]]: device.Device,
1202
- [translations.Adapter[lang]]: device.Adapter,
1203
- [translations.Battery[lang]]: device.Battery,
1204
- });
1205
- }
1206
- }
1207
-
1208
- // Offline List
1209
- if (device.Status === 'Offline') {
1210
- const alreadyOffline = this.offlineDevices.some(
1211
- (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1212
- );
1213
- if (!alreadyOffline) {
1214
- this.offlineDevices.push({
1215
- [translations.Device[lang]]: device.Device,
1216
- [translations.Adapter[lang]]: device.Adapter,
1217
- [translations.Last_Contact[lang]]: device.LastContact,
1218
- });
1219
- }
1220
- }
1221
-
1222
- // Device update List
1223
- if (device.Upgradable === true || device.Upgradable === 1) {
1224
- const alreadyUpgradable = this.upgradableList.some(
1225
- (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1226
- );
1227
- if (!alreadyUpgradable) {
1228
- this.upgradableList.push({
1229
- [translations.Device[lang]]: device.Device,
1230
- [translations.Adapter[lang]]: device.Adapter,
1231
- });
1232
- }
1233
- }
1234
- }
1235
- }
1236
-
1237
- /**
1238
- * @param {string | string[]} id
1239
- * @param {ioBroker.State} state
1240
- */
1241
- async renewDeviceData(id, state) {
1242
- let batteryData;
1243
- let signalData;
1244
- let oldLowBatState;
1245
- let contactData;
1246
- let oldStatus;
1247
- let isLowBatValue;
1248
-
1249
- const deviceID = id.slice(0, id.lastIndexOf('.') + 1 - 1);
1250
- const deviceData = this.listAllDevicesRaw.get(deviceID);
1251
-
1252
- this.log.debug(`[renewDeviceData] - ${id}`);
1253
-
1254
- if (deviceData) {
1255
- const gefundenerAdapter = Object.values(adapterArray).find((adapter) => adapter.adapterID === deviceData.adapterID);
1256
- if (!gefundenerAdapter) {
1257
- this.log.warn(`[renewDeviceData] - adapter not found for adapterID: ${deviceData.adapterID}`);
1258
- return;
1259
- }
1260
- const silentEnabled = Object.values(this.config.tableDevices).find((adapter) => adapter.adapterKey === gefundenerAdapter.adapterKey);
1261
- if (!silentEnabled) {
1262
- this.log.warn(`[renewDeviceData] - device config not found for adapterKey: ${gefundenerAdapter.adapterKey}`);
1263
- return;
1264
- }
1265
-
1266
- // On statechange update available datapoint
1267
- switch (id) {
1268
- // device connection
1269
- case deviceData.instanceDeviceConnectionDP:
1270
- if (state.val !== deviceData.instanceDeviceConnected) {
1271
- deviceData.instanceDeviceConnected = state.val;
1272
- }
1273
- break;
1274
-
1275
- // device updates
1276
- case deviceData.UpdateDP:
1277
- if (state.val !== deviceData.Upgradable) {
1278
- deviceData.Upgradable = await this.checkDeviceUpdate(deviceData.adapterID, state.val);
1279
- if (deviceData.Upgradable === true) {
1280
- if (this.config.checkSendDeviceUpgrade && !this.blacklistNotify.includes(deviceData.Path)) {
1281
- await this.sendStateNotifications('Devices', 'updateDevice', deviceID, silentEnabled.telegramSilent);
1282
- }
1283
- }
1284
- }
1285
- break;
1286
-
1287
- // device signal
1288
- case deviceData.SignalStrengthDP:
1289
- signalData = await this.calculateSignalStrength(state, deviceData.adapterID);
1290
- deviceData.SignalStrength = signalData[0];
1291
-
1292
- break;
1293
-
1294
- // device battery
1295
- case deviceData.batteryDP:
1296
- if (deviceData.isBatteryDevice) {
1297
- oldLowBatState = deviceData.LowBat;
1298
- if (state.val === 0 && deviceData.BatteryRaw >= 5) {
1299
- // Glitch-Filter: ignore single 0-value if battery was above 5 before
1300
- break;
1301
- }
1302
- batteryData = await this.getBatteryData(state.val, oldLowBatState, deviceData.faultReport, deviceData.adapterID);
1303
-
1304
- deviceData.Battery = batteryData[0];
1305
- deviceData.BatteryRaw = batteryData[2];
1306
- deviceData.BatteryUnitRaw = batteryData[3];
1307
- if (deviceData.LowBatDP !== 'none') {
1308
- isLowBatValue = await tools.getInitValue(this, deviceData.LowBatDP);
1309
- } else {
1310
- isLowBatValue = undefined;
1311
- }
1312
- deviceData.LowBat = await this.setLowbatIndicator(state.val, isLowBatValue, deviceData.faultReport, deviceData.adapterID);
1313
-
1314
- if (deviceData.LowBat && oldLowBatState !== deviceData.LowBat) {
1315
- if (this.config.checkSendBatteryMsg && !this.blacklistNotify.includes(deviceData.Path)) {
1316
- await this.sendStateNotifications('Devices', 'lowBatDevice', deviceID, silentEnabled.telegramSilent);
1317
- }
1318
- }
1319
- }
1320
- break;
1321
-
1322
- // device low bat
1323
- case deviceData.LowBatDP:
1324
- if (deviceData.isBatteryDevice) {
1325
- oldLowBatState = deviceData.LowBat;
1326
- batteryData = await this.getBatteryData(deviceData.BatteryRaw, state.val, deviceData.faultReport, deviceData.adapterID);
1327
- deviceData.Battery = batteryData[0];
1328
- deviceData.BatteryRaw = batteryData[2];
1329
- deviceData.BatteryUnitRaw = batteryData[3];
1330
- deviceData.LowBat = await this.setLowbatIndicator(deviceData.BatteryRaw, state.val, deviceData.faultReport, deviceData.adapterID);
1331
-
1332
- if (deviceData.LowBat && oldLowBatState !== deviceData.LowBat) {
1333
- if (this.config.checkSendBatteryMsg && !this.blacklistNotify.includes(deviceData.Path)) {
1334
- await this.sendStateNotifications('Devices', 'lowBatDevice', deviceID, silentEnabled.telegramSilent);
1335
- }
1336
- }
1337
- }
1338
- break;
1339
-
1340
- //device error / fault reports
1341
- case deviceData.faultReportDP:
1342
- if (deviceData.isBatteryDevice) {
1343
- oldLowBatState = deviceData.LowBat;
1344
- batteryData = await this.getBatteryData(deviceData.BatteryRaw, oldLowBatState, state.val, deviceData.adapterID);
1345
-
1346
- deviceData.Battery = batteryData[0];
1347
- deviceData.BatteryRaw = batteryData[2];
1348
- deviceData.BatteryUnitRaw = batteryData[3];
1349
- deviceData.LowBat = await this.setLowbatIndicator(deviceData.BatteryRaw, undefined, state.val, deviceData.adapterID);
1350
-
1351
- if (deviceData.LowBat && oldLowBatState !== deviceData.LowBat) {
1352
- if (this.config.checkSendBatteryMsg && !this.blacklistNotify.includes(deviceData.Path)) {
1353
- await this.sendStateNotifications('Devices', 'lowBatDevice', deviceID, silentEnabled.telegramSilent);
1354
- }
1355
- }
1356
- }
1357
- break;
1358
-
1359
- // device unreach
1360
- case deviceData.UnreachDP:
1361
- if (deviceData.instanceDeviceConnected !== undefined) {
1362
- if (deviceData.UnreachState !== state.val) {
1363
- oldStatus = deviceData.Status;
1364
- deviceData.UnreachState = state.val;
1365
-
1366
- contactData = await this.getOnlineState(
1367
- deviceData.timeSelector,
1368
- deviceData.adapterID,
1369
- deviceData.UnreachDP,
1370
- deviceData.SignalStrength,
1371
- deviceData.UnreachState,
1372
- deviceData.deviceStateSelectorHMRPC,
1373
- deviceData.rssiPeerSelectorHMRPC,
1374
- );
1375
-
1376
- if (contactData !== undefined && contactData !== null) {
1377
- deviceData.LastContact = contactData[0];
1378
- deviceData.Status = contactData[1];
1379
- deviceData.SignalStrength = contactData[2];
1380
- }
1381
-
1382
- if (this.config.checkSendOfflineMsg && oldStatus !== deviceData.Status && !this.blacklistNotify.includes(deviceData.Path)) {
1383
- // check if the generally deviceData connected state is for a while true
1384
- if (await tools.getTimestampConnectionDP(this, deviceData.instanceDeviceConnectionDP, 50000)) {
1385
- await this.sendStateNotifications('Devices', 'onlineStateDevice', deviceID, silentEnabled.telegramSilent);
1386
- }
1387
- }
1388
- }
1389
- }
1390
- }
1391
- }
1392
- }
1393
-
1394
- /**
1395
- * get all Instances at start
1396
- */
1397
- async getAllInstanceData() {
1398
- try {
1399
- const allInstances = `system.adapter.*`;
1400
- await this.getInstanceData(allInstances);
1401
- } catch (error) {
1402
- this.log.error(`[getInstance] - ${error}`);
1403
- }
1404
- }
1405
-
1406
- /**
1407
- * get instance data
1408
- *
1409
- *@param {string} instanceObject
1410
- */
1411
- async getInstanceData(instanceObject) {
1412
- try {
1413
- const idDP = `${instanceObject}.alive`;
1414
- const instanceAliveDP = await this.getForeignStatesAsync(idDP);
1415
-
1416
- this.adapterUpdatesJsonRaw = await this.getAdapterUpdateData(adapterUpdateListDP);
1417
-
1418
- for (const [id] of Object.entries(instanceAliveDP)) {
1419
- if (!(typeof id === 'string' && id.startsWith(`system.adapter.`))) {
1420
- continue;
1421
- }
1422
-
1423
- // get instance name
1424
- const instanceID = await this.getInstanceName(id);
1425
-
1426
- // get instance connected to host data
1427
- const instanceConnectedHostDP = `system.adapter.${instanceID}.connected`;
1428
- const instanceConnectedHostVal = await tools.getInitValue(this, instanceConnectedHostDP);
1429
-
1430
- // get instance connected to device data
1431
- const instanceConnectedDeviceDP = `${instanceID}.info.connection`;
1432
- const devicesState = await this.getForeignStateAsync(instanceConnectedDeviceDP);
1433
-
1434
- let instanceConnectedDeviceVal;
1435
- if (devicesState !== null && typeof devicesState.val === 'boolean') {
1436
- instanceConnectedDeviceVal = await tools.getInitValue(this, instanceConnectedDeviceDP);
1437
- } else {
1438
- instanceConnectedDeviceVal = 'N/A';
1439
- }
1440
-
1441
- // get adapter version
1442
- const instanceObjectPath = `system.adapter.${instanceID}`;
1443
- let adapterName;
1444
- let adapterVersion;
1445
- let adapterAvailableUpdate = '';
1446
- let instanceMode;
1447
- let scheduleTime = 'N/A';
1448
-
1449
- const instanceObjectData = await this.getForeignObjectAsync(instanceObjectPath);
1450
-
1451
- if (instanceObjectData) {
1452
- adapterName = tools.capitalize(instanceObjectData.common.name);
1453
- adapterVersion = instanceObjectData.common.version;
1454
- instanceMode = instanceObjectData.common.mode;
1455
-
1456
- if (instanceMode === 'schedule') {
1457
- scheduleTime = instanceObjectData.common.schedule;
1458
- }
1459
- }
1460
-
1461
- const updateEntry = this.adapterUpdatesJsonRaw.find((entry) => entry.adapter.toLowerCase() === adapterName.toLowerCase());
1462
-
1463
- if (updateEntry) {
1464
- adapterAvailableUpdate = updateEntry.newVersion;
1465
- } else {
1466
- adapterAvailableUpdate = ' - ';
1467
- }
1468
-
1469
- let isAlive;
1470
- let isHealthy;
1471
- let instanceStatus;
1472
- if (instanceMode === 'schedule') {
1473
- const instanceStatusRaw = await this.checkScheduleisHealty(instanceID, scheduleTime);
1474
- isAlive = instanceStatusRaw[0];
1475
- isHealthy = instanceStatusRaw[1];
1476
- instanceStatus = instanceStatusRaw[2];
1477
- } else if (instanceMode === 'daemon') {
1478
- const instanceStatusRaw = await this.checkDaemonIsHealthy(instanceID);
1479
- isAlive = instanceStatusRaw[0];
1480
- isHealthy = instanceStatusRaw[1];
1481
- instanceStatus = instanceStatusRaw[2];
1482
- }
1483
-
1484
- //subscribe to statechanges
1485
- this.subscribeForeignStates(id);
1486
- this.subscribeForeignStates(instanceConnectedHostDP);
1487
- this.subscribeForeignStates(instanceConnectedDeviceDP);
1488
- this.subscribeForeignObjects(`system.adapter.*`);
1489
- // this.subscribeForeignStates('*');
1490
- // this.subscribeForeignObjects('*');
1491
-
1492
- // create raw list
1493
- this.listInstanceRaw.set(instanceID, {
1494
- Adapter: adapterName,
1495
- instanceObjectPath: instanceObjectPath,
1496
- instanceMode: instanceMode,
1497
- schedule: scheduleTime,
1498
- adapterVersion: adapterVersion,
1499
- updateAvailable: adapterAvailableUpdate,
1500
- isAlive: isAlive,
1501
- isHealthy: isHealthy,
1502
- isConnectedHost: instanceConnectedHostVal,
1503
- isConnectedDevice: instanceConnectedDeviceVal,
1504
- status: instanceStatus,
1505
- aliveDP: `system.adapter.${instanceID}.alive`,
1506
- hostConnectionDP: instanceConnectedHostDP,
1507
- deviceConnectionDP: instanceConnectedDeviceDP,
1508
- });
1509
- }
1510
- await this.createInstanceList();
1511
- await this.writeInstanceDPs();
1512
- } catch (error) {
1513
- this.log.error(`[getInstanceData] - ${error}`);
1514
- }
1515
- }
1516
-
1517
- /**
1518
- * get Instances
1519
- *
1520
- * @param {string} id - Path of alive datapoint
1521
- */
1522
- async getInstanceName(id) {
1523
- let instance = id;
1524
- instance = instance.slice(15); // remove "system.adapter."
1525
- instance = instance.slice(0, instance.lastIndexOf('.') + 1 - 1); // remove ".alive"
1526
- return instance;
1527
- }
1528
-
1529
- /**
1530
- * Check if instance is alive and ok
1531
- *
1532
- * @param {string} instanceID
1533
- */
1534
- async checkDaemonIsHealthy(instanceID) {
1535
- const connectedHostState = await tools.getInitValue(this, `system.adapter.${instanceID}.connected`);
1536
- const isAlive = await tools.getInitValue(this, `system.adapter.${instanceID}.alive`);
1537
- let connectedDeviceState = await tools.getInitValue(this, `${instanceID}.info.connection`);
1538
- if (connectedDeviceState === undefined) {
1539
- connectedDeviceState = true;
1540
- }
1541
-
1542
- let isHealthy = false;
1543
- let instanceStatusString = translations.instance_deactivated[this.config.userSelectedLanguage];
1544
-
1545
- if (isAlive) {
1546
- if (connectedHostState && connectedDeviceState) {
1547
- isHealthy = true;
1548
- instanceStatusString = translations.instance_okay[this.config.userSelectedLanguage];
1549
- } else if (!connectedHostState) {
1550
- instanceStatusString = translations.not_connected_host[this.config.userSelectedLanguage];
1551
- } else if (!connectedDeviceState) {
1552
- instanceStatusString = translations.not_connected_device[this.config.userSelectedLanguage];
1553
- }
1554
- }
1555
-
1556
- return [Boolean(isAlive), Boolean(isHealthy), String(instanceStatusString), Boolean(connectedHostState), Boolean(connectedDeviceState)];
1557
- }
1558
-
1559
- /**
1560
- * Check if instance is alive and ok
1561
- *
1562
- * @param {string} instanceID
1563
- * @param {number} instanceDeactivationTime
1564
- */
1565
- async checkDaemonIsAlive(instanceID, instanceDeactivationTime) {
1566
- let isAlive = await tools.getInitValue(this, `system.adapter.${instanceID}.alive`);
1567
- let daemonIsAlive;
1568
- let isHealthy = false;
1569
- let instanceStatusString = isAlive ? translations.instance_activated[this.config.userSelectedLanguage] : translations.instance_deactivated[this.config.userSelectedLanguage];
1570
-
1571
- if (isAlive) {
1572
- daemonIsAlive = await this.checkDaemonIsHealthy(instanceID);
1573
- } else {
1574
- await this.delay(instanceDeactivationTime);
1575
- daemonIsAlive = await this.checkDaemonIsHealthy(instanceID);
1576
- if (!daemonIsAlive[0]) {
1577
- await this.delay(instanceDeactivationTime);
1578
- daemonIsAlive = await this.checkDaemonIsHealthy(instanceID);
1579
- }
1580
- }
1581
-
1582
- isAlive = Boolean(daemonIsAlive[0]);
1583
- isHealthy = Boolean(daemonIsAlive[1]);
1584
- instanceStatusString = String(daemonIsAlive[2]);
1585
- const connectedToHost = Boolean(daemonIsAlive[3]);
1586
- const connectedToDevice = Boolean(daemonIsAlive[4]);
1587
-
1588
- return [isAlive, isHealthy, instanceStatusString, connectedToHost, connectedToDevice];
1589
- }
1590
-
1591
- async checkScheduleisHealty(instanceID, scheduleTime) {
1592
- let lastUpdate;
1593
- let previousCronRun = null;
1594
- let lastCronRun;
1595
- let diff;
1596
- let isAlive = false;
1597
- let isHealthy = false;
1598
- let instanceStatusString = translations.instance_deactivated[this.config.userSelectedLanguage];
1599
- const isAliveSchedule = await this.getForeignStateAsync(`system.adapter.${instanceID}.alive`);
1600
-
1601
- if (isAliveSchedule) {
1602
- lastUpdate = Math.round((Date.now() - isAliveSchedule.lc) / 1000); // Last state change in seconds
1603
- previousCronRun = await this.getPreviousCronRun(scheduleTime); // When was the last cron run
1604
- if (previousCronRun) {
1605
- lastCronRun = Math.round(previousCronRun / 1000); // change distance to last run in seconds
1606
- diff = lastCronRun - lastUpdate;
1607
- if (diff > -300) {
1608
- // if 5 minutes difference exceeded, instance is not alive
1609
- isAlive = true;
1610
- isHealthy = true;
1611
- instanceStatusString = translations.instance_okay[this.config.userSelectedLanguage];
1612
- }
1613
- }
1614
- }
1615
-
1616
- return [isAlive, isHealthy, instanceStatusString];
1617
- }
1618
-
1619
- /**
1620
- * set status for instance
1621
- *
1622
- * @param {string} instanceMode
1623
- * @param {string} scheduleTime
1624
- * @param {any} instanceID
1625
- */
1626
- async setInstanceStatus(instanceMode, scheduleTime, instanceID) {
1627
- let instanceDeactivationTime = (this.config.offlineTimeInstances * 1000) / 2;
1628
- let instanceErrorTime = (this.config.errorTimeInstances * 1000) / 2;
1629
- let isAlive;
1630
- let isHealthy;
1631
- let instanceStatusString;
1632
- let daemonIsAlive;
1633
- let daemonIsNotAlive;
1634
- let scheduleIsAlive;
1635
- let connectedToHost;
1636
- let connectedToDevice;
1637
-
1638
- switch (instanceMode) {
1639
- case 'schedule':
1640
- scheduleIsAlive = await this.checkScheduleisHealty(instanceID, scheduleTime);
1641
- isAlive = Boolean(scheduleIsAlive[0]);
1642
- isHealthy = Boolean(scheduleIsAlive[1]);
1643
- instanceStatusString = String(scheduleIsAlive[2]);
1644
- break;
1645
- case 'daemon':
1646
- // check with time the user did define for error and deactivation
1647
- if (this.userTimeInstancesList.has(instanceID)) {
1648
- const userTimeInstances = this.userTimeInstancesList.get(instanceID);
1649
- instanceDeactivationTime = (userTimeInstances.deactivationTime * 1000) / 2;
1650
- instanceErrorTime = (userTimeInstances.errorTime * 1000) / 2;
1651
- }
1652
- daemonIsAlive = await this.checkDaemonIsHealthy(instanceID);
1653
- if (daemonIsAlive[0] && !daemonIsAlive[1]) {
1654
- await this.delay(instanceErrorTime);
1655
- const daemonIsAliveAfterDelay = await this.checkDaemonIsHealthy(instanceID);
1656
-
1657
- if (daemonIsAliveAfterDelay[0] && !daemonIsAliveAfterDelay[1]) {
1658
- await this.delay(instanceErrorTime);
1659
- const daemonIsAliveAfterSecondDelay = await this.checkDaemonIsHealthy(instanceID);
1660
-
1661
- // nach allen Retries: Status übernehmen (egal ob erholt oder weiterhin fehlerhaft)
1662
- isAlive = Boolean(daemonIsAliveAfterSecondDelay[0]);
1663
- isHealthy = Boolean(daemonIsAliveAfterSecondDelay[1]);
1664
- instanceStatusString = String(daemonIsAliveAfterSecondDelay[2]);
1665
- connectedToHost = Boolean(daemonIsAliveAfterSecondDelay[3]);
1666
- connectedToDevice = Boolean(daemonIsAliveAfterSecondDelay[4]);
1667
- } else {
1668
- // nach erstem Retry wieder gesund
1669
- isAlive = Boolean(daemonIsAliveAfterDelay[0]);
1670
- isHealthy = Boolean(daemonIsAliveAfterDelay[1]);
1671
- instanceStatusString = String(daemonIsAliveAfterDelay[2]);
1672
- connectedToHost = Boolean(daemonIsAliveAfterDelay[3]);
1673
- connectedToDevice = Boolean(daemonIsAliveAfterDelay[4]);
1674
- }
1675
- } else {
1676
- daemonIsNotAlive = await this.checkDaemonIsAlive(instanceID, instanceDeactivationTime);
1677
- isAlive = Boolean(daemonIsNotAlive[0]);
1678
- isHealthy = Boolean(daemonIsNotAlive[1]);
1679
- instanceStatusString = String(daemonIsNotAlive[2]);
1680
- connectedToHost = Boolean(daemonIsNotAlive[3]);
1681
- connectedToDevice = Boolean(daemonIsNotAlive[4]);
1682
- }
1683
-
1684
- break;
1685
- }
1686
-
1687
- return [isAlive, isHealthy, instanceStatusString, connectedToHost, connectedToDevice];
1688
- }
1689
-
1690
- /**
1691
- * create adapter update raw lists
1692
- *
1693
- * @param {string} adapterUpdateListDP
1694
- */
1695
- async getAdapterUpdateData(adapterUpdateListDP) {
1696
- // Clear the existing adapter updates data
1697
- let adapterUpdatesJsonRaw = [];
1698
- let adapterJsonList = {};
1699
-
1700
- // Fetch the adapter updates list
1701
- const adapterUpdatesListVal = await this.getForeignStatesAsync(adapterUpdateListDP);
1702
-
1703
- // Extract adapter data from the list - merge all admin instances
1704
- for (const [_id, value] of Object.entries(adapterUpdatesListVal)) {
1705
- const parsed = tools.parseData(value.val);
1706
- if (parsed && typeof parsed === 'object') {
1707
- Object.assign(adapterJsonList, parsed);
1708
- }
1709
- }
1710
-
1711
- // Populate the adapter updates data
1712
- for (const [id, adapterData] of Object.entries(adapterJsonList)) {
1713
- adapterUpdatesJsonRaw.push({
1714
- adapter: tools.capitalize(id),
1715
- newVersion: adapterData.availableVersion,
1716
- oldVersion: adapterData.installedVersion,
1717
- });
1718
- }
1719
-
1720
- return adapterUpdatesJsonRaw;
1721
- }
1722
-
1723
- /**
1724
- * create instanceList
1725
- */
1726
- async createAdapterUpdateList() {
1727
- this.listAdapterUpdates = [];
1728
- this.countAdapterUpdates = 0;
1729
-
1730
- for (const updateData of this.adapterUpdatesJsonRaw) {
1731
- this.listAdapterUpdates.push({
1732
- [translations.Adapter[this.config.userSelectedLanguage]]: updateData.adapter,
1733
- [translations.Available_Version[this.config.userSelectedLanguage]]: updateData.newVersion,
1734
- [translations.Installed_Version[this.config.userSelectedLanguage]]: updateData.oldVersion,
1735
- });
1736
- }
1737
-
1738
- this.countAdapterUpdates = this.listAdapterUpdates.length;
1739
- await this.writeAdapterUpdatesDPs();
1740
- }
1741
-
1742
- /**
1743
- * write datapoints for adapter with updates
1744
- */
1745
- async writeAdapterUpdatesDPs() {
1746
- // Write Datapoints for counts
1747
- await this.setStateChangedAsync(`adapterAndInstances.countAdapterUpdates`, { val: this.countAdapterUpdates, ack: true });
1748
-
1749
- if (this.countAdapterUpdates === 0) {
1750
- this.listAdapterUpdates = [
1751
- {
1752
- [translations.Adapter[this.config.userSelectedLanguage]]: '--no updates--',
1753
- [translations.Available_Version[this.config.userSelectedLanguage]]: '',
1754
- [translations.Installed_Version[this.config.userSelectedLanguage]]: '',
1755
- },
1756
- ];
1757
- }
1758
- await this.setStateChangedAsync(`adapterAndInstances.listAdapterUpdates`, { val: JSON.stringify(this.listAdapterUpdates), ack: true });
1759
- }
1760
-
1761
- /**
1762
- * create instanceList
1763
- */
1764
- async createInstanceList() {
1765
- this.listAllInstances = [];
1766
- this.listAllActiveInstances = [];
1767
- this.listDeactivatedInstances = [];
1768
- this.listErrorInstanceRaw = [];
1769
- this.listErrorInstance = [];
1770
-
1771
- for (const [instance, instanceData] of this.listInstanceRaw) {
1772
- // fill raw list
1773
- if (instanceData.isAlive && !instanceData.isHealthy) {
1774
- this.listErrorInstanceRaw.push({
1775
- Adapter: instanceData.Adapter,
1776
- Instance: instance,
1777
- Mode: instanceData.instanceMode,
1778
- Status: instanceData.status,
1779
- });
1780
- }
1781
-
1782
- if (this.blacklistInstancesLists.includes(instance)) {
1783
- continue;
1784
- }
1785
- // all instances
1786
- this.listAllInstances.push({
1787
- [translations.Adapter[this.config.userSelectedLanguage]]: instanceData.Adapter,
1788
- [translations.Instance[this.config.userSelectedLanguage]]: instance,
1789
- [translations.Mode[this.config.userSelectedLanguage]]: instanceData.instanceMode,
1790
- [translations.Schedule[this.config.userSelectedLanguage]]: instanceData.schedule,
1791
- [translations.Version[this.config.userSelectedLanguage]]: instanceData.adapterVersion,
1792
- [translations.Updateable[this.config.userSelectedLanguage]]: instanceData.updateAvailable,
1793
- [translations.Status[this.config.userSelectedLanguage]]: instanceData.status,
1794
- });
1795
-
1796
- if (!instanceData.isAlive) {
1797
- // list with deactivated instances
1798
- this.listDeactivatedInstances.push({
1799
- [translations.Adapter[this.config.userSelectedLanguage]]: instanceData.Adapter,
1800
- [translations.Instance[this.config.userSelectedLanguage]]: instance,
1801
- [translations.Status[this.config.userSelectedLanguage]]: instanceData.status,
1802
- });
1803
- } else {
1804
- // list with active instances
1805
- this.listAllActiveInstances.push({
1806
- [translations.Adapter[this.config.userSelectedLanguage]]: instanceData.Adapter,
1807
- [translations.Instance[this.config.userSelectedLanguage]]: instance,
1808
- [translations.Mode[this.config.userSelectedLanguage]]: instanceData.instanceMode,
1809
- [translations.Schedule[this.config.userSelectedLanguage]]: instanceData.schedule,
1810
- [translations.Status[this.config.userSelectedLanguage]]: instanceData.status,
1811
- });
1812
- }
1813
-
1814
- // list with error instances
1815
- if (instanceData.isAlive && !instanceData.isHealthy) {
1816
- this.listErrorInstance.push({
1817
- [translations.Adapter[this.config.userSelectedLanguage]]: instanceData.Adapter,
1818
- [translations.Instance[this.config.userSelectedLanguage]]: instance,
1819
- [translations.Mode[this.config.userSelectedLanguage]]: instanceData.instanceMode,
1820
- [translations.Status[this.config.userSelectedLanguage]]: instanceData.status,
1821
- });
1822
- }
1823
- }
1824
- await this.countInstances();
1825
- }
1826
-
1827
- /**
1828
- * count instanceList
1829
- */
1830
- async countInstances() {
1831
- this.countAllInstances = 0;
1832
- this.countAllActiveInstances = 0;
1833
- this.countDeactivatedInstances = 0;
1834
- this.countErrorInstance = 0;
1835
-
1836
- this.countAllInstances = this.listAllInstances.length;
1837
- this.countAllActiveInstances = this.listAllActiveInstances.length;
1838
- this.countDeactivatedInstances = this.listDeactivatedInstances.length;
1839
- this.countErrorInstance = this.listErrorInstance.length;
1840
- }
1841
-
1842
- /**
1843
- * write datapoints for instances list and counts
1844
- */
1845
- async writeInstanceDPs() {
1846
- // List all instances
1847
- await this.setStateChangedAsync(`adapterAndInstances.listAllInstances`, { val: JSON.stringify(this.listAllInstances), ack: true });
1848
- await this.setStateChangedAsync(`adapterAndInstances.countAllInstances`, { val: this.countAllInstances, ack: true });
1849
-
1850
- // List all active instances
1851
- await this.setStateChangedAsync(`adapterAndInstances.listAllActiveInstances`, { val: JSON.stringify(this.listAllActiveInstances), ack: true });
1852
- await this.setStateChangedAsync(`adapterAndInstances.countAllActiveInstances`, { val: this.countAllActiveInstances, ack: true });
1853
-
1854
- // list deactivated instances
1855
- if (this.countDeactivatedInstances === 0) {
1856
- this.listDeactivatedInstances = [
1857
- {
1858
- [translations.Adapter[this.config.userSelectedLanguage]]: '--none--',
1859
- [translations.Instance[this.config.userSelectedLanguage]]: '',
1860
- [translations.Version[this.config.userSelectedLanguage]]: '',
1861
- [translations.Status[this.config.userSelectedLanguage]]: '',
1862
- },
1863
- ];
1864
- }
1865
- await this.setStateChangedAsync(`adapterAndInstances.listDeactivatedInstances`, { val: JSON.stringify(this.listDeactivatedInstances), ack: true });
1866
- await this.setStateChangedAsync(`adapterAndInstances.countDeactivatedInstances`, { val: this.countDeactivatedInstances, ack: true });
1867
-
1868
- // list error instances
1869
- if (this.countErrorInstance === 0) {
1870
- this.listErrorInstance = [
1871
- {
1872
- [translations.Adapter[this.config.userSelectedLanguage]]: '--none--',
1873
- [translations.Instance[this.config.userSelectedLanguage]]: '',
1874
- [translations.Mode[this.config.userSelectedLanguage]]: '',
1875
- [translations.Status[this.config.userSelectedLanguage]]: '',
1876
- },
1877
- ];
1878
- }
1879
- await this.setStateChangedAsync(`adapterAndInstances.listInstancesError`, { val: JSON.stringify(this.listErrorInstance), ack: true });
1880
- await this.setStateChangedAsync(`adapterAndInstances.countInstancesError`, { val: this.countErrorInstance, ack: true });
1881
- }
1882
-
1883
- /**
1884
- * @param {string} id
1885
- */
1886
- async renewAdapterUpdateData(id) {
1887
- const previousAdapterUpdatesCount = this.countAdapterUpdates;
1888
-
1889
- // Fetch and process adapter update data
1890
- await this.getAdapterUpdateData(id);
1891
- await this.createAdapterUpdateList();
1892
-
1893
- // Check and send update notification if required
1894
- if (this.config.checkSendAdapterUpdateMsg && this.countAdapterUpdates > previousAdapterUpdatesCount) {
1895
- await this.sendStateNotifications('AdapterUpdates', 'updateAdapter', null);
1896
- }
1897
-
1898
- // Update instances with available adapter updates
1899
- for (const instance of this.listInstanceRaw.values()) {
1900
- const adapterUpdate = this.adapterUpdatesJsonRaw.find((entry) => entry.adapter.toLowerCase() === instance.Adapter.toLowerCase());
1901
-
1902
- if (adapterUpdate) {
1903
- instance.updateAvailable = adapterUpdate.newVersion;
1904
- } else {
1905
- instance.updateAvailable = ' - ';
1906
- }
1907
- }
1908
- }
1909
- /**
1910
- * call function on state change, renew data and send messages
1911
- *
1912
- * @param {string} id
1913
- * @param {ioBroker.State} state
1914
- */
1915
- async renewInstanceData(id, state) {
1916
- const instanceID = await this.getInstanceName(id);
1917
- const instanceData = this.listInstanceRaw.get(instanceID);
1918
- if (instanceData) {
1919
- let instanceStatusRaw;
1920
-
1921
- const checkInstance = async (instanceID, instanceData) => {
1922
- instanceStatusRaw = await this.setInstanceStatus(instanceData.instanceMode, instanceData.schedule, instanceID);
1923
- instanceData.isAlive = instanceStatusRaw[0];
1924
- instanceData.isHealthy = instanceStatusRaw[1];
1925
- instanceData.status = instanceStatusRaw[2];
1926
- instanceData.isConnectedHost = instanceStatusRaw[3];
1927
- instanceData.isConnectedDevice = instanceStatusRaw[4];
1928
- return;
1929
- };
1930
-
1931
- switch (id) {
1932
- case `system.adapter.${instanceID}.alive`:
1933
- if (state.val !== instanceData.isAlive) {
1934
- await checkInstance(instanceID, instanceData);
1935
- // send message when instance was deactivated
1936
- if (this.config.checkSendInstanceDeactivatedMsg && !instanceData.isAlive) {
1937
- if (this.blacklistInstancesNotify.includes(instanceID)) {
1938
- break;
1939
- }
1940
- // Restart-Erkennung: Toleranzzeit abwarten und prüfen ob Instanz schon wieder läuft
1941
- const restartTolerance = this.userTimeInstancesList.has(instanceID)
1942
- ? this.userTimeInstancesList.get(instanceID).deactivationTime * 1000
1943
- : this.config.offlineTimeInstances * 1000;
1944
-
1945
- this.log.debug(`[renewInstanceData] Instance ${instanceID} went offline - waiting ${restartTolerance}ms to check for restart...`);
1946
- await this.delay(restartTolerance);
1947
-
1948
- const aliveAfterWait = await tools.getInitValue(this, `system.adapter.${instanceID}.alive`);
1949
- if (aliveAfterWait) {
1950
- // Instanz ist bereits wieder online → war nur ein Neustart
1951
- this.log.debug(`[renewInstanceData] Instance ${instanceID} is back online after restart. No deactivation notification sent.`);
1952
- await checkInstance(instanceID, instanceData);
1953
- break;
1954
- }
1955
-
1956
- await this.sendStateNotifications('Instances', 'deactivatedInstance', instanceID);
1957
- }
1958
- }
1959
- break;
1960
-
1961
- case `system.adapter.${instanceID}.connected`:
1962
- if (state.val !== instanceData.isConnectedHost && instanceData.isAlive) {
1963
- await checkInstance(instanceID, instanceData);
1964
- // send message when instance has an error
1965
- if (this.config.checkSendInstanceFailedMsg && !instanceData.isHealthy && instanceData.isAlive) {
1966
- if (this.blacklistInstancesNotify.includes(instanceID)) {
1967
- return;
1968
- }
1969
- await this.sendStateNotifications('Instances', 'errorInstance', instanceID);
1970
- }
1971
- }
1972
- break;
1973
-
1974
- case `${instanceID}.info.connection`:
1975
- if (state.val !== instanceData.isConnectedDevice && instanceData.isAlive) {
1976
- await checkInstance(instanceID, instanceData);
1977
- // send message when instance has an error
1978
- if (this.config.checkSendInstanceFailedMsg && !instanceData.isHealthy && instanceData.isAlive) {
1979
- if (this.blacklistInstancesNotify.includes(instanceID)) {
1980
- return;
1981
- }
1982
- await this.sendStateNotifications('Instances', 'errorInstance', instanceID);
1983
- }
1984
- }
1985
- break;
1986
- }
1987
- }
1988
- }
1989
-
1990
- /*=============================================
1991
- = functions to send notifications =
1992
- =============================================*/
1993
-
1994
- /**
1995
- * Notification service
1996
- *
1997
- * @param {string} text - Text which should be send
1998
- * @param silent
1999
- */
2000
- async sendNotification(text, silent = false) {
2001
- // Pushover
2002
- if (this.config.instancePushover) {
2003
- try {
2004
- //first check if instance is living
2005
- const pushoverAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instancePushover}.alive`);
2006
-
2007
- if (!pushoverAliveState) {
2008
- this.log.warn('Pushover instance is not running. Message could not be sent. Please check your instance configuration.');
2009
- } else {
2010
- await this.sendToAsync(this.config.instancePushover, 'send', {
2011
- message: text,
2012
- title: this.config.titlePushover,
2013
- device: this.config.devicePushover,
2014
- user: this.config.userPushover,
2015
- priority: this.config.prioPushover,
2016
- });
2017
- }
2018
- } catch (error) {
2019
- this.log.error(`[sendNotification Pushover] - ${error}`);
2020
- }
2021
- }
2022
-
2023
- // Telegram
2024
- if (this.config.instanceTelegram) {
2025
- try {
2026
- //first check if instance is living
2027
- const telegramAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceTelegram}.alive`);
2028
-
2029
- if (!telegramAliveState) {
2030
- this.log.warn('Telegram instance is not running. Message could not be sent. Please check your instance configuration.');
2031
- } else {
2032
- await this.sendToAsync(this.config.instanceTelegram, 'send', {
2033
- text: text,
2034
- user: this.config.deviceTelegram,
2035
- chatId: this.config.chatIdTelegram,
2036
- disable_notification: silent,
2037
- });
2038
- }
2039
- } catch (error) {
2040
- this.log.error(`[sendNotification Telegram] - ${error}`);
2041
- }
2042
- }
2043
-
2044
- // Whatsapp
2045
- if (this.config.instanceWhatsapp) {
2046
- try {
2047
- //first check if instance is living
2048
- const whatsappAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceWhatsapp}.alive`);
2049
-
2050
- if (!whatsappAliveState) {
2051
- this.log.warn('Whatsapp instance is not running. Message could not be sent. Please check your instance configuration.');
2052
- } else {
2053
- await this.sendToAsync(this.config.instanceWhatsapp, 'send', {
2054
- text: text,
2055
- phone: this.config.phoneWhatsapp,
2056
- });
2057
- }
2058
- } catch (error) {
2059
- this.log.error(`[sendNotification Whatsapp] - ${error}`);
2060
- }
2061
- }
2062
-
2063
- // Matrix
2064
- if (this.config.instanceMatrix) {
2065
- try {
2066
- //first check if instance is living
2067
- const matrixAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceMatrix}.alive`);
2068
-
2069
- if (!matrixAliveState) {
2070
- this.log.warn('Matrix instance is not running. Message could not be sent. Please check your instance configuration.');
2071
- } else {
2072
- await this.sendToAsync(this.config.instanceMatrix, 'send', {
2073
- html: `<h1>${this.config.titleMatrix}</h1>`,
2074
- text: text,
2075
- });
2076
- }
2077
- } catch (error) {
2078
- this.log.error(`[sendNotification Matrix] - ${error}`);
2079
- }
2080
- }
2081
-
2082
- // Signal
2083
- if (this.config.instanceSignal) {
2084
- try {
2085
- //first check if instance is living
2086
- const signalAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceSignal}.alive`);
2087
-
2088
- if (!signalAliveState) {
2089
- this.log.warn('Signal instance is not running. Message could not be sent. Please check your instance configuration.');
2090
- } else {
2091
- await this.sendToAsync(this.config.instanceSignal, 'send', {
2092
- text: text,
2093
- phone: this.config.phoneSignal,
2094
- });
2095
- }
2096
- } catch (error) {
2097
- this.log.error(`[sendNotification Signal] - ${error}`);
2098
- }
2099
- }
2100
-
2101
- // Email
2102
- if (this.config.instanceEmail) {
2103
- try {
2104
- //first check if instance is living
2105
- const eMailAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceEmail}.alive`);
2106
-
2107
- if (!eMailAliveState) {
2108
- this.log.warn('eMail instance is not running. Message could not be sent. Please check your instance configuration.');
2109
- } else {
2110
- await this.sendToAsync(this.config.instanceEmail, 'send', {
2111
- sendTo: this.config.sendToEmail,
2112
- text: text,
2113
- subject: this.config.subjectEmail,
2114
- });
2115
- }
2116
- } catch (error) {
2117
- this.log.error(`[sendNotification eMail] - ${error}`);
2118
- }
2119
- }
2120
-
2121
- // Jarvis Notification
2122
- if (this.config.instanceJarvis) {
2123
- try {
2124
- //first check if instance is living
2125
- const jarvisAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceJarvis}.alive`);
2126
-
2127
- if (!jarvisAliveState) {
2128
- this.log.warn('Jarvis instance is not running. Message could not be sent. Please check your instance configuration.');
2129
- } else {
2130
- const jsonText = JSON.stringify(text);
2131
- await this.setForeignStateAsync(
2132
- `${this.config.instanceJarvis}.addNotification`,
2133
- `{"title":"${this.config.titleJarvis} (${this.formatDate(new Date(), 'DD.MM.YYYY - hh:mm:ss')})","message": ${jsonText},"display": "drawer"}`,
2134
- );
2135
- }
2136
- } catch (error) {
2137
- this.log.error(`[sendNotification Jarvis] - ${error}`);
2138
- }
2139
- }
2140
-
2141
- // Lovelace Notification
2142
- if (this.config.instanceLovelace) {
2143
- try {
2144
- //first check if instance is living
2145
- const lovelaceAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceLovelace}.alive`);
2146
-
2147
- if (!lovelaceAliveState) {
2148
- this.log.warn('Lovelace instance is not running. Message could not be sent. Please check your instance configuration.');
2149
- } else {
2150
- const jsonText = JSON.stringify(text);
2151
- await this.setForeignStateAsync(
2152
- `${this.config.instanceLovelace}.notifications.add`,
2153
- `{"message":${jsonText}, "title":"${this.config.titleLovelace} (${this.formatDate(new Date(), 'DD.MM.YYYY - hh:mm:ss')})"}`,
2154
- );
2155
- }
2156
- } catch (error) {
2157
- this.log.error(`[sendNotification Lovelace] - ${error}`);
2158
- }
2159
- }
2160
-
2161
- // Synochat Notification
2162
- if (this.config.instanceSynochat) {
2163
- try {
2164
- //first check if instance is living
2165
- const synochatAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceSynochat}.alive`);
2166
-
2167
- if (!synochatAliveState) {
2168
- this.log.warn('Synochat instance is not running. Message could not be sent. Please check your instance configuration.');
2169
- } else {
2170
- if (this.config.channelSynochat !== undefined) {
2171
- await this.setForeignStateAsync(`${this.config.instanceSynochat}.${this.config.channelSynochat}.message`, text);
2172
- } else {
2173
- this.log.warn('Synochat channel is not set. Message could not be sent. Please check your instance configuration.');
2174
- }
2175
- }
2176
- } catch (error) {
2177
- this.log.error(`[sendNotification Synochat] - ${error}`);
2178
- }
2179
- }
2180
- } // <-- End of sendNotification function
2181
-
2182
- /*---------- Notifications ----------*/
2183
- /**
2184
- * Notifications on state changes
2185
- *
2186
- * @param {string} mainType
2187
- * @param {string} type
2188
- * @param {object} id
2189
- * @param silent
2190
- */
2191
- async sendStateNotifications(mainType, type, id, silent = false) {
2192
- if (isUnloaded) {
2193
- return;
2194
- }
2195
- let objectData;
2196
- let adapterName;
2197
- let list = '';
2198
- let message = '';
2199
-
2200
- if (id !== null) {
2201
- if (mainType === 'Devices') {
2202
- objectData = this.listAllDevicesRaw.get(id);
2203
- adapterName = this.config.showAdapterNameinMsg ? `${objectData.Adapter}: ` : '';
2204
- } else if (mainType === 'Instances') {
2205
- objectData = this.listInstanceRaw.get(id);
2206
- }
2207
- }
2208
-
2209
- const setMessage = async (message) => {
2210
- this.log.info(message);
2211
- await this.setStateAsync('lastNotification', message, true);
2212
- await this.sendNotification(message, silent);
2213
- };
2214
-
2215
- switch (type) {
2216
- case 'lowBatDevice':
2217
- message = `${translations.Device_low_bat_detected[this.config.userSelectedLanguage]}: \n${adapterName} ${objectData.Device} (${objectData.Battery})`;
2218
- await setMessage(message);
2219
- break;
2220
-
2221
- case 'onlineStateDevice':
2222
- switch (objectData.Status) {
2223
- case 'Online':
2224
- message = `${translations.Device_available_again[this.config.userSelectedLanguage]}: \n${adapterName} ${objectData.Device} (${objectData.LastContact})`;
2225
- break;
2226
-
2227
- case 'Offline':
2228
- message = `${translations.Device_not_reachable[this.config.userSelectedLanguage]}: \n${adapterName} ${objectData.Device} (${objectData.LastContact})`;
2229
- break;
2230
- }
2231
- await setMessage(message);
2232
- break;
2233
-
2234
- case 'updateDevice':
2235
- message = `${translations.Device_new_updates[this.config.userSelectedLanguage]}: \n${adapterName} ${objectData.Device}`;
2236
- await setMessage(message);
2237
- break;
2238
-
2239
- case 'updateAdapter':
2240
- if (this.countAdapterUpdates === 0) {
2241
- return;
2242
- }
2243
-
2244
- objectData = this.listAdapterUpdates;
2245
- list = '';
2246
-
2247
- for (const id of objectData) {
2248
- list = `${list}\n${id[translations.Adapter[this.config.userSelectedLanguage]]}: v${id[translations.Available_Version[this.config.userSelectedLanguage]]}`;
2249
- }
2250
-
2251
- message = `${translations.Adapter_new_updates[this.config.userSelectedLanguage]}: ${list}`;
2252
- await setMessage(message);
2253
- break;
2254
-
2255
- case 'errorInstance':
2256
- case 'deactivatedInstance':
2257
- message = `${translations.Instance_Watchdog[this.config.userSelectedLanguage]}:\n${id}: ${objectData.status}`;
2258
- await setMessage(message);
2259
- break;
2260
- }
2261
- }
2262
-
2263
- /**
2264
- * Notifications per user defined schedule
2265
- *
2266
- * @param {string} type
2267
- * @param silent
2268
- */
2269
- async sendScheduleNotifications(type, silent = false) {
2270
- if (isUnloaded) {
2271
- return;
2272
- }
2273
-
2274
- const checkDays = [];
2275
- const dayConfigKeys = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
2276
- let list = '';
2277
- let message = '';
2278
-
2279
- const setMessage = async (message) => {
2280
- this.log.info(message);
2281
- await this.setStateAsync('lastNotification', message, true);
2282
- if (!message.includes('no updates')) {
2283
- await this.sendNotification(message, silent);
2284
- }
2285
- };
2286
-
2287
- const processDeviceList = (deviceList, property1, property2) => {
2288
- list = '';
2289
- for (const id of deviceList) {
2290
- if (this.blacklistNotify.includes(id.Path)) {
2291
- continue;
2292
- }
2293
- list += `\n${!this.config.showAdapterNameinMsg ? '' : `${id.Adapter}: `}${id[property1]}${property2 ? ` (${id[property2]})` : ''}`;
2294
- }
2295
- };
2296
-
2297
- const processInstanceList = (instanceList, property) => {
2298
- list = '';
2299
- for (const id of instanceList) {
2300
- if (this.blacklistInstancesNotify.includes(id[translations['Instance'][this.config.userSelectedLanguage]])) {
2301
- continue;
2302
- }
2303
- list += `\n${id[translations['Instance'][this.config.userSelectedLanguage]]}${property ? `: ${id[property]}` : ''}`;
2304
- }
2305
- };
2306
-
2307
- const processNotification = async (list, messageType) => {
2308
- if (list.length === 0) {
2309
- return;
2310
- }
2311
-
2312
- switch (checkDays.length) {
2313
- case 1:
2314
- message = `${translations.Weekly_overview[this.config.userSelectedLanguage]} ${translations[messageType][this.config.userSelectedLanguage]}: ${list}`;
2315
- break;
2316
- case 7:
2317
- message = `${translations.Daily_overview[this.config.userSelectedLanguage]} ${translations[messageType][this.config.userSelectedLanguage]}: ${list}`;
2318
- break;
2319
- default:
2320
- message = `${translations.Overview_of[this.config.userSelectedLanguage]} ${translations[messageType][this.config.userSelectedLanguage]}: ${list}`;
2321
- break;
2322
- }
2323
-
2324
- await setMessage(message);
2325
- };
2326
-
2327
- switch (type) {
2328
- case 'lowBatteryDevices':
2329
- checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`check${day}`] ? index : null)).filter((day) => day !== null));
2330
-
2331
- if (checkDays.length === 0) {
2332
- this.log.warn(`No days selected for daily low battery devices message. Please check the instance configuration!`);
2333
- return;
2334
- }
2335
- this.log.debug(`Number of selected days for daily low battery devices message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2336
-
2337
- schedule.scheduleJob(`1 ${this.config.checkSendBatteryTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2338
- processDeviceList(this.batteryLowPoweredRaw, 'Device', 'Battery');
2339
-
2340
- await processNotification(list, 'devices_low_bat');
2341
- });
2342
- break;
2343
-
2344
- case 'offlineDevices':
2345
- checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkOffline${day}`] ? index : null)).filter((day) => day !== null));
2346
-
2347
- if (checkDays.length === 0) {
2348
- this.log.warn(`No days selected for daily offline devices message. Please check the instance configuration!`);
2349
- return;
2350
- }
2351
- this.log.debug(`Number of selected days for daily offline devices message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2352
-
2353
- schedule.scheduleJob(`2 ${this.config.checkSendOfflineTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2354
- processDeviceList(this.offlineDevicesRaw, `Device`, 'LastContact');
2355
-
2356
- await processNotification(list, 'offline_devices');
2357
- });
2358
- break;
2359
-
2360
- case 'updateDevices':
2361
- checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkUpgrade${day}`] ? index : null)).filter((day) => day !== null));
2362
-
2363
- if (checkDays.length === 0) {
2364
- this.log.warn(`No days selected for daily updatable devices message. Please check the instance configuration!`);
2365
- return;
2366
- }
2367
- this.log.debug(`Number of selected days for daily updatable devices message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2368
-
2369
- schedule.scheduleJob(`3 ${this.config.checkSendUpgradeTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2370
- processDeviceList(this.upgradableDevicesRaw, 'Device');
2371
-
2372
- await processNotification(list, 'available_updatable_devices');
2373
- });
2374
- break;
2375
-
2376
- case 'updateAdapter':
2377
- checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkAdapterUpdate${day}`] ? index : null)).filter((day) => day !== null));
2378
-
2379
- if (checkDays.length === 0) {
2380
- this.log.warn(`No days selected for daily adapter update message. Please check the instance configuration!`);
2381
- return;
2382
- }
2383
- this.log.debug(`Number of selected days for daily adapter update message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2384
-
2385
- schedule.scheduleJob(`4 ${this.config.checkSendAdapterUpdateTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2386
- list = '';
2387
- for (const id of this.listAdapterUpdates) {
2388
- list = `${list}\n${id[translations.Adapter[this.config.userSelectedLanguage]]}: v${id[translations.Available_Version[this.config.userSelectedLanguage]]}`;
2389
- }
2390
- await processNotification(list, 'available_adapter_updates');
2391
- });
2392
- break;
2393
-
2394
- case 'errorInstance':
2395
- checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkFailedInstances${day}`] ? index : null)).filter((day) => day !== null));
2396
-
2397
- if (checkDays.length === 0) {
2398
- this.log.warn(`No days selected for daily instance error message. Please check the instance configuration!`);
2399
- return;
2400
- }
2401
- this.log.debug(`Number of selected days for daily instance error message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2402
-
2403
- schedule.scheduleJob(`5 ${this.config.checkSendInstanceFailedTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2404
- processInstanceList(this.listErrorInstanceRaw, 'Status');
2405
-
2406
- await processNotification(list, 'error_instances_msg');
2407
- });
2408
- break;
2409
-
2410
- case 'deactivatedInstance':
2411
- checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkInstanceDeactivated${day}`] ? index : null)).filter((day) => day !== null));
2412
-
2413
- if (checkDays.length === 0) {
2414
- this.log.warn(`No days selected for daily instance deactivated message. Please check the instance configuration!`);
2415
- return;
2416
- }
2417
- this.log.debug(`Number of selected days for daily instance deactivated message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2418
-
2419
- schedule.scheduleJob(`5 ${this.config.checkSendInstanceDeactivatedTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2420
- processInstanceList(this.listDeactivatedInstances);
2421
-
2422
- await processNotification(list, 'deactivated_instances_msg');
2423
- });
2424
- break;
2425
- }
2426
- }
2427
- async getPreviousCronRun(lastCronRun) {
2428
- try {
2429
- let interval;
2430
- // cron-parser v4: parseExpression() – v5: CronExpressionParser.parse()
2431
- if (typeof cronParserLib.parseExpression === 'function') {
2432
- interval = cronParserLib.parseExpression(lastCronRun);
2433
- } else if (cronParserLib.CronExpressionParser && typeof cronParserLib.CronExpressionParser.parse === 'function') {
2434
- interval = cronParserLib.CronExpressionParser.parse(lastCronRun);
2435
- } else {
2436
- throw new Error('cron-parser: no compatible API found (parseExpression / CronExpressionParser.parse)');
2437
- }
2438
- const previous = interval.prev();
2439
-
2440
- // Differenz in ms seit dem vorherigen Cron-Zeitpunkt
2441
- return Date.now() - previous.getTime();
2442
- } catch (error) {
2443
- this.log.error(`[getPreviousCronRun] - ${error}`);
2444
- return null;
2445
- }
2446
- }
2447
-
2448
-
2449
- /**
2450
- * @param {() => void} callback
2451
- */
2452
- onUnload(callback) {
2453
- try {
2454
- this.log.debug('clearing timeouts');
2455
-
2456
- isUnloaded = true;
2457
-
2458
- if (this.refreshDataTimeout) {
2459
- this.clearTimeout(this.refreshDataTimeout);
2460
- this.refreshDataTimeout = null;
2461
- }
2462
-
2463
- this.log.info('cleaned everything up...');
2464
-
2465
- callback();
2466
- } catch (e) {
2467
- callback();
2468
- }
2469
- }
17
+ constructor(options) {
18
+ super({
19
+ ...options,
20
+ name: adapterName,
21
+ useFormatDate: true,
22
+ });
23
+
24
+ // instances and adapters
25
+ // raw arrays
26
+ this.listInstanceRaw = new Map();
27
+ this.adapterUpdatesJsonRaw = [];
28
+ this.listErrorInstanceRaw = [];
29
+
30
+ // user arrays
31
+ this.listAllInstances = [];
32
+ this.listAllActiveInstances = [];
33
+ this.listDeactivatedInstances = [];
34
+ this.listAdapterUpdates = [];
35
+ this.listErrorInstance = [];
36
+
37
+ //counts
38
+ this.countAllInstances = 0;
39
+ this.countAllActiveInstances = 0;
40
+ this.countDeactivatedInstances = 0;
41
+ this.countAdapterUpdates = 0;
42
+ this.countErrorInstance = 0;
43
+
44
+ // devices
45
+ // raw arrays
46
+ this.listAllDevicesRaw = new Map();
47
+ this.batteryLowPoweredRaw = [];
48
+ this.offlineDevicesRaw = [];
49
+ this.upgradableDevicesRaw = [];
50
+
51
+ // arrays
52
+ this.listAllDevicesUserRaw = [];
53
+ this.listAllDevices = [];
54
+ this.offlineDevices = [];
55
+ this.linkQualityDevices = [];
56
+ this.batteryPowered = [];
57
+ this.batteryLowPowered = [];
58
+ this.selAdapter = [];
59
+ this.adapterSelected = [];
60
+ this.upgradableList = [];
61
+
62
+ // counts
63
+ this.offlineDevicesCount = 0;
64
+ this.deviceCounter = 0;
65
+ this.linkQualityCount = 0;
66
+ this.batteryPoweredCount = 0;
67
+ this.lowBatteryPoweredCount = 0;
68
+ this.upgradableDevicesCount = 0;
69
+
70
+ // Blacklist
71
+ // Instances
72
+ this.blacklistInstancesLists = [];
73
+ this.blacklistInstancesNotify = [];
74
+
75
+ // Devices
76
+ this.blacklistLists = [];
77
+ this.blacklistAdapterLists = [];
78
+ this.blacklistNotify = [];
79
+
80
+ // Timelist instances
81
+ this.userTimeInstancesList = new Map();
82
+
83
+ // Interval timer
84
+ this.refreshDataTimeout = null;
85
+
86
+ // Shared processing lock – verhindert Race Condition zwischen main() und refreshData()
87
+ this.processingLock = false;
88
+
89
+ // Check if main function is running (wird noch intern genutzt für pendingRescan-Guard)
90
+ this.mainRunning = false;
91
+
92
+ // Pending rescan flag (set if a new device was detected while main() was running)
93
+ this.pendingRescan = false;
94
+
95
+ // Pending refresh flag (set if refreshData() wurde während main() geblockt)
96
+ this.pendingRefresh = false;
97
+
98
+ this.on('ready', this.onReady.bind(this));
99
+ this.on('stateChange', this.onStateChange.bind(this));
100
+ this.on('objectChange', this.onObjectChange.bind(this));
101
+ this.on('message', this.onMessage.bind(this));
102
+ this.on('unload', this.onUnload.bind(this));
103
+ }
104
+
105
+ /**
106
+ * onReady
107
+ */
108
+ async onReady() {
109
+ this.log.debug(`Adapter ${adapterName} was started`);
110
+
111
+ // set user language
112
+ if (this.config.userSelectedLanguage === '') {
113
+ if (this.language !== undefined && this.language !== null) {
114
+ this.config.userSelectedLanguage = this.language;
115
+ } else {
116
+ this.config.userSelectedLanguage = 'de';
117
+ }
118
+ }
119
+ this.log.debug(`Set language to ${this.config.userSelectedLanguage}`);
120
+
121
+ this.configCreateInstanceList = this.config.checkAdapterInstances;
122
+ this.configListOnlyBattery = this.config.listOnlyBattery;
123
+ this.configCreateOwnFolder = this.config.createOwnFolder;
124
+ this.configCreateHtmlList = this.config.createHtmlList;
125
+
126
+ try {
127
+ // create list with enabled adapters for monitor devices
128
+ for (const device of Object.values(this.config.tableDevices)) {
129
+ if (device.enabled) {
130
+ for (const [_adapterName, adapter] of Object.entries(adapterArray)) {
131
+ if (String(adapter.adapterKey).toLowerCase() === String(device.adapterKey).toLowerCase()) {
132
+ this.selAdapter.push(adapter);
133
+ this.adapterSelected.push(adapter.adapterKey);
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ // Check if an adapter to monitor devices is selected.
141
+ if (this.adapterSelected.length >= 1) {
142
+ this.log.debug(JSON.stringify(this.selAdapter));
143
+ this.log.info(`Number of selected adapters to monitor devices: ${this.adapterSelected.length}. Loading data from: ${this.adapterSelected.join(', ')} ...`);
144
+ } else {
145
+ this.log.info(`No adapters selected to monitor devices.`);
146
+ }
147
+
148
+ // create Blacklist
149
+ await crud.createBlacklist(this);
150
+
151
+ // create user defined list with time of error for instances
152
+ await crud.createTimeListInstances(this);
153
+
154
+ //create datapoints for each adapter if selected
155
+ for (const [id] of Object.entries(adapterArray)) {
156
+ try {
157
+ if (!this.configCreateOwnFolder) {
158
+ await crud.deleteDPsForEachAdapter(this, id);
159
+ await crud.deleteHtmlListDatapoints(this, id);
160
+ } else {
161
+ const adapter = adapterArray[id];
162
+
163
+ if (this.adapterSelected.includes(adapter.adapterKey)) {
164
+ await crud.createDPsForEachAdapter(this, id);
165
+ // create HTML list datapoints
166
+ if (!this.configCreateHtmlList) {
167
+ await crud.deleteHtmlListDatapoints(this, id);
168
+ } else {
169
+ await crud.createHtmlListDatapoints(this, id);
170
+ }
171
+ this.log.debug(`Created datapoints for ${tools.capitalize(id)}`);
172
+ }
173
+ }
174
+ } catch (error) {
175
+ this.log.error(`[onReady - create and fill datapoints for each adapter] - ${error}`);
176
+ }
177
+ }
178
+
179
+ // create HTML list datapoints
180
+ if (!this.configCreateHtmlList) {
181
+ await crud.deleteHtmlListDatapoints(this);
182
+ await crud.deleteHtmlListDatapointsInstances(this);
183
+ } else {
184
+ await crud.createHtmlListDatapoints(this);
185
+ if (this.config.checkAdapterInstances) {
186
+ await crud.createHtmlListDatapointsInstances(this);
187
+ }
188
+ }
189
+ if (!this.config.checkAdapterInstances) {
190
+ await crud.deleteHtmlListDatapointsInstances(this);
191
+ }
192
+
193
+ // instances and adapters
194
+ if (this.configCreateInstanceList) {
195
+ // instances
196
+ await crud.createDPsForInstances(this);
197
+ await this.getAllInstanceData();
198
+ // adapter updates
199
+ await crud.createAdapterUpdateData(this, adapterUpdateListDP);
200
+ } else {
201
+ await crud.deleteDPsForInstances(this);
202
+ }
203
+
204
+ await this.main();
205
+
206
+ // update last contact data in interval
207
+ await this.refreshData();
208
+
209
+ // send overview for low battery devices
210
+ if (this.config.checkSendBatteryMsgDaily) {
211
+ await this.sendScheduleNotifications('lowBatteryDevices');
212
+ }
213
+
214
+ // send overview of offline devices
215
+ if (this.config.checkSendOfflineMsgDaily) {
216
+ await this.sendScheduleNotifications('offlineDevices');
217
+ }
218
+
219
+ // send overview of upgradeable devices
220
+ if (this.config.checkSendUpgradeMsgDaily) {
221
+ await this.sendScheduleNotifications('updateDevices');
222
+ }
223
+
224
+ // send overview of updatable adapters
225
+ if (this.config.checkSendAdapterUpdateMsgDaily) {
226
+ await this.sendScheduleNotifications('updateAdapter');
227
+ }
228
+
229
+ // send overview of deactivated instances
230
+ if (this.config.checkSendInstanceDeactivatedDaily) {
231
+ await this.sendScheduleNotifications('deactivatedInstance');
232
+ }
233
+
234
+ // send overview of instances with error
235
+ if (this.config.checkSendInstanceFailedDaily) {
236
+ await this.sendScheduleNotifications('errorInstance');
237
+ }
238
+ } catch (error) {
239
+ this.log.error(`[onReady] - ${error}`);
240
+ this.terminate ? this.terminate(15) : process.exit(15);
241
+ }
242
+ } // <-- onReady end
243
+
244
+ /**
245
+ * main function
246
+ */
247
+ async main() {
248
+ // Guard: wenn bereits ein Prozess läuft (main oder refreshData), rescan vormerken und zurückkehren
249
+ if (this.processingLock) {
250
+ this.pendingRescan = true;
251
+ return;
252
+ }
253
+
254
+ this.log.debug(`Function started main`);
255
+ this.processingLock = true;
256
+ this.mainRunning = true;
257
+
258
+ do {
259
+ this.pendingRescan = false;
260
+
261
+ // cancel run if no adapter is selected
262
+ if (this.adapterSelected.length === 0) {
263
+ break;
264
+ }
265
+
266
+ // fill counts and lists of all selected adapter
267
+ try {
268
+ // Erst alle Adapter-Daten sammeln, DANN einmal createLists aufrufen.
269
+ // createLists resettet batteryLowPowered/offlineDevices/etc. am Anfang –
270
+ // wird es innerhalb der Schleife aufgerufen, enthält listAllDevicesRaw beim
271
+ // 1. Durchlauf nur Adapter 0, beim 2. nur Adapter 0+1 usw. → instabile Listen.
272
+ for (let i = 0; i < this.selAdapter.length; i++) {
273
+ await crud.createData(this, i);
274
+ }
275
+ await crud.createLists(this); // einmal, nachdem alle Daten gesammelt sind
276
+ await crud.writeDatapoints(this); // fill the datapoints
277
+ this.log.debug(`Created and filled data for all adapters`);
278
+ } catch (error) {
279
+ this.log.error(`[main - create data of all adapter] - ${error}`);
280
+ }
281
+
282
+ // fill datapoints for each adapter if selected
283
+ if (this.configCreateOwnFolder) {
284
+ try {
285
+ for (const [id] of Object.entries(adapterArray)) {
286
+ const adapter = adapterArray[id];
287
+
288
+ if (this.adapterSelected.includes(adapter.adapterKey)) {
289
+ // createLists nur EINMAL pro Adapter aufrufen, nicht pro Gerät (Systemlast + Mixing-Fix)
290
+ await crud.createLists(this, id);
291
+ await crud.writeDatapoints(this, id); // fill the datapoints
292
+ this.log.debug(`Created and filled data for ${tools.capitalize(id)}`);
293
+ }
294
+ }
295
+ } catch (error) {
296
+ this.log.error(`[main - create and fill datapoints for each adapter] - ${error}`);
297
+ }
298
+ }
299
+
300
+ if (this.pendingRescan) {
301
+ this.log.info(`[main] Pending rescan detected – Schleife wird wiederholt`);
302
+ }
303
+
304
+ } while (this.pendingRescan);
305
+
306
+ this.processingLock = false;
307
+ this.mainRunning = false;
308
+ this.log.debug(`Function finished: ${this.main.name}`);
309
+
310
+ // Falls refreshData() während main() gewartet hat, jetzt nachholen
311
+ if (this.pendingRefresh) {
312
+ this.pendingRefresh = false;
313
+ this.log.debug(`[main] pendingRefresh – starte refreshData nach main()`);
314
+ await this.refreshData();
315
+ }
316
+ } //<--End of main function
317
+
318
+ // If you need to react to object changes, uncomment the following block and the corresponding line in the constructor.
319
+ // You also need to subscribe to the objects with `this.subscribeObjects`, similar to `this.subscribeStates`.
320
+ //
321
+
322
+ async onObjectChange(id, obj) {
323
+ if (obj) {
324
+ try {
325
+ // The object was changed
326
+ //this.log.debug(`object ${id} changed: ${JSON.stringify(obj)}`);
327
+
328
+ if (this.config.checkAdapterInstances && id.startsWith('system.adapter.')) {
329
+ //read new instance data and add it to the lists
330
+ if (typeof id === 'string' && /\d$/.test(id)) {
331
+ await this.getInstanceData(id);
332
+ }
333
+ } else {
334
+ if (Array.from(this.listAllDevicesRaw.values()).some((obj) => obj.mainSelector === id)) {
335
+ if (!this.mainRunning) {
336
+ await this.main();
337
+ } else {
338
+ this.pendingRescan = true;
339
+ }
340
+ } else {
341
+ // Check if the changed object belongs to a monitored adapter (new device)
342
+ const belongsToMonitoredAdapter = this.adapterSelected.some((adapterKey) =>
343
+ id.toLowerCase().startsWith(`${adapterKey.toLowerCase()}.`)
344
+ );
345
+
346
+ if (belongsToMonitoredAdapter) {
347
+ if (!this.mainRunning) {
348
+ this.log.info(`[onObjectChange] New device detected: ${id} – triggering rescan`);
349
+ await this.main();
350
+ } else {
351
+ this.log.debug(`[onObjectChange] main() is running rescan for ${id} queued`);
352
+ this.pendingRescan = true;
353
+ }
354
+ }
355
+ }
356
+ }
357
+ } catch (error) {
358
+ this.log.error(`Issue at object change: ${error}`);
359
+ }
360
+ } else {
361
+ try {
362
+ // The object was deleted
363
+ this.log.debug(`object ${id} deleted`);
364
+
365
+ // delete instance data in map
366
+ if (this.listInstanceRaw.has(id)) {
367
+ this.listInstanceRaw.delete(id);
368
+ }
369
+
370
+ // delete device data in map
371
+ if (this.listAllDevicesRaw.has(id)) {
372
+ this.listAllDevicesRaw.delete(id);
373
+ }
374
+ // also remove all child devices if a parent/adapter object was deleted
375
+ const idPrefix = `${id}.`;
376
+ for (const key of this.listAllDevicesRaw.keys()) {
377
+ if (key.startsWith(idPrefix)) {
378
+ this.log.debug(`[onObjectChange] removing child device from map: ${key}`);
379
+ this.listAllDevicesRaw.delete(key);
380
+ }
381
+ }
382
+
383
+ //unsubscribe of Objects and states
384
+ this.unsubscribeForeignObjects(id);
385
+ this.unsubscribeForeignStates(id);
386
+ } catch (error) {
387
+ this.log.error(`Issue at object deletion: ${error}`);
388
+ }
389
+ }
390
+ }
391
+
392
+ async onStateChange(id, state) {
393
+ if (state) {
394
+ // this.log.debug(`State changed: ${id} changed ${state.val}`);
395
+ try {
396
+ /*=============================================
397
+ = Instances / Adapter =
398
+ =============================================*/
399
+ if (this.config.checkAdapterInstances) {
400
+ // Adapter Update data
401
+ if (id.endsWith('updatesJson')) {
402
+ await this.renewAdapterUpdateData(id);
403
+ }
404
+ // Instanz data
405
+ if (Array.from(this.listInstanceRaw.values()).some((obj) => Object.values(obj).includes(id))) {
406
+ await this.renewInstanceData(id, state);
407
+ }
408
+ }
409
+
410
+ /*=============================================
411
+ = Devices =
412
+ =============================================*/
413
+ if (Array.from(this.listAllDevicesRaw.values()).some((obj) => Object.values(obj).includes(id))) {
414
+ const listDirty = await this.renewDeviceData(id, state);
415
+
416
+ if (listDirty) {
417
+ await crud.createLists(this);
418
+ await crud.writeDatapoints(this);
419
+
420
+ if (this.configCreateOwnFolder) {
421
+ for (const [adId] of Object.entries(adapterArray)) {
422
+ const adapter = adapterArray[adId];
423
+ if (this.adapterSelected.includes(adapter.adapterKey)) {
424
+ await crud.createLists(this, adId);
425
+ await crud.writeDatapoints(this, adId);
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+ } catch (error) {
432
+ this.log.error(`Issue at state change: ${id}`);
433
+ }
434
+ } else {
435
+ // The state was deleted
436
+ this.log.debug(`state ${id} deleted`);
437
+ }
438
+ }
439
+
440
+ onMessage(obj) {
441
+ const devices = [];
442
+ const instances = [];
443
+ const instancesTime = [];
444
+ let countDevices = 0;
445
+ let countInstances = 0;
446
+
447
+ switch (obj.command) {
448
+ case 'devicesList':
449
+ if (obj.message) {
450
+ try {
451
+ for (const deviceData of this.listAllDevicesRaw.values()) {
452
+ const label = `${deviceData.Adapter}: ${deviceData.Device}`;
453
+ const valueObjectDevices = {
454
+ deviceName: deviceData.Device,
455
+ adapter: deviceData.Adapter,
456
+ path: deviceData.Path,
457
+ };
458
+ devices[countDevices] = {label: label, value: JSON.stringify(valueObjectDevices)};
459
+ countDevices++;
460
+ }
461
+ const sortDevices = devices.slice(0);
462
+ sortDevices.sort(function (a, b) {
463
+ const x = a.label;
464
+ const y = b.label;
465
+ return x < y ? -1 : x > y ? 1 : 0;
466
+ });
467
+ this.sendTo(obj.from, obj.command, sortDevices, obj.callback);
468
+ } catch (error) {
469
+ this.log.error(`[onMessage - deviceList for blacklisttable] - ${error}`);
470
+ }
471
+ }
472
+ break;
473
+
474
+ case 'instancesList':
475
+ if (obj.message) {
476
+ try {
477
+ for (const [instance, instanceData] of this.listInstanceRaw) {
478
+ const label = `${instanceData.Adapter}: ${instance}`;
479
+ const valueObjectInstances = {
480
+ adapter: instanceData.Adapter,
481
+ instanceID: instance,
482
+ };
483
+ instances[countInstances] = {label: label, value: JSON.stringify(valueObjectInstances)};
484
+ countInstances++;
485
+ }
486
+ const sortInstances = instances.slice(0);
487
+ sortInstances.sort(function (a, b) {
488
+ const x = a.label;
489
+ const y = b.label;
490
+ return x < y ? -1 : x > y ? 1 : 0;
491
+ });
492
+ this.sendTo(obj.from, obj.command, sortInstances, obj.callback);
493
+ } catch (error) {
494
+ this.log.error(`[onMessage - instanceList] - ${error}`);
495
+ }
496
+ }
497
+ break;
498
+ case 'instancesListTime':
499
+ if (obj.message) {
500
+ try {
501
+ for (const [instance, instanceData] of this.listInstanceRaw) {
502
+ const label = `${instanceData.Adapter}: ${instance}`;
503
+ const valueObjectInstances = {
504
+ adapter: instanceData.Adapter,
505
+ instanceName: instance,
506
+ };
507
+ instancesTime[countInstances] = {label: label, value: JSON.stringify(valueObjectInstances)};
508
+ countInstances++;
509
+ }
510
+ const sortInstances = instancesTime.slice(0);
511
+ sortInstances.sort(function (a, b) {
512
+ const x = a.label;
513
+ const y = b.label;
514
+ return x < y ? -1 : x > y ? 1 : 0;
515
+ });
516
+ this.sendTo(obj.from, obj.command, sortInstances, obj.callback);
517
+ } catch (error) {
518
+ this.log.error(`[onMessage - instanceList] - ${error}`);
519
+ }
520
+ }
521
+ break;
522
+ default:
523
+ this.log.warn(`[onMessage] Unknown command: ${obj.command}`);
524
+ break;
525
+ }
526
+ }
527
+
528
+ /**
529
+ * refresh data with interval
530
+ * is neccessary to refresh lastContact data, especially of devices without state changes
531
+ */
532
+ async refreshData() {
533
+ if (isUnloaded) {
534
+ return;
535
+ } // cancel run if unloaded was called.
536
+
537
+ // Timer ZUERST clearen – vor allen awaits, um Timer-Akkumulation zu verhindern
538
+ if (this.refreshDataTimeout) {
539
+ this.clearTimeout(this.refreshDataTimeout);
540
+ this.refreshDataTimeout = null;
541
+ }
542
+
543
+ // Race Condition Guard: wenn main() gerade läuft, refresh verschieben
544
+ if (this.processingLock) {
545
+ this.log.debug(`[refreshData] processingLock aktiv – refreshData wird nach main() nachgeholt`);
546
+ this.pendingRefresh = true;
547
+ // Timer neu ansetzen damit der refresh nicht komplett verloren geht
548
+ this.refreshDataTimeout = this.setTimeout(async () => {
549
+ this.log.debug('Updating Data (delayed after lock)');
550
+ await this.refreshData();
551
+ }, this.config.updateinterval * 1000);
552
+ return;
553
+ }
554
+
555
+ this.processingLock = true;
556
+ const nextTimeout = this.config.updateinterval * 1000;
557
+
558
+ try {
559
+ const statusChanged = await tools.checkLastContact(this);
560
+
561
+ if (statusChanged) {
562
+ this.log.debug(`[refreshData] Statusänderung erkannt – Listen werden neu gebaut`);
563
+ await crud.createLists(this);
564
+ await crud.writeDatapoints(this);
565
+
566
+ if (this.configCreateOwnFolder) {
567
+ for (const [id] of Object.entries(adapterArray)) {
568
+ const adapter = adapterArray[id];
569
+
570
+ if (this.adapterSelected.includes(adapter.adapterKey)) {
571
+ await crud.createLists(this, id);
572
+ await crud.writeDatapoints(this, id);
573
+ this.log.debug(`Created and filled data for ${tools.capitalize(id)}`);
574
+ }
575
+ }
576
+ }
577
+ } else {
578
+ this.log.debug(`[refreshData] Kein Statuswechsel – kein Neuschreiben der Listen`);
579
+ const lastCheck = `${this.formatDate(new Date(), 'DD.MM.YYYY')} - ${this.formatDate(new Date(), 'hh:mm:ss')}`;
580
+ await this.setStateChangedAsync('lastCheck', lastCheck, true);
581
+ }
582
+
583
+ if (this.configCreateInstanceList) {
584
+ await this.createInstanceList();
585
+ await this.writeInstanceDPs();
586
+ }
587
+ } finally {
588
+ this.processingLock = false;
589
+ }
590
+
591
+ if (isUnloaded) {
592
+ return;
593
+ } // nochmal prüfen nach den awaits
594
+
595
+ this.refreshDataTimeout = this.setTimeout(async () => {
596
+ this.log.debug('Updating Data');
597
+ await this.refreshData();
598
+ }, nextTimeout);
599
+ } // <-- refreshData end
600
+
601
+ /*=============================================
602
+ = functions to get data =
603
+ =============================================*/
604
+
605
+ /**
606
+ * @param {object} id - deviceID
607
+ * @param {object} i - each Device
608
+ */
609
+ async getDeviceName(id, i) {
610
+ try {
611
+ //id = id.replace(/[\]\\[.*,;'"`<>\\\s?]/g, '-');
612
+
613
+ const currDeviceString = id.slice(0, id.lastIndexOf('.') + 1 - 1);
614
+ const shortCurrDeviceString = currDeviceString.slice(0, currDeviceString.lastIndexOf('.') + 1 - 1);
615
+ const shortshortCurrDeviceString = shortCurrDeviceString.slice(0, shortCurrDeviceString.lastIndexOf('.') + 1 - 1);
616
+
617
+ // Get device name
618
+ const deviceObject = await this.getForeignObjectAsync(currDeviceString);
619
+ const shortDeviceObject = await this.getForeignObjectAsync(shortCurrDeviceString);
620
+ const shortshortDeviceObject = await this.getForeignObjectAsync(shortshortCurrDeviceString);
621
+ let deviceName;
622
+ let folderName;
623
+ let deviceID;
624
+
625
+ switch (this.selAdapter[i].adapterID) {
626
+ case 'fullybrowser':
627
+ deviceName = `${await tools.getInitValue(this, currDeviceString + this.selAdapter[i].id)} ${await tools.getInitValue(this, currDeviceString + this.selAdapter[i].id2)}`;
628
+ break;
629
+
630
+ // Get ID with short currDeviceString from objectjson
631
+ case 'hueExt':
632
+ case 'hmrpc':
633
+ case 'matter':
634
+ case 'nukiExt':
635
+ case 'wled':
636
+ case 'mqttNuki':
637
+ case 'loqedSmartLock':
638
+ case 'viessmann':
639
+ case 'homekitController':
640
+ case 'ring':
641
+ if (shortDeviceObject && typeof shortDeviceObject === 'object' && shortDeviceObject.common) {
642
+ deviceName = shortDeviceObject.common.name;
643
+ }
644
+ break;
645
+
646
+ // Get ID with short short currDeviceString from objectjson (HMiP Devices)
647
+ case 'hmiP':
648
+ if (shortshortDeviceObject && typeof shortshortDeviceObject === 'object' && shortshortDeviceObject.common) {
649
+ deviceName = shortshortDeviceObject.common.name;
650
+ }
651
+ break;
652
+
653
+ // Get ID with short currDeviceString from datapoint
654
+ case 'mihomeVacuum':
655
+ case 'roomba':
656
+ folderName = shortCurrDeviceString.slice(shortCurrDeviceString.lastIndexOf('.') + 1);
657
+ deviceID = await tools.getInitValue(this, shortCurrDeviceString + this.selAdapter[i].id);
658
+ deviceName = `I${folderName} ${deviceID}`;
659
+ break;
660
+
661
+ //Get ID of foldername
662
+ case 'tado':
663
+ case 'wifilight':
664
+ case 'fullybrowserV3':
665
+ case 'sonoff':
666
+ deviceName = currDeviceString.slice(currDeviceString.lastIndexOf('.') + 1);
667
+ break;
668
+
669
+ // Format Device name
670
+ case 'sureflap':
671
+ if (deviceObject && typeof deviceObject === 'object' && deviceObject.common) {
672
+ deviceName = deviceObject.common.name
673
+ .replace(/'/g, '')
674
+ .replace(/\(\d+\)/g, '')
675
+ .trim()
676
+ .replace('Hub', 'Hub -')
677
+ .replace('Device', 'Device -');
678
+ }
679
+ break;
680
+
681
+ //Get ID of foldername
682
+ case 'yeelight':
683
+ deviceName = shortCurrDeviceString.slice(shortCurrDeviceString.lastIndexOf('.') + 1);
684
+ break;
685
+
686
+ // Get ID with main selektor from objectjson
687
+ default:
688
+ if (this.selAdapter[i].id !== 'none' || this.selAdapter[i].id !== undefined) {
689
+ deviceName = await tools.getInitValue(this, currDeviceString + this.selAdapter[i].id);
690
+ }
691
+ if (deviceName === null || deviceName === undefined) {
692
+ if (deviceObject && typeof deviceObject === 'object' && deviceObject.common) {
693
+ deviceName = deviceObject.common.name;
694
+ }
695
+ }
696
+ break;
697
+ }
698
+ return deviceName;
699
+ } catch (error) {
700
+ this.log.error(`[getDeviceName] - ${error}`);
701
+ }
702
+ }
703
+
704
+ /**
705
+ * calculate Signalstrength
706
+ *
707
+ * @param {object} deviceQualityState - State value
708
+ * @param {object} adapterID - adapter name
709
+ */
710
+ async calculateSignalStrength(deviceQualityState, adapterID) {
711
+ let linkQuality;
712
+ let linkQualityRaw;
713
+ let mqttNukiValue;
714
+
715
+ if (deviceQualityState != null) {
716
+ const {val} = deviceQualityState;
717
+
718
+ if (typeof val === 'number') {
719
+ if (this.config.trueState) {
720
+ linkQuality = val;
721
+ } else {
722
+ switch (adapterID) {
723
+ case 'roomba':
724
+ case 'sonoff':
725
+ case 'smartgarden':
726
+ linkQuality = `${val}%`;
727
+ linkQualityRaw = val;
728
+ break;
729
+ case 'lupusec':
730
+ case 'fullybrowserV3':
731
+ linkQuality = val;
732
+ break;
733
+ default:
734
+ if (val <= -255) {
735
+ linkQuality = ' - ';
736
+ } else if (val < 0) {
737
+ linkQualityRaw = Math.min(Math.max(2 * (val + 100), 0), 100);
738
+ linkQuality = `${linkQualityRaw}%`;
739
+ } else if (val >= 0) {
740
+ linkQualityRaw = parseFloat(((100 / 255) * val).toFixed(0));
741
+ linkQuality = `${linkQualityRaw}%`;
742
+ }
743
+ break;
744
+ }
745
+ }
746
+ } else if (typeof val === 'string') {
747
+ switch (adapterID) {
748
+ case 'netatmo':
749
+ linkQuality = val;
750
+ break;
751
+ case 'nukiExt':
752
+ linkQuality = ' - ';
753
+ break;
754
+ case 'mqttNuki':
755
+ linkQuality = val;
756
+ mqttNukiValue = parseInt(linkQuality);
757
+ if (this.config.trueState) {
758
+ linkQuality = val;
759
+ } else if (mqttNukiValue < 0) {
760
+ linkQualityRaw = Math.min(Math.max(2 * (mqttNukiValue + 100), 0), 100);
761
+ linkQuality = `${linkQualityRaw}%`;
762
+ }
763
+ break;
764
+ }
765
+ }
766
+ } else {
767
+ linkQuality = ' - ';
768
+ }
769
+ return [linkQuality, linkQualityRaw];
770
+ }
771
+
772
+ /**
773
+ * get battery data
774
+ *
775
+ * @param {object} deviceBatteryState - State value
776
+ * @param {object} deviceLowBatState - State value
777
+ * @param {object} faultReportingState - State value
778
+ * @param {object} adapterID - adapter name
779
+ */
780
+ async getBatteryData(deviceBatteryState, deviceLowBatState, faultReportingState, adapterID) {
781
+ let batteryHealth = '-';
782
+ let isBatteryDevice = false;
783
+ let batteryHealthRaw;
784
+ let batteryHealthUnitRaw;
785
+
786
+ switch (adapterID) {
787
+ case 'lupusec':
788
+ if (deviceBatteryState === undefined) {
789
+ if (deviceLowBatState === 1) {
790
+ batteryHealth = 'ok';
791
+ isBatteryDevice = true;
792
+ } else {
793
+ batteryHealth = 'low';
794
+ isBatteryDevice = true;
795
+ }
796
+ }
797
+ break;
798
+ case 'hmrpc':
799
+ if (deviceBatteryState === undefined) {
800
+ if (faultReportingState !== undefined && faultReportingState !== 6) {
801
+ batteryHealth = 'ok';
802
+ isBatteryDevice = true;
803
+ } else if (deviceLowBatState !== undefined && deviceLowBatState !== 1) {
804
+ batteryHealth = 'ok';
805
+ isBatteryDevice = true;
806
+ } else if (deviceLowBatState !== undefined) {
807
+ batteryHealth = 'low';
808
+ isBatteryDevice = true;
809
+ }
810
+ } else if (deviceBatteryState !== 0 && deviceBatteryState < 6) {
811
+ batteryHealth = `${deviceBatteryState}V`;
812
+ batteryHealthRaw = deviceBatteryState;
813
+ batteryHealthUnitRaw = 'V';
814
+ isBatteryDevice = true;
815
+ }
816
+ break;
817
+ case 'xsense':
818
+ if (deviceBatteryState === undefined) {
819
+ // do nothin brdge has no battery
820
+ isBatteryDevice = false;
821
+ } else if (deviceBatteryState >= 0) {
822
+ batteryHealthRaw = deviceBatteryState;
823
+ batteryHealthUnitRaw = '';
824
+ isBatteryDevice = true;
825
+ switch (batteryHealthRaw) {
826
+ case 1:
827
+ batteryHealth = 'low';
828
+ break;
829
+ case 2:
830
+ batteryHealth = 'medium';
831
+ break;
832
+ case 3:
833
+ batteryHealth = 'ok';
834
+ break;
835
+ default:
836
+ batteryHealth = 'error';
837
+ }
838
+ }
839
+ break;
840
+ default:
841
+ if (deviceBatteryState === undefined) {
842
+ if (deviceLowBatState !== undefined) {
843
+ // Explicit OK states: false, 'NORMAL', 0 (some adapters use 0=ok)
844
+ if (
845
+ deviceLowBatState === false ||
846
+ deviceLowBatState === 'NORMAL' ||
847
+ deviceLowBatState === 0
848
+ ) {
849
+ batteryHealth = 'ok';
850
+ isBatteryDevice = true;
851
+ } else {
852
+ // true, 1, any other string != 'NORMAL' → low
853
+ batteryHealth = 'low';
854
+ isBatteryDevice = true;
855
+ }
856
+ }
857
+ } else {
858
+ if (typeof deviceBatteryState === 'string') {
859
+ if (deviceBatteryState === 'high' || deviceBatteryState === 'medium') {
860
+ batteryHealth = 'ok';
861
+ isBatteryDevice = true;
862
+ } else if (deviceBatteryState === 'low') {
863
+ batteryHealth = 'low';
864
+ isBatteryDevice = true;
865
+ }
866
+ } else {
867
+ batteryHealth = `${deviceBatteryState}%`;
868
+ batteryHealthRaw = deviceBatteryState;
869
+ batteryHealthUnitRaw = '%';
870
+ isBatteryDevice = true;
871
+ }
872
+ }
873
+ break;
874
+ }
875
+
876
+ return [batteryHealth, isBatteryDevice, batteryHealthRaw, batteryHealthUnitRaw];
877
+ }
878
+
879
+ /**
880
+ * set low bat indicator
881
+ *
882
+ * @param {object} deviceBatteryState
883
+ * @param {object} deviceLowBatState
884
+ * @param {object} faultReportState
885
+ * @param {object} adapterID
886
+ */
887
+
888
+ async setLowbatIndicator(deviceBatteryState, deviceLowBatState, faultReportState, adapterID) {
889
+ let lowBatIndicator = false;
890
+
891
+ if (deviceLowBatState !== undefined || faultReportState !== undefined) {
892
+ switch (adapterID) {
893
+ case 'hmrpc':
894
+ if (deviceLowBatState === 1 || deviceLowBatState === true || faultReportState === 6) {
895
+ lowBatIndicator = true;
896
+ }
897
+ break;
898
+ default:
899
+ if (typeof deviceLowBatState === 'boolean' && deviceLowBatState === true) {
900
+ // true = low bat
901
+ lowBatIndicator = true;
902
+ } else if (typeof deviceLowBatState === 'string' && deviceLowBatState !== 'NORMAL') {
903
+ // any string other than 'NORMAL' = low bat
904
+ lowBatIndicator = true;
905
+ } else if (typeof deviceLowBatState === 'number' && deviceLowBatState === 1) {
906
+ // 1 = low bat (0 = ok), consistent with getBatteryData
907
+ lowBatIndicator = true;
908
+ }
909
+ }
910
+ } else if (typeof deviceBatteryState === 'number' && deviceBatteryState < this.config.minWarnBatterie) {
911
+ lowBatIndicator = true;
912
+ } else if (typeof deviceBatteryState === 'string' && deviceBatteryState === 'low') {
913
+ lowBatIndicator = true;
914
+ }
915
+
916
+ return lowBatIndicator;
917
+ }
918
+
919
+ /**
920
+ * get Last Contact
921
+ *
922
+ * @param {object} selector - Selector
923
+ */
924
+ async getLastContact(selector) {
925
+ const lastContact = tools.getTimestamp(selector);
926
+
927
+ let lastContactString;
928
+
929
+ if (lastContact >= 3600) {
930
+ lastContactString = `${(lastContact / 3600).toFixed(1)} ${translations.hours[this.config.userSelectedLanguage]}`;
931
+ } else if (lastContact >= 60) {
932
+ lastContactString = `${Math.round(lastContact / 60)} ${translations.minits[this.config.userSelectedLanguage]}`;
933
+ } else {
934
+ lastContactString = `${Math.round(lastContact)} ${translations.secs[this.config.userSelectedLanguage]}`;
935
+ }
936
+
937
+ return lastContactString;
938
+ }
939
+
940
+ async getOnlineState(timeSelector, adapterID, treeDP, linkQuality, deviceUnreachState, deviceStateSelectorHMRPC, rssiPeerSelectorHMRPC) {
941
+ let lastContactString;
942
+ let lastContact;
943
+ let deviceState = 'Online';
944
+ let linkQualitySet = linkQuality ?? '0%';
945
+
946
+ try {
947
+ const deviceTimeSelector = await this.getForeignStateAsync(timeSelector);
948
+ const deviceUnreachSelector = await this.getForeignStateAsync(treeDP);
949
+
950
+ const lastDeviceUnreachStateChange = deviceUnreachSelector?.lc != null ? tools.getTimestamp(deviceUnreachSelector.lc) : tools.getTimestamp(deviceTimeSelector?.ts ?? Date.now());
951
+
952
+ // ignore disabled device from zigbee2MQTT
953
+ if (adapterID === 'zigbee2MQTT') {
954
+ const is_device_disabled = await tools.isDisabledDevice(this, treeDP.substring(0, treeDP.lastIndexOf('.')));
955
+
956
+ if (is_device_disabled) {
957
+ return [null, 'disabled', ' - '];
958
+ }
959
+ }
960
+
961
+ if (adapterID === 'hmrpc') {
962
+ const deviceState = await this.getForeignStateAsync(deviceStateSelectorHMRPC);
963
+ const rssiPeer = await this.getForeignStateAsync(rssiPeerSelectorHMRPC);
964
+
965
+ if (linkQuality !== ' - ' && deviceTimeSelector) {
966
+ const ts = deviceUnreachState === 1 ? deviceTimeSelector.lc : deviceTimeSelector.ts;
967
+ lastContactString = await this.getLastContact(ts);
968
+ } else if (deviceState) {
969
+ lastContactString = await this.getLastContact(deviceState.ts);
970
+ } else if (rssiPeer) {
971
+ lastContactString = await this.getLastContact(rssiPeer.ts);
972
+ }
973
+ } else if (deviceTimeSelector) {
974
+ const ts = !deviceUnreachState ? deviceTimeSelector.lc : deviceTimeSelector.ts;
975
+ lastContactString = await this.getLastContact(ts);
976
+ }
977
+
978
+ if (deviceTimeSelector) {
979
+ lastContact = tools.getTimestamp(deviceTimeSelector.ts);
980
+ }
981
+
982
+ const gefundenerAdapter = Object.values(adapterArray).find((adapter) => adapter.adapterID === adapterID);
983
+ if (!gefundenerAdapter) {
984
+ this.log.warn(`[getOnlineState] - adapter not found in adapterArray for adapterID: ${adapterID}`);
985
+ return [lastContactString ?? ' - ', deviceState, linkQualitySet];
986
+ }
987
+ const device = Object.values(this.config.tableDevices).find((adapter) => adapter.adapterKey === gefundenerAdapter.adapterKey);
988
+ if (!device) {
989
+ this.log.warn(`[getOnlineState] - device config not found for adapterKey: ${gefundenerAdapter.adapterKey}`);
990
+ return [lastContactString ?? ' - ', deviceState, linkQualitySet];
991
+ }
992
+ const maxSecondDevicesOffline = device.maxSecondDevicesOffline;
993
+
994
+ switch (adapterID) {
995
+ case 'hmrpc': {
996
+ if (maxSecondDevicesOffline <= 0) {
997
+ if (deviceUnreachState === 1) {
998
+ deviceState = 'Offline'; //set online state to offline
999
+ if (linkQuality !== ' - ') {
1000
+ linkQualitySet = '0%';
1001
+ } // set linkQuality to nothing
1002
+ }
1003
+ } else if (lastDeviceUnreachStateChange > maxSecondDevicesOffline && deviceUnreachState === 1) {
1004
+ deviceState = 'Offline'; //set online state to offline
1005
+ if (linkQuality !== ' - ') {
1006
+ linkQualitySet = '0%';
1007
+ } // set linkQuality to nothing
1008
+ }
1009
+ break;
1010
+ }
1011
+ case 'proxmox': {
1012
+ if (maxSecondDevicesOffline <= 0) {
1013
+ if (deviceUnreachState !== 'running' && deviceUnreachState !== 'online') {
1014
+ deviceState = 'Offline'; //set online state to offline
1015
+ if (linkQuality !== ' - ') {
1016
+ linkQualitySet = '0%';
1017
+ } // set linkQuality to nothing
1018
+ }
1019
+ } else if (lastDeviceUnreachStateChange > maxSecondDevicesOffline && deviceUnreachState !== 'running' && deviceUnreachState !== 'online') {
1020
+ deviceState = 'Offline'; //set online state to offline
1021
+ if (linkQuality !== ' - ') {
1022
+ linkQualitySet = '0%';
1023
+ } // set linkQuality to nothing
1024
+ }
1025
+ break;
1026
+ }
1027
+ case 'hmiP':
1028
+ case 'maxcube': {
1029
+ if (maxSecondDevicesOffline <= 0) {
1030
+ if (deviceUnreachState) {
1031
+ deviceState = 'Offline'; //set online state to offline
1032
+ if (linkQuality !== ' - ') {
1033
+ linkQualitySet = '0%';
1034
+ } // set linkQuality to nothing
1035
+ }
1036
+ } else if (lastDeviceUnreachStateChange > maxSecondDevicesOffline && deviceUnreachState) {
1037
+ deviceState = 'Offline'; //set online state to offline
1038
+ if (linkQuality !== ' - ') {
1039
+ linkQualitySet = '0%';
1040
+ } // set linkQuality to nothing
1041
+ }
1042
+ break;
1043
+ }
1044
+ case 'apcups':
1045
+ case 'hue':
1046
+ case 'hueExt':
1047
+ case 'matter':
1048
+ case 'ping':
1049
+ case 'deconz':
1050
+ case 'shelly':
1051
+ case 'sonoff':
1052
+ case 'tradfri':
1053
+ case 'unifi':
1054
+ case 'zigbee':
1055
+ case 'zigbee2MQTT': {
1056
+ if (maxSecondDevicesOffline <= 0) {
1057
+ if (!deviceUnreachState) {
1058
+ deviceState = 'Offline'; //set online state to offline
1059
+ if (linkQuality !== ' - ') {
1060
+ linkQualitySet = '0%';
1061
+ } // set linkQuality to nothing
1062
+ }
1063
+ } else if (!deviceUnreachState && lastDeviceUnreachStateChange > maxSecondDevicesOffline) {
1064
+ deviceState = 'Offline'; //set online state to offline
1065
+ if (linkQuality !== ' - ') {
1066
+ linkQualitySet = '0%';
1067
+ } // set linkQuality to nothing
1068
+ }
1069
+ break;
1070
+ }
1071
+ case 'mqttClientZigbee2Mqtt': {
1072
+ if (maxSecondDevicesOffline <= 0) {
1073
+ if (deviceUnreachState !== 'online') {
1074
+ deviceState = 'Offline'; //set online state to offline
1075
+ if (linkQuality !== ' - ') {
1076
+ linkQualitySet = '0%';
1077
+ } // set linkQuality to nothing
1078
+ }
1079
+ } else if (deviceUnreachState !== 'online' && lastDeviceUnreachStateChange > maxSecondDevicesOffline) {
1080
+ deviceState = 'Offline'; //set online state to offline
1081
+ if (linkQuality !== ' - ') {
1082
+ linkQualitySet = '0%';
1083
+ }
1084
+ }
1085
+ break;
1086
+ }
1087
+ case 'mihome': {
1088
+ const offlineByTime = maxSecondDevicesOffline <= 0 || (lastContact && lastContact > maxSecondDevicesOffline);
1089
+ const offlineByState = deviceUnreachState !== undefined ? !deviceUnreachState && offlineByTime : offlineByTime;
1090
+
1091
+ if (offlineByState) {
1092
+ deviceState = 'Offline';
1093
+ if (linkQuality !== ' - ') {
1094
+ linkQualitySet = '0%';
1095
+ }
1096
+ }
1097
+ break;
1098
+ }
1099
+ case 'smartgarden': {
1100
+ if (maxSecondDevicesOffline <= 0) {
1101
+ if (deviceUnreachState === 'OFFLINE') {
1102
+ deviceState = 'Offline'; //set online state to offline
1103
+ if (linkQuality !== ' - ') {
1104
+ linkQualitySet = '0%';
1105
+ } // set linkQuality to nothing
1106
+ }
1107
+ } else if (deviceUnreachState === 'OFFLINE' && lastDeviceUnreachStateChange > maxSecondDevicesOffline) {
1108
+ deviceState = 'Offline'; //set online state to offline
1109
+ if (linkQuality !== ' - ') {
1110
+ linkQualitySet = '0%';
1111
+ } // set linkQuality to nothing
1112
+ }
1113
+ break;
1114
+ }
1115
+ default: {
1116
+ // Gerät gilt als offline, wenn es unerreichbar ist und keine Wartezeit definiert ist, oder wenn der letzte Kontakt zu lange her ist als Wartezeit
1117
+ let shouldBeOffline = false;
1118
+
1119
+ if (maxSecondDevicesOffline <= 0) {
1120
+ if (!deviceUnreachState) {
1121
+ shouldBeOffline = true;
1122
+ }
1123
+ } else if (lastContact && lastContact > maxSecondDevicesOffline) {
1124
+ shouldBeOffline = true;
1125
+ }
1126
+
1127
+ if (shouldBeOffline) {
1128
+ deviceState = 'Offline'; // Gerät auf offline setzen
1129
+ if (linkQuality !== ' - ') {
1130
+ linkQualitySet = '0%';
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+
1136
+ return [lastContactString, deviceState, linkQualitySet];
1137
+ } catch (error) {
1138
+ this.log.error(`[getLastContact] - ${error}`);
1139
+ }
1140
+ }
1141
+
1142
+ /**
1143
+ * @param {any} adapterID
1144
+ * @param {string | number | boolean | null} deviceUpdateSelector
1145
+ */
1146
+ async checkDeviceUpdate(adapterID, deviceUpdateSelector) {
1147
+ let isUpgradable = false;
1148
+
1149
+ switch (adapterID) {
1150
+ case 'hmiP':
1151
+ isUpgradable = deviceUpdateSelector === 'UPDATE_AVAILABLE';
1152
+ break;
1153
+
1154
+ case 'ring':
1155
+ isUpgradable = deviceUpdateSelector !== 'Up to Date';
1156
+ break;
1157
+
1158
+ default:
1159
+ if (typeof deviceUpdateSelector === 'boolean') {
1160
+ isUpgradable = deviceUpdateSelector;
1161
+ }
1162
+ break;
1163
+ }
1164
+
1165
+ return isUpgradable;
1166
+ }
1167
+
1168
+ /**
1169
+ * fill the lists for user
1170
+ *
1171
+ * @param {object} device
1172
+ */
1173
+ async theLists(device) {
1174
+ // Raw List with all devices for user
1175
+ if (device.Status !== 'disabled') {
1176
+ // Deduplication: some adapters (e.g. hmrpc with multiple channels, hue-extended with
1177
+ // devices appearing under both lights and sensors) create multiple Map entries for the
1178
+ // same physical device. Use Path as unique key to prevent duplicate list entries.
1179
+ const lang = this.config.userSelectedLanguage;
1180
+ const alreadyInUserRaw = this.listAllDevicesUserRaw.some((d) => d.Device === device.Device && d.Adapter === device.Adapter);
1181
+ if (!alreadyInUserRaw) {
1182
+ this.listAllDevicesUserRaw.push({
1183
+ Device: device.Device,
1184
+ Adapter: device.Adapter,
1185
+ Instance: device.instance,
1186
+ 'Instance connected': device.instanceDeviceConnected,
1187
+ isBatteryDevice: device.isBatteryDevice,
1188
+ Battery: device.Battery,
1189
+ BatteryRaw: device.BatteryRaw,
1190
+ BatteryUnitRaw: device.BatteryUnitRaw,
1191
+ isLowBat: device.LowBat,
1192
+ 'Signal strength': device.SignalStrength,
1193
+ 'Signal strength Raw': device.SignalStrengthRaw,
1194
+ 'Last contact': device.LastContact,
1195
+ 'Update Available': device.Upgradable,
1196
+ Status: device.Status,
1197
+ });
1198
+ }
1199
+
1200
+ // List with all devices
1201
+ const alreadyInAll = this.listAllDevices.some(
1202
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1203
+ );
1204
+ if (!alreadyInAll) {
1205
+ this.listAllDevices.push({
1206
+ [translations.Device[lang]]: device.Device,
1207
+ [translations.Adapter[lang]]: device.Adapter,
1208
+ [translations.Battery[lang]]: device.Battery,
1209
+ [translations.Signal_strength[lang]]: device.SignalStrength,
1210
+ [translations.Last_Contact[lang]]: device.LastContact,
1211
+ [translations.Status[lang]]: device.Status,
1212
+ });
1213
+ }
1214
+
1215
+ // LinkQuality lists
1216
+ if (device.SignalStrength !== ' - ') {
1217
+ const alreadyInLQ = this.linkQualityDevices.some(
1218
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1219
+ );
1220
+ if (!alreadyInLQ) {
1221
+ this.linkQualityDevices.push({
1222
+ [translations.Device[lang]]: device.Device,
1223
+ [translations.Adapter[lang]]: device.Adapter,
1224
+ [translations.Signal_strength[lang]]: device.SignalStrength,
1225
+ });
1226
+ }
1227
+ }
1228
+
1229
+ // Battery lists
1230
+ if (device.isBatteryDevice) {
1231
+ const alreadyInBat = this.batteryPowered.some(
1232
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1233
+ );
1234
+ if (!alreadyInBat) {
1235
+ this.batteryPowered.push({
1236
+ [translations.Device[lang]]: device.Device,
1237
+ [translations.Adapter[lang]]: device.Adapter,
1238
+ [translations.Battery[lang]]: device.Battery,
1239
+ [translations.Status[lang]]: device.Status,
1240
+ });
1241
+ }
1242
+ }
1243
+
1244
+ // Low Bat lists
1245
+ if (device.LowBat && device.Status !== 'Offline') {
1246
+ const alreadyInLowBat = this.batteryLowPowered.some(
1247
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1248
+ );
1249
+ if (!alreadyInLowBat) {
1250
+ this.batteryLowPowered.push({
1251
+ [translations.Device[lang]]: device.Device,
1252
+ [translations.Adapter[lang]]: device.Adapter,
1253
+ [translations.Battery[lang]]: device.Battery,
1254
+ });
1255
+ }
1256
+ }
1257
+
1258
+ // Offline List
1259
+ if (device.Status === 'Offline') {
1260
+ const alreadyOffline = this.offlineDevices.some(
1261
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1262
+ );
1263
+ if (!alreadyOffline) {
1264
+ this.offlineDevices.push({
1265
+ [translations.Device[lang]]: device.Device,
1266
+ [translations.Adapter[lang]]: device.Adapter,
1267
+ [translations.Last_Contact[lang]]: device.LastContact,
1268
+ });
1269
+ }
1270
+ }
1271
+
1272
+ // Device update List
1273
+ if (device.Upgradable === true || device.Upgradable === 1) {
1274
+ const alreadyUpgradable = this.upgradableList.some(
1275
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1276
+ );
1277
+ if (!alreadyUpgradable) {
1278
+ this.upgradableList.push({
1279
+ [translations.Device[lang]]: device.Device,
1280
+ [translations.Adapter[lang]]: device.Adapter,
1281
+ });
1282
+ }
1283
+ }
1284
+ }
1285
+ }
1286
+
1287
+ /**
1288
+ * @param {string | string[]} id
1289
+ * @param {ioBroker.State} state
1290
+ */
1291
+ async renewDeviceData(id, state) {
1292
+ let batteryData;
1293
+ let signalData;
1294
+ let oldLowBatState;
1295
+ let contactData;
1296
+ let oldStatus;
1297
+ let isLowBatValue;
1298
+ let listDirty = false;
1299
+
1300
+ const deviceID = id.slice(0, id.lastIndexOf('.') + 1 - 1);
1301
+ const deviceData = this.listAllDevicesRaw.get(deviceID);
1302
+
1303
+ this.log.debug(`[renewDeviceData] - ${id}`);
1304
+
1305
+ if (deviceData) {
1306
+ const gefundenerAdapter = Object.values(adapterArray).find((adapter) => adapter.adapterID === deviceData.adapterID);
1307
+ if (!gefundenerAdapter) {
1308
+ this.log.warn(`[renewDeviceData] - adapter not found for adapterID: ${deviceData.adapterID}`);
1309
+ return;
1310
+ }
1311
+ const silentEnabled = Object.values(this.config.tableDevices).find((adapter) => adapter.adapterKey === gefundenerAdapter.adapterKey);
1312
+ if (!silentEnabled) {
1313
+ this.log.warn(`[renewDeviceData] - device config not found for adapterKey: ${gefundenerAdapter.adapterKey}`);
1314
+ return;
1315
+ }
1316
+
1317
+ // On statechange update available datapoint
1318
+ switch (id) {
1319
+ // device connection
1320
+ case deviceData.instanceDeviceConnectionDP:
1321
+ if (state.val !== deviceData.instanceDeviceConnected) {
1322
+ deviceData.instanceDeviceConnected = state.val;
1323
+ }
1324
+ break;
1325
+
1326
+ // device updates
1327
+ case deviceData.UpdateDP:
1328
+ if (state.val !== deviceData.Upgradable) {
1329
+ deviceData.Upgradable = await this.checkDeviceUpdate(deviceData.adapterID, state.val);
1330
+ listDirty = true;
1331
+ if (deviceData.Upgradable === true) {
1332
+ if (this.config.checkSendDeviceUpgrade && !this.blacklistNotify.includes(deviceData.Path)) {
1333
+ await this.sendStateNotifications('Devices', 'updateDevice', deviceID, silentEnabled.telegramSilent);
1334
+ }
1335
+ }
1336
+ }
1337
+ break;
1338
+
1339
+ // device signal – kein listDirty, SignalStrength ändert sich ständig
1340
+ case deviceData.SignalStrengthDP:
1341
+ signalData = await this.calculateSignalStrength(state, deviceData.adapterID);
1342
+ deviceData.SignalStrength = signalData[0];
1343
+ break;
1344
+
1345
+ // device battery
1346
+ case deviceData.batteryDP:
1347
+ if (deviceData.isBatteryDevice) {
1348
+ oldLowBatState = deviceData.LowBat;
1349
+ if (state.val === 0 && deviceData.BatteryRaw >= 5) {
1350
+ break;
1351
+ }
1352
+ batteryData = await this.getBatteryData(state.val, oldLowBatState, deviceData.faultReport, deviceData.adapterID);
1353
+ deviceData.Battery = batteryData[0];
1354
+ deviceData.BatteryRaw = batteryData[2];
1355
+ deviceData.BatteryUnitRaw = batteryData[3];
1356
+ if (deviceData.LowBatDP !== 'none') {
1357
+ isLowBatValue = await tools.getInitValue(this, deviceData.LowBatDP);
1358
+ } else {
1359
+ isLowBatValue = undefined;
1360
+ }
1361
+ deviceData.LowBat = await this.setLowbatIndicator(state.val, isLowBatValue, deviceData.faultReport, deviceData.adapterID);
1362
+ listDirty = true;
1363
+ if (deviceData.LowBat && oldLowBatState !== deviceData.LowBat) {
1364
+ if (this.config.checkSendBatteryMsg && !this.blacklistNotify.includes(deviceData.Path)) {
1365
+ await this.sendStateNotifications('Devices', 'lowBatDevice', deviceID, silentEnabled.telegramSilent);
1366
+ }
1367
+ }
1368
+ }
1369
+ break;
1370
+
1371
+ // device low bat
1372
+ case deviceData.LowBatDP:
1373
+ if (deviceData.isBatteryDevice) {
1374
+ oldLowBatState = deviceData.LowBat;
1375
+ batteryData = await this.getBatteryData(deviceData.BatteryRaw, state.val, deviceData.faultReport, deviceData.adapterID);
1376
+ deviceData.Battery = batteryData[0];
1377
+ deviceData.BatteryRaw = batteryData[2];
1378
+ deviceData.BatteryUnitRaw = batteryData[3];
1379
+ deviceData.LowBat = await this.setLowbatIndicator(deviceData.BatteryRaw, state.val, deviceData.faultReport, deviceData.adapterID);
1380
+ listDirty = true;
1381
+ if (deviceData.LowBat && oldLowBatState !== deviceData.LowBat) {
1382
+ if (this.config.checkSendBatteryMsg && !this.blacklistNotify.includes(deviceData.Path)) {
1383
+ await this.sendStateNotifications('Devices', 'lowBatDevice', deviceID, silentEnabled.telegramSilent);
1384
+ }
1385
+ }
1386
+ }
1387
+ break;
1388
+
1389
+ //device error / fault reports
1390
+ case deviceData.faultReportDP:
1391
+ if (deviceData.isBatteryDevice) {
1392
+ oldLowBatState = deviceData.LowBat;
1393
+ batteryData = await this.getBatteryData(deviceData.BatteryRaw, oldLowBatState, state.val, deviceData.adapterID);
1394
+ deviceData.Battery = batteryData[0];
1395
+ deviceData.BatteryRaw = batteryData[2];
1396
+ deviceData.BatteryUnitRaw = batteryData[3];
1397
+ deviceData.LowBat = await this.setLowbatIndicator(deviceData.BatteryRaw, undefined, state.val, deviceData.adapterID);
1398
+ listDirty = true;
1399
+ if (deviceData.LowBat && oldLowBatState !== deviceData.LowBat) {
1400
+ if (this.config.checkSendBatteryMsg && !this.blacklistNotify.includes(deviceData.Path)) {
1401
+ await this.sendStateNotifications('Devices', 'lowBatDevice', deviceID, silentEnabled.telegramSilent);
1402
+ }
1403
+ }
1404
+ }
1405
+ break;
1406
+
1407
+ // device unreach
1408
+ case deviceData.UnreachDP:
1409
+ if (deviceData.instanceDeviceConnected !== undefined) {
1410
+ if (deviceData.UnreachState !== state.val) {
1411
+ oldStatus = deviceData.Status;
1412
+ deviceData.UnreachState = state.val;
1413
+
1414
+ contactData = await this.getOnlineState(
1415
+ deviceData.timeSelector,
1416
+ deviceData.adapterID,
1417
+ deviceData.UnreachDP,
1418
+ deviceData.SignalStrength,
1419
+ deviceData.UnreachState,
1420
+ deviceData.deviceStateSelectorHMRPC,
1421
+ deviceData.rssiPeerSelectorHMRPC,
1422
+ );
1423
+
1424
+ if (contactData !== undefined && contactData !== null) {
1425
+ deviceData.LastContact = contactData[0];
1426
+ deviceData.Status = contactData[1];
1427
+ deviceData.SignalStrength = contactData[2];
1428
+ }
1429
+
1430
+ if (oldStatus !== deviceData.Status) {
1431
+ listDirty = true;
1432
+ }
1433
+
1434
+ if (this.config.checkSendOfflineMsg && oldStatus !== deviceData.Status && !this.blacklistNotify.includes(deviceData.Path)) {
1435
+ if (await tools.getTimestampConnectionDP(this, deviceData.instanceDeviceConnectionDP, 50000)) {
1436
+ await this.sendStateNotifications('Devices', 'onlineStateDevice', deviceID, silentEnabled.telegramSilent);
1437
+ }
1438
+ }
1439
+ }
1440
+ }
1441
+ }
1442
+ }
1443
+ return listDirty;
1444
+ }
1445
+
1446
+ /**
1447
+ * get all Instances at start
1448
+ */
1449
+ async getAllInstanceData() {
1450
+ try {
1451
+ const allInstances = `system.adapter.*`;
1452
+ await this.getInstanceData(allInstances);
1453
+ } catch (error) {
1454
+ this.log.error(`[getInstance] - ${error}`);
1455
+ }
1456
+ }
1457
+
1458
+ /**
1459
+ * get instance data
1460
+ *
1461
+ *@param {string} instanceObject
1462
+ */
1463
+ async getInstanceData(instanceObject) {
1464
+ try {
1465
+ const idDP = `${instanceObject}.alive`;
1466
+ const instanceAliveDP = await this.getForeignStatesAsync(idDP);
1467
+
1468
+ this.adapterUpdatesJsonRaw = await this.getAdapterUpdateData(adapterUpdateListDP);
1469
+
1470
+ for (const [id] of Object.entries(instanceAliveDP)) {
1471
+ if (!(typeof id === 'string' && id.startsWith(`system.adapter.`))) {
1472
+ continue;
1473
+ }
1474
+
1475
+ // get instance name
1476
+ const instanceID = await this.getInstanceName(id);
1477
+
1478
+ // get instance connected to host data
1479
+ const instanceConnectedHostDP = `system.adapter.${instanceID}.connected`;
1480
+ const instanceConnectedHostVal = await tools.getInitValue(this, instanceConnectedHostDP);
1481
+
1482
+ // get instance connected to device data
1483
+ const instanceConnectedDeviceDP = `${instanceID}.info.connection`;
1484
+ const devicesState = await this.getForeignStateAsync(instanceConnectedDeviceDP);
1485
+
1486
+ let instanceConnectedDeviceVal;
1487
+ if (devicesState !== null && typeof devicesState.val === 'boolean') {
1488
+ instanceConnectedDeviceVal = await tools.getInitValue(this, instanceConnectedDeviceDP);
1489
+ } else {
1490
+ instanceConnectedDeviceVal = 'N/A';
1491
+ }
1492
+
1493
+ // get adapter version
1494
+ const instanceObjectPath = `system.adapter.${instanceID}`;
1495
+ let adapterName;
1496
+ let adapterVersion;
1497
+ let adapterAvailableUpdate = '';
1498
+ let instanceMode;
1499
+ let scheduleTime = 'N/A';
1500
+
1501
+ const instanceObjectData = await this.getForeignObjectAsync(instanceObjectPath);
1502
+
1503
+ if (instanceObjectData) {
1504
+ adapterName = tools.capitalize(instanceObjectData.common.name);
1505
+ adapterVersion = instanceObjectData.common.version;
1506
+ instanceMode = instanceObjectData.common.mode;
1507
+
1508
+ if (instanceMode === 'schedule') {
1509
+ scheduleTime = instanceObjectData.common.schedule;
1510
+ }
1511
+ }
1512
+
1513
+ const updateEntry = this.adapterUpdatesJsonRaw.find((entry) => entry.adapter.toLowerCase() === adapterName.toLowerCase());
1514
+
1515
+ if (updateEntry) {
1516
+ adapterAvailableUpdate = updateEntry.newVersion;
1517
+ } else {
1518
+ adapterAvailableUpdate = ' - ';
1519
+ }
1520
+
1521
+ let isAlive;
1522
+ let isHealthy;
1523
+ let instanceStatus;
1524
+ if (instanceMode === 'schedule') {
1525
+ const instanceStatusRaw = await this.checkScheduleisHealty(instanceID, scheduleTime);
1526
+ isAlive = instanceStatusRaw[0];
1527
+ isHealthy = instanceStatusRaw[1];
1528
+ instanceStatus = instanceStatusRaw[2];
1529
+ } else if (instanceMode === 'daemon') {
1530
+ const instanceStatusRaw = await this.checkDaemonIsHealthy(instanceID);
1531
+ isAlive = instanceStatusRaw[0];
1532
+ isHealthy = instanceStatusRaw[1];
1533
+ instanceStatus = instanceStatusRaw[2];
1534
+ }
1535
+
1536
+ //subscribe to statechanges
1537
+ this.subscribeForeignStates(id);
1538
+ this.subscribeForeignStates(instanceConnectedHostDP);
1539
+ this.subscribeForeignStates(instanceConnectedDeviceDP);
1540
+ this.subscribeForeignObjects(`system.adapter.*`);
1541
+ // this.subscribeForeignStates('*');
1542
+ // this.subscribeForeignObjects('*');
1543
+
1544
+ // create raw list
1545
+ this.listInstanceRaw.set(instanceID, {
1546
+ Adapter: adapterName,
1547
+ instanceObjectPath: instanceObjectPath,
1548
+ instanceMode: instanceMode,
1549
+ schedule: scheduleTime,
1550
+ adapterVersion: adapterVersion,
1551
+ updateAvailable: adapterAvailableUpdate,
1552
+ isAlive: isAlive,
1553
+ isHealthy: isHealthy,
1554
+ isConnectedHost: instanceConnectedHostVal,
1555
+ isConnectedDevice: instanceConnectedDeviceVal,
1556
+ status: instanceStatus,
1557
+ aliveDP: `system.adapter.${instanceID}.alive`,
1558
+ hostConnectionDP: instanceConnectedHostDP,
1559
+ deviceConnectionDP: instanceConnectedDeviceDP,
1560
+ });
1561
+ }
1562
+ await this.createInstanceList();
1563
+ await this.writeInstanceDPs();
1564
+ } catch (error) {
1565
+ this.log.error(`[getInstanceData] - ${error}`);
1566
+ }
1567
+ }
1568
+
1569
+ /**
1570
+ * get Instances
1571
+ *
1572
+ * @param {string} id - Path of alive datapoint
1573
+ */
1574
+ async getInstanceName(id) {
1575
+ let instance = id;
1576
+ instance = instance.slice(15); // remove "system.adapter."
1577
+ instance = instance.slice(0, instance.lastIndexOf('.') + 1 - 1); // remove ".alive"
1578
+ return instance;
1579
+ }
1580
+
1581
+ /**
1582
+ * Check if instance is alive and ok
1583
+ *
1584
+ * @param {string} instanceID
1585
+ */
1586
+ async checkDaemonIsHealthy(instanceID) {
1587
+ const connectedHostState = await tools.getInitValue(this, `system.adapter.${instanceID}.connected`);
1588
+ const isAlive = await tools.getInitValue(this, `system.adapter.${instanceID}.alive`);
1589
+ let connectedDeviceState = await tools.getInitValue(this, `${instanceID}.info.connection`);
1590
+ if (connectedDeviceState === undefined) {
1591
+ connectedDeviceState = true;
1592
+ }
1593
+
1594
+ let isHealthy = false;
1595
+ let instanceStatusString = translations.instance_deactivated[this.config.userSelectedLanguage];
1596
+
1597
+ if (isAlive) {
1598
+ if (connectedHostState && connectedDeviceState) {
1599
+ isHealthy = true;
1600
+ instanceStatusString = translations.instance_okay[this.config.userSelectedLanguage];
1601
+ } else if (!connectedHostState) {
1602
+ instanceStatusString = translations.not_connected_host[this.config.userSelectedLanguage];
1603
+ } else if (!connectedDeviceState) {
1604
+ instanceStatusString = translations.not_connected_device[this.config.userSelectedLanguage];
1605
+ }
1606
+ }
1607
+
1608
+ return [Boolean(isAlive), Boolean(isHealthy), String(instanceStatusString), Boolean(connectedHostState), Boolean(connectedDeviceState)];
1609
+ }
1610
+
1611
+ /**
1612
+ * Check if instance is alive and ok
1613
+ *
1614
+ * @param {string} instanceID
1615
+ * @param {number} instanceDeactivationTime
1616
+ */
1617
+ async checkDaemonIsAlive(instanceID, instanceDeactivationTime) {
1618
+ let isAlive = await tools.getInitValue(this, `system.adapter.${instanceID}.alive`);
1619
+ let daemonIsAlive;
1620
+ let isHealthy = false;
1621
+ let instanceStatusString = isAlive ? translations.instance_activated[this.config.userSelectedLanguage] : translations.instance_deactivated[this.config.userSelectedLanguage];
1622
+
1623
+ if (isAlive) {
1624
+ daemonIsAlive = await this.checkDaemonIsHealthy(instanceID);
1625
+ } else {
1626
+ await this.delay(instanceDeactivationTime);
1627
+ daemonIsAlive = await this.checkDaemonIsHealthy(instanceID);
1628
+ if (!daemonIsAlive[0]) {
1629
+ await this.delay(instanceDeactivationTime);
1630
+ daemonIsAlive = await this.checkDaemonIsHealthy(instanceID);
1631
+ }
1632
+ }
1633
+
1634
+ isAlive = Boolean(daemonIsAlive[0]);
1635
+ isHealthy = Boolean(daemonIsAlive[1]);
1636
+ instanceStatusString = String(daemonIsAlive[2]);
1637
+ const connectedToHost = Boolean(daemonIsAlive[3]);
1638
+ const connectedToDevice = Boolean(daemonIsAlive[4]);
1639
+
1640
+ return [isAlive, isHealthy, instanceStatusString, connectedToHost, connectedToDevice];
1641
+ }
1642
+
1643
+ async checkScheduleisHealty(instanceID, scheduleTime) {
1644
+ let lastUpdate;
1645
+ let previousCronRun = null;
1646
+ let lastCronRun;
1647
+ let diff;
1648
+ let isAlive = false;
1649
+ let isHealthy = false;
1650
+ let instanceStatusString = translations.instance_deactivated[this.config.userSelectedLanguage];
1651
+ const isAliveSchedule = await this.getForeignStateAsync(`system.adapter.${instanceID}.alive`);
1652
+
1653
+ if (isAliveSchedule) {
1654
+ lastUpdate = Math.round((Date.now() - isAliveSchedule.lc) / 1000); // Last state change in seconds
1655
+ previousCronRun = await this.getPreviousCronRun(scheduleTime); // When was the last cron run
1656
+ if (previousCronRun) {
1657
+ lastCronRun = Math.round(previousCronRun / 1000); // change distance to last run in seconds
1658
+ diff = lastCronRun - lastUpdate;
1659
+ if (diff > -300) {
1660
+ // if 5 minutes difference exceeded, instance is not alive
1661
+ isAlive = true;
1662
+ isHealthy = true;
1663
+ instanceStatusString = translations.instance_okay[this.config.userSelectedLanguage];
1664
+ }
1665
+ }
1666
+ }
1667
+
1668
+ return [isAlive, isHealthy, instanceStatusString];
1669
+ }
1670
+
1671
+ /**
1672
+ * set status for instance
1673
+ *
1674
+ * @param {string} instanceMode
1675
+ * @param {string} scheduleTime
1676
+ * @param {any} instanceID
1677
+ */
1678
+ async setInstanceStatus(instanceMode, scheduleTime, instanceID) {
1679
+ let instanceDeactivationTime = (this.config.offlineTimeInstances * 1000) / 2;
1680
+ let instanceErrorTime = (this.config.errorTimeInstances * 1000) / 2;
1681
+ let isAlive;
1682
+ let isHealthy;
1683
+ let instanceStatusString;
1684
+ let daemonIsAlive;
1685
+ let daemonIsNotAlive;
1686
+ let scheduleIsAlive;
1687
+ let connectedToHost;
1688
+ let connectedToDevice;
1689
+
1690
+ switch (instanceMode) {
1691
+ case 'schedule':
1692
+ scheduleIsAlive = await this.checkScheduleisHealty(instanceID, scheduleTime);
1693
+ isAlive = Boolean(scheduleIsAlive[0]);
1694
+ isHealthy = Boolean(scheduleIsAlive[1]);
1695
+ instanceStatusString = String(scheduleIsAlive[2]);
1696
+ break;
1697
+ case 'daemon':
1698
+ // check with time the user did define for error and deactivation
1699
+ if (this.userTimeInstancesList.has(instanceID)) {
1700
+ const userTimeInstances = this.userTimeInstancesList.get(instanceID);
1701
+ instanceDeactivationTime = (userTimeInstances.deactivationTime * 1000) / 2;
1702
+ instanceErrorTime = (userTimeInstances.errorTime * 1000) / 2;
1703
+ }
1704
+ daemonIsAlive = await this.checkDaemonIsHealthy(instanceID);
1705
+ if (daemonIsAlive[0] && !daemonIsAlive[1]) {
1706
+ await this.delay(instanceErrorTime);
1707
+ const daemonIsAliveAfterDelay = await this.checkDaemonIsHealthy(instanceID);
1708
+
1709
+ if (daemonIsAliveAfterDelay[0] && !daemonIsAliveAfterDelay[1]) {
1710
+ await this.delay(instanceErrorTime);
1711
+ const daemonIsAliveAfterSecondDelay = await this.checkDaemonIsHealthy(instanceID);
1712
+
1713
+ // nach allen Retries: Status übernehmen (egal ob erholt oder weiterhin fehlerhaft)
1714
+ isAlive = Boolean(daemonIsAliveAfterSecondDelay[0]);
1715
+ isHealthy = Boolean(daemonIsAliveAfterSecondDelay[1]);
1716
+ instanceStatusString = String(daemonIsAliveAfterSecondDelay[2]);
1717
+ connectedToHost = Boolean(daemonIsAliveAfterSecondDelay[3]);
1718
+ connectedToDevice = Boolean(daemonIsAliveAfterSecondDelay[4]);
1719
+ } else {
1720
+ // nach erstem Retry wieder gesund
1721
+ isAlive = Boolean(daemonIsAliveAfterDelay[0]);
1722
+ isHealthy = Boolean(daemonIsAliveAfterDelay[1]);
1723
+ instanceStatusString = String(daemonIsAliveAfterDelay[2]);
1724
+ connectedToHost = Boolean(daemonIsAliveAfterDelay[3]);
1725
+ connectedToDevice = Boolean(daemonIsAliveAfterDelay[4]);
1726
+ }
1727
+ } else {
1728
+ daemonIsNotAlive = await this.checkDaemonIsAlive(instanceID, instanceDeactivationTime);
1729
+ isAlive = Boolean(daemonIsNotAlive[0]);
1730
+ isHealthy = Boolean(daemonIsNotAlive[1]);
1731
+ instanceStatusString = String(daemonIsNotAlive[2]);
1732
+ connectedToHost = Boolean(daemonIsNotAlive[3]);
1733
+ connectedToDevice = Boolean(daemonIsNotAlive[4]);
1734
+ }
1735
+
1736
+ break;
1737
+ }
1738
+
1739
+ return [isAlive, isHealthy, instanceStatusString, connectedToHost, connectedToDevice];
1740
+ }
1741
+
1742
+ /**
1743
+ * create adapter update raw lists
1744
+ *
1745
+ * @param {string} adapterUpdateListDP
1746
+ */
1747
+ async getAdapterUpdateData(adapterUpdateListDP) {
1748
+ // Clear the existing adapter updates data
1749
+ let adapterUpdatesJsonRaw = [];
1750
+ let adapterJsonList = {};
1751
+
1752
+ // Fetch the adapter updates list
1753
+ const adapterUpdatesListVal = await this.getForeignStatesAsync(adapterUpdateListDP);
1754
+
1755
+ // Extract adapter data from the list - merge all admin instances
1756
+ for (const [_id, value] of Object.entries(adapterUpdatesListVal)) {
1757
+ const parsed = tools.parseData(value.val);
1758
+ if (parsed && typeof parsed === 'object') {
1759
+ Object.assign(adapterJsonList, parsed);
1760
+ }
1761
+ }
1762
+
1763
+ // Populate the adapter updates data
1764
+ for (const [id, adapterData] of Object.entries(adapterJsonList)) {
1765
+ adapterUpdatesJsonRaw.push({
1766
+ adapter: tools.capitalize(id),
1767
+ newVersion: adapterData.availableVersion,
1768
+ oldVersion: adapterData.installedVersion,
1769
+ });
1770
+ }
1771
+
1772
+ return adapterUpdatesJsonRaw;
1773
+ }
1774
+
1775
+ /**
1776
+ * create instanceList
1777
+ */
1778
+ async createAdapterUpdateList() {
1779
+ this.listAdapterUpdates = [];
1780
+ this.countAdapterUpdates = 0;
1781
+
1782
+ for (const updateData of this.adapterUpdatesJsonRaw) {
1783
+ this.listAdapterUpdates.push({
1784
+ [translations.Adapter[this.config.userSelectedLanguage]]: updateData.adapter,
1785
+ [translations.Available_Version[this.config.userSelectedLanguage]]: updateData.newVersion,
1786
+ [translations.Installed_Version[this.config.userSelectedLanguage]]: updateData.oldVersion,
1787
+ });
1788
+ }
1789
+
1790
+ this.countAdapterUpdates = this.listAdapterUpdates.length;
1791
+ await this.writeAdapterUpdatesDPs();
1792
+ }
1793
+
1794
+ /**
1795
+ * write datapoints for adapter with updates
1796
+ */
1797
+ async writeAdapterUpdatesDPs() {
1798
+ // Write Datapoints for counts
1799
+ await this.setStateChangedAsync(`adapterAndInstances.countAdapterUpdates`, {
1800
+ val: this.countAdapterUpdates,
1801
+ ack: true
1802
+ });
1803
+
1804
+ if (this.countAdapterUpdates === 0) {
1805
+ this.listAdapterUpdates = [
1806
+ {
1807
+ [translations.Adapter[this.config.userSelectedLanguage]]: '--no updates--',
1808
+ [translations.Available_Version[this.config.userSelectedLanguage]]: '',
1809
+ [translations.Installed_Version[this.config.userSelectedLanguage]]: '',
1810
+ },
1811
+ ];
1812
+ }
1813
+ await this.setStateChangedAsync(`adapterAndInstances.listAdapterUpdates`, {
1814
+ val: JSON.stringify(this.listAdapterUpdates),
1815
+ ack: true
1816
+ });
1817
+ }
1818
+
1819
+ /**
1820
+ * create instanceList
1821
+ */
1822
+ async createInstanceList() {
1823
+ this.listAllInstances = [];
1824
+ this.listAllActiveInstances = [];
1825
+ this.listDeactivatedInstances = [];
1826
+ this.listErrorInstanceRaw = [];
1827
+ this.listErrorInstance = [];
1828
+
1829
+ for (const [instance, instanceData] of this.listInstanceRaw) {
1830
+ // fill raw list
1831
+ if (instanceData.isAlive && !instanceData.isHealthy) {
1832
+ this.listErrorInstanceRaw.push({
1833
+ Adapter: instanceData.Adapter,
1834
+ Instance: instance,
1835
+ Mode: instanceData.instanceMode,
1836
+ Status: instanceData.status,
1837
+ });
1838
+ }
1839
+
1840
+ if (this.blacklistInstancesLists.includes(instance)) {
1841
+ continue;
1842
+ }
1843
+ // all instances
1844
+ this.listAllInstances.push({
1845
+ [translations.Adapter[this.config.userSelectedLanguage]]: instanceData.Adapter,
1846
+ [translations.Instance[this.config.userSelectedLanguage]]: instance,
1847
+ [translations.Mode[this.config.userSelectedLanguage]]: instanceData.instanceMode,
1848
+ [translations.Schedule[this.config.userSelectedLanguage]]: instanceData.schedule,
1849
+ [translations.Version[this.config.userSelectedLanguage]]: instanceData.adapterVersion,
1850
+ [translations.Updateable[this.config.userSelectedLanguage]]: instanceData.updateAvailable,
1851
+ [translations.Status[this.config.userSelectedLanguage]]: instanceData.status,
1852
+ });
1853
+
1854
+ if (!instanceData.isAlive) {
1855
+ // list with deactivated instances
1856
+ this.listDeactivatedInstances.push({
1857
+ [translations.Adapter[this.config.userSelectedLanguage]]: instanceData.Adapter,
1858
+ [translations.Instance[this.config.userSelectedLanguage]]: instance,
1859
+ [translations.Status[this.config.userSelectedLanguage]]: instanceData.status,
1860
+ });
1861
+ } else {
1862
+ // list with active instances
1863
+ this.listAllActiveInstances.push({
1864
+ [translations.Adapter[this.config.userSelectedLanguage]]: instanceData.Adapter,
1865
+ [translations.Instance[this.config.userSelectedLanguage]]: instance,
1866
+ [translations.Mode[this.config.userSelectedLanguage]]: instanceData.instanceMode,
1867
+ [translations.Schedule[this.config.userSelectedLanguage]]: instanceData.schedule,
1868
+ [translations.Status[this.config.userSelectedLanguage]]: instanceData.status,
1869
+ });
1870
+ }
1871
+
1872
+ // list with error instances
1873
+ if (instanceData.isAlive && !instanceData.isHealthy) {
1874
+ this.listErrorInstance.push({
1875
+ [translations.Adapter[this.config.userSelectedLanguage]]: instanceData.Adapter,
1876
+ [translations.Instance[this.config.userSelectedLanguage]]: instance,
1877
+ [translations.Mode[this.config.userSelectedLanguage]]: instanceData.instanceMode,
1878
+ [translations.Status[this.config.userSelectedLanguage]]: instanceData.status,
1879
+ });
1880
+ }
1881
+ }
1882
+ await this.countInstances();
1883
+ }
1884
+
1885
+ /**
1886
+ * count instanceList
1887
+ */
1888
+ async countInstances() {
1889
+ this.countAllInstances = 0;
1890
+ this.countAllActiveInstances = 0;
1891
+ this.countDeactivatedInstances = 0;
1892
+ this.countErrorInstance = 0;
1893
+
1894
+ this.countAllInstances = this.listAllInstances.length;
1895
+ this.countAllActiveInstances = this.listAllActiveInstances.length;
1896
+ this.countDeactivatedInstances = this.listDeactivatedInstances.length;
1897
+ this.countErrorInstance = this.listErrorInstance.length;
1898
+ }
1899
+
1900
+ /**
1901
+ * write datapoints for instances list and counts
1902
+ */
1903
+ async writeInstanceDPs() {
1904
+ // List all instances
1905
+ await this.setStateChangedAsync(`adapterAndInstances.listAllInstances`, {
1906
+ val: JSON.stringify(this.listAllInstances),
1907
+ ack: true
1908
+ });
1909
+ await this.setStateChangedAsync(`adapterAndInstances.countAllInstances`, {
1910
+ val: this.countAllInstances,
1911
+ ack: true
1912
+ });
1913
+
1914
+ // List all active instances
1915
+ await this.setStateChangedAsync(`adapterAndInstances.listAllActiveInstances`, {
1916
+ val: JSON.stringify(this.listAllActiveInstances),
1917
+ ack: true
1918
+ });
1919
+ await this.setStateChangedAsync(`adapterAndInstances.countAllActiveInstances`, {
1920
+ val: this.countAllActiveInstances,
1921
+ ack: true
1922
+ });
1923
+
1924
+ // list deactivated instances
1925
+ if (this.countDeactivatedInstances === 0) {
1926
+ this.listDeactivatedInstances = [
1927
+ {
1928
+ [translations.Adapter[this.config.userSelectedLanguage]]: '--none--',
1929
+ [translations.Instance[this.config.userSelectedLanguage]]: '',
1930
+ [translations.Version[this.config.userSelectedLanguage]]: '',
1931
+ [translations.Status[this.config.userSelectedLanguage]]: '',
1932
+ },
1933
+ ];
1934
+ }
1935
+ await this.setStateChangedAsync(`adapterAndInstances.listDeactivatedInstances`, {
1936
+ val: JSON.stringify(this.listDeactivatedInstances),
1937
+ ack: true
1938
+ });
1939
+ await this.setStateChangedAsync(`adapterAndInstances.countDeactivatedInstances`, {
1940
+ val: this.countDeactivatedInstances,
1941
+ ack: true
1942
+ });
1943
+
1944
+ // list error instances
1945
+ if (this.countErrorInstance === 0) {
1946
+ this.listErrorInstance = [
1947
+ {
1948
+ [translations.Adapter[this.config.userSelectedLanguage]]: '--none--',
1949
+ [translations.Instance[this.config.userSelectedLanguage]]: '',
1950
+ [translations.Mode[this.config.userSelectedLanguage]]: '',
1951
+ [translations.Status[this.config.userSelectedLanguage]]: '',
1952
+ },
1953
+ ];
1954
+ }
1955
+ await this.setStateChangedAsync(`adapterAndInstances.listInstancesError`, {
1956
+ val: JSON.stringify(this.listErrorInstance),
1957
+ ack: true
1958
+ });
1959
+ await this.setStateChangedAsync(`adapterAndInstances.countInstancesError`, {
1960
+ val: this.countErrorInstance,
1961
+ ack: true
1962
+ });
1963
+ }
1964
+
1965
+ /**
1966
+ * @param {string} id
1967
+ */
1968
+ async renewAdapterUpdateData(id) {
1969
+ const previousAdapterUpdatesCount = this.countAdapterUpdates;
1970
+
1971
+ // Fetch and process adapter update data
1972
+ await this.getAdapterUpdateData(id);
1973
+ await this.createAdapterUpdateList();
1974
+
1975
+ // Check and send update notification if required
1976
+ if (this.config.checkSendAdapterUpdateMsg && this.countAdapterUpdates > previousAdapterUpdatesCount) {
1977
+ await this.sendStateNotifications('AdapterUpdates', 'updateAdapter', null);
1978
+ }
1979
+
1980
+ // Update instances with available adapter updates
1981
+ for (const instance of this.listInstanceRaw.values()) {
1982
+ const adapterUpdate = this.adapterUpdatesJsonRaw.find((entry) => entry.adapter.toLowerCase() === instance.Adapter.toLowerCase());
1983
+
1984
+ if (adapterUpdate) {
1985
+ instance.updateAvailable = adapterUpdate.newVersion;
1986
+ } else {
1987
+ instance.updateAvailable = ' - ';
1988
+ }
1989
+ }
1990
+ }
1991
+
1992
+ /**
1993
+ * call function on state change, renew data and send messages
1994
+ *
1995
+ * @param {string} id
1996
+ * @param {ioBroker.State} state
1997
+ */
1998
+ async renewInstanceData(id, state) {
1999
+ const instanceID = await this.getInstanceName(id);
2000
+ const instanceData = this.listInstanceRaw.get(instanceID);
2001
+ if (instanceData) {
2002
+ let instanceStatusRaw;
2003
+
2004
+ const checkInstance = async (instanceID, instanceData) => {
2005
+ instanceStatusRaw = await this.setInstanceStatus(instanceData.instanceMode, instanceData.schedule, instanceID);
2006
+ instanceData.isAlive = instanceStatusRaw[0];
2007
+ instanceData.isHealthy = instanceStatusRaw[1];
2008
+ instanceData.status = instanceStatusRaw[2];
2009
+ instanceData.isConnectedHost = instanceStatusRaw[3];
2010
+ instanceData.isConnectedDevice = instanceStatusRaw[4];
2011
+ return;
2012
+ };
2013
+
2014
+ switch (id) {
2015
+ case `system.adapter.${instanceID}.alive`:
2016
+ if (state.val !== instanceData.isAlive) {
2017
+ await checkInstance(instanceID, instanceData);
2018
+ // send message when instance was deactivated
2019
+ if (this.config.checkSendInstanceDeactivatedMsg && !instanceData.isAlive) {
2020
+ if (this.blacklistInstancesNotify.includes(instanceID)) {
2021
+ break;
2022
+ }
2023
+ // Restart-Erkennung: Toleranzzeit abwarten und prüfen ob Instanz schon wieder läuft
2024
+ const restartTolerance = this.userTimeInstancesList.has(instanceID)
2025
+ ? this.userTimeInstancesList.get(instanceID).deactivationTime * 1000
2026
+ : this.config.offlineTimeInstances * 1000;
2027
+
2028
+ this.log.debug(`[renewInstanceData] Instance ${instanceID} went offline - waiting ${restartTolerance}ms to check for restart...`);
2029
+ await this.delay(restartTolerance);
2030
+
2031
+ const aliveAfterWait = await tools.getInitValue(this, `system.adapter.${instanceID}.alive`);
2032
+ if (aliveAfterWait) {
2033
+ // Instanz ist bereits wieder online → war nur ein Neustart
2034
+ this.log.debug(`[renewInstanceData] Instance ${instanceID} is back online after restart. No deactivation notification sent.`);
2035
+ await checkInstance(instanceID, instanceData);
2036
+ break;
2037
+ }
2038
+
2039
+ await this.sendStateNotifications('Instances', 'deactivatedInstance', instanceID);
2040
+ }
2041
+ }
2042
+ break;
2043
+
2044
+ case `system.adapter.${instanceID}.connected`:
2045
+ if (state.val !== instanceData.isConnectedHost && instanceData.isAlive) {
2046
+ await checkInstance(instanceID, instanceData);
2047
+ // send message when instance has an error
2048
+ if (this.config.checkSendInstanceFailedMsg && !instanceData.isHealthy && instanceData.isAlive) {
2049
+ if (this.blacklistInstancesNotify.includes(instanceID)) {
2050
+ return;
2051
+ }
2052
+ await this.sendStateNotifications('Instances', 'errorInstance', instanceID);
2053
+ }
2054
+ }
2055
+ break;
2056
+
2057
+ case `${instanceID}.info.connection`:
2058
+ if (state.val !== instanceData.isConnectedDevice && instanceData.isAlive) {
2059
+ await checkInstance(instanceID, instanceData);
2060
+ // send message when instance has an error
2061
+ if (this.config.checkSendInstanceFailedMsg && !instanceData.isHealthy && instanceData.isAlive) {
2062
+ if (this.blacklistInstancesNotify.includes(instanceID)) {
2063
+ return;
2064
+ }
2065
+ await this.sendStateNotifications('Instances', 'errorInstance', instanceID);
2066
+ }
2067
+ }
2068
+ break;
2069
+ }
2070
+ }
2071
+ }
2072
+
2073
+ /*=============================================
2074
+ = functions to send notifications =
2075
+ =============================================*/
2076
+
2077
+ /**
2078
+ * Notification service
2079
+ *
2080
+ * @param {string} text - Text which should be send
2081
+ * @param silent
2082
+ */
2083
+ async sendNotification(text, silent = false) {
2084
+ // Pushover
2085
+ if (this.config.instancePushover) {
2086
+ try {
2087
+ //first check if instance is living
2088
+ const pushoverAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instancePushover}.alive`);
2089
+
2090
+ if (!pushoverAliveState) {
2091
+ this.log.warn('Pushover instance is not running. Message could not be sent. Please check your instance configuration.');
2092
+ } else {
2093
+ await this.sendToAsync(this.config.instancePushover, 'send', {
2094
+ message: text,
2095
+ title: this.config.titlePushover,
2096
+ device: this.config.devicePushover,
2097
+ user: this.config.userPushover,
2098
+ priority: this.config.prioPushover,
2099
+ });
2100
+ }
2101
+ } catch (error) {
2102
+ this.log.error(`[sendNotification Pushover] - ${error}`);
2103
+ }
2104
+ }
2105
+
2106
+ // Telegram
2107
+ if (this.config.instanceTelegram) {
2108
+ try {
2109
+ //first check if instance is living
2110
+ const telegramAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceTelegram}.alive`);
2111
+
2112
+ if (!telegramAliveState) {
2113
+ this.log.warn('Telegram instance is not running. Message could not be sent. Please check your instance configuration.');
2114
+ } else {
2115
+ await this.sendToAsync(this.config.instanceTelegram, 'send', {
2116
+ text: text,
2117
+ user: this.config.deviceTelegram,
2118
+ chatId: this.config.chatIdTelegram,
2119
+ disable_notification: silent,
2120
+ });
2121
+ }
2122
+ } catch (error) {
2123
+ this.log.error(`[sendNotification Telegram] - ${error}`);
2124
+ }
2125
+ }
2126
+
2127
+ // Whatsapp
2128
+ if (this.config.instanceWhatsapp) {
2129
+ try {
2130
+ //first check if instance is living
2131
+ const whatsappAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceWhatsapp}.alive`);
2132
+
2133
+ if (!whatsappAliveState) {
2134
+ this.log.warn('Whatsapp instance is not running. Message could not be sent. Please check your instance configuration.');
2135
+ } else {
2136
+ await this.sendToAsync(this.config.instanceWhatsapp, 'send', {
2137
+ text: text,
2138
+ phone: this.config.phoneWhatsapp,
2139
+ });
2140
+ }
2141
+ } catch (error) {
2142
+ this.log.error(`[sendNotification Whatsapp] - ${error}`);
2143
+ }
2144
+ }
2145
+
2146
+ // Matrix
2147
+ if (this.config.instanceMatrix) {
2148
+ try {
2149
+ //first check if instance is living
2150
+ const matrixAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceMatrix}.alive`);
2151
+
2152
+ if (!matrixAliveState) {
2153
+ this.log.warn('Matrix instance is not running. Message could not be sent. Please check your instance configuration.');
2154
+ } else {
2155
+ await this.sendToAsync(this.config.instanceMatrix, 'send', {
2156
+ html: `<h1>${this.config.titleMatrix}</h1>`,
2157
+ text: text,
2158
+ });
2159
+ }
2160
+ } catch (error) {
2161
+ this.log.error(`[sendNotification Matrix] - ${error}`);
2162
+ }
2163
+ }
2164
+
2165
+ // Signal
2166
+ if (this.config.instanceSignal) {
2167
+ try {
2168
+ //first check if instance is living
2169
+ const signalAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceSignal}.alive`);
2170
+
2171
+ if (!signalAliveState) {
2172
+ this.log.warn('Signal instance is not running. Message could not be sent. Please check your instance configuration.');
2173
+ } else {
2174
+ await this.sendToAsync(this.config.instanceSignal, 'send', {
2175
+ text: text,
2176
+ phone: this.config.phoneSignal,
2177
+ });
2178
+ }
2179
+ } catch (error) {
2180
+ this.log.error(`[sendNotification Signal] - ${error}`);
2181
+ }
2182
+ }
2183
+
2184
+ // Email
2185
+ if (this.config.instanceEmail) {
2186
+ try {
2187
+ //first check if instance is living
2188
+ const eMailAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceEmail}.alive`);
2189
+
2190
+ if (!eMailAliveState) {
2191
+ this.log.warn('eMail instance is not running. Message could not be sent. Please check your instance configuration.');
2192
+ } else {
2193
+ await this.sendToAsync(this.config.instanceEmail, 'send', {
2194
+ sendTo: this.config.sendToEmail,
2195
+ text: text,
2196
+ subject: this.config.subjectEmail,
2197
+ });
2198
+ }
2199
+ } catch (error) {
2200
+ this.log.error(`[sendNotification eMail] - ${error}`);
2201
+ }
2202
+ }
2203
+
2204
+ // Jarvis Notification
2205
+ if (this.config.instanceJarvis) {
2206
+ try {
2207
+ //first check if instance is living
2208
+ const jarvisAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceJarvis}.alive`);
2209
+
2210
+ if (!jarvisAliveState) {
2211
+ this.log.warn('Jarvis instance is not running. Message could not be sent. Please check your instance configuration.');
2212
+ } else {
2213
+ const jsonText = JSON.stringify(text);
2214
+ await this.setForeignStateAsync(
2215
+ `${this.config.instanceJarvis}.addNotification`,
2216
+ `{"title":"${this.config.titleJarvis} (${this.formatDate(new Date(), 'DD.MM.YYYY - hh:mm:ss')})","message": ${jsonText},"display": "drawer"}`,
2217
+ );
2218
+ }
2219
+ } catch (error) {
2220
+ this.log.error(`[sendNotification Jarvis] - ${error}`);
2221
+ }
2222
+ }
2223
+
2224
+ // Lovelace Notification
2225
+ if (this.config.instanceLovelace) {
2226
+ try {
2227
+ //first check if instance is living
2228
+ const lovelaceAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceLovelace}.alive`);
2229
+
2230
+ if (!lovelaceAliveState) {
2231
+ this.log.warn('Lovelace instance is not running. Message could not be sent. Please check your instance configuration.');
2232
+ } else {
2233
+ const jsonText = JSON.stringify(text);
2234
+ await this.setForeignStateAsync(
2235
+ `${this.config.instanceLovelace}.notifications.add`,
2236
+ `{"message":${jsonText}, "title":"${this.config.titleLovelace} (${this.formatDate(new Date(), 'DD.MM.YYYY - hh:mm:ss')})"}`,
2237
+ );
2238
+ }
2239
+ } catch (error) {
2240
+ this.log.error(`[sendNotification Lovelace] - ${error}`);
2241
+ }
2242
+ }
2243
+
2244
+ // Synochat Notification
2245
+ if (this.config.instanceSynochat) {
2246
+ try {
2247
+ //first check if instance is living
2248
+ const synochatAliveState = await tools.getInitValue(this, `system.adapter.${this.config.instanceSynochat}.alive`);
2249
+
2250
+ if (!synochatAliveState) {
2251
+ this.log.warn('Synochat instance is not running. Message could not be sent. Please check your instance configuration.');
2252
+ } else {
2253
+ if (this.config.channelSynochat !== undefined) {
2254
+ await this.setForeignStateAsync(`${this.config.instanceSynochat}.${this.config.channelSynochat}.message`, text);
2255
+ } else {
2256
+ this.log.warn('Synochat channel is not set. Message could not be sent. Please check your instance configuration.');
2257
+ }
2258
+ }
2259
+ } catch (error) {
2260
+ this.log.error(`[sendNotification Synochat] - ${error}`);
2261
+ }
2262
+ }
2263
+ } // <-- End of sendNotification function
2264
+
2265
+ /*---------- Notifications ----------*/
2266
+ /**
2267
+ * Notifications on state changes
2268
+ *
2269
+ * @param {string} mainType
2270
+ * @param {string} type
2271
+ * @param {object} id
2272
+ * @param silent
2273
+ */
2274
+ async sendStateNotifications(mainType, type, id, silent = false) {
2275
+ if (isUnloaded) {
2276
+ return;
2277
+ }
2278
+ let objectData;
2279
+ let adapterName;
2280
+ let list = '';
2281
+ let message = '';
2282
+
2283
+ if (id !== null) {
2284
+ if (mainType === 'Devices') {
2285
+ objectData = this.listAllDevicesRaw.get(id);
2286
+ adapterName = this.config.showAdapterNameinMsg ? `${objectData.Adapter}: ` : '';
2287
+ } else if (mainType === 'Instances') {
2288
+ objectData = this.listInstanceRaw.get(id);
2289
+ }
2290
+ }
2291
+
2292
+ const setMessage = async (message) => {
2293
+ this.log.info(message);
2294
+ await this.setStateAsync('lastNotification', message, true);
2295
+ await this.sendNotification(message, silent);
2296
+ };
2297
+
2298
+ switch (type) {
2299
+ case 'lowBatDevice':
2300
+ message = `${translations.Device_low_bat_detected[this.config.userSelectedLanguage]}: \n${adapterName} ${objectData.Device} (${objectData.Battery})`;
2301
+ await setMessage(message);
2302
+ break;
2303
+
2304
+ case 'onlineStateDevice':
2305
+ switch (objectData.Status) {
2306
+ case 'Online':
2307
+ message = `${translations.Device_available_again[this.config.userSelectedLanguage]}: \n${adapterName} ${objectData.Device} (${objectData.LastContact})`;
2308
+ break;
2309
+
2310
+ case 'Offline':
2311
+ message = `${translations.Device_not_reachable[this.config.userSelectedLanguage]}: \n${adapterName} ${objectData.Device} (${objectData.LastContact})`;
2312
+ break;
2313
+ }
2314
+ await setMessage(message);
2315
+ break;
2316
+
2317
+ case 'updateDevice':
2318
+ message = `${translations.Device_new_updates[this.config.userSelectedLanguage]}: \n${adapterName} ${objectData.Device}`;
2319
+ await setMessage(message);
2320
+ break;
2321
+
2322
+ case 'updateAdapter':
2323
+ if (this.countAdapterUpdates === 0) {
2324
+ return;
2325
+ }
2326
+
2327
+ objectData = this.listAdapterUpdates;
2328
+ list = '';
2329
+
2330
+ for (const id of objectData) {
2331
+ list = `${list}\n${id[translations.Adapter[this.config.userSelectedLanguage]]}: v${id[translations.Available_Version[this.config.userSelectedLanguage]]}`;
2332
+ }
2333
+
2334
+ message = `${translations.Adapter_new_updates[this.config.userSelectedLanguage]}: ${list}`;
2335
+ await setMessage(message);
2336
+ break;
2337
+
2338
+ case 'errorInstance':
2339
+ case 'deactivatedInstance':
2340
+ message = `${translations.Instance_Watchdog[this.config.userSelectedLanguage]}:\n${id}: ${objectData.status}`;
2341
+ await setMessage(message);
2342
+ break;
2343
+ }
2344
+ }
2345
+
2346
+ /**
2347
+ * Notifications per user defined schedule
2348
+ *
2349
+ * @param {string} type
2350
+ * @param silent
2351
+ */
2352
+ async sendScheduleNotifications(type, silent = false) {
2353
+ if (isUnloaded) {
2354
+ return;
2355
+ }
2356
+
2357
+ const checkDays = [];
2358
+ const dayConfigKeys = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
2359
+ let list = '';
2360
+ let message = '';
2361
+
2362
+ const setMessage = async (message) => {
2363
+ this.log.info(message);
2364
+ await this.setStateAsync('lastNotification', message, true);
2365
+ if (!message.includes('no updates')) {
2366
+ await this.sendNotification(message, silent);
2367
+ }
2368
+ };
2369
+
2370
+ const processDeviceList = (deviceList, property1, property2) => {
2371
+ list = '';
2372
+ for (const id of deviceList) {
2373
+ if (this.blacklistNotify.includes(id.Path)) {
2374
+ continue;
2375
+ }
2376
+ list += `\n${!this.config.showAdapterNameinMsg ? '' : `${id.Adapter}: `}${id[property1]}${property2 ? ` (${id[property2]})` : ''}`;
2377
+ }
2378
+ };
2379
+
2380
+ const processInstanceList = (instanceList, property) => {
2381
+ list = '';
2382
+ for (const id of instanceList) {
2383
+ if (this.blacklistInstancesNotify.includes(id[translations['Instance'][this.config.userSelectedLanguage]])) {
2384
+ continue;
2385
+ }
2386
+ list += `\n${id[translations['Instance'][this.config.userSelectedLanguage]]}${property ? `: ${id[property]}` : ''}`;
2387
+ }
2388
+ };
2389
+
2390
+ const processNotification = async (list, messageType) => {
2391
+ if (list.length === 0) {
2392
+ return;
2393
+ }
2394
+
2395
+ switch (checkDays.length) {
2396
+ case 1:
2397
+ message = `${translations.Weekly_overview[this.config.userSelectedLanguage]} ${translations[messageType][this.config.userSelectedLanguage]}: ${list}`;
2398
+ break;
2399
+ case 7:
2400
+ message = `${translations.Daily_overview[this.config.userSelectedLanguage]} ${translations[messageType][this.config.userSelectedLanguage]}: ${list}`;
2401
+ break;
2402
+ default:
2403
+ message = `${translations.Overview_of[this.config.userSelectedLanguage]} ${translations[messageType][this.config.userSelectedLanguage]}: ${list}`;
2404
+ break;
2405
+ }
2406
+
2407
+ await setMessage(message);
2408
+ };
2409
+
2410
+ switch (type) {
2411
+ case 'lowBatteryDevices':
2412
+ checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`check${day}`] ? index : null)).filter((day) => day !== null));
2413
+
2414
+ if (checkDays.length === 0) {
2415
+ this.log.warn(`No days selected for daily low battery devices message. Please check the instance configuration!`);
2416
+ return;
2417
+ }
2418
+ this.log.debug(`Number of selected days for daily low battery devices message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2419
+
2420
+ schedule.scheduleJob(`1 ${this.config.checkSendBatteryTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2421
+ processDeviceList(this.batteryLowPoweredRaw, 'Device', 'Battery');
2422
+
2423
+ await processNotification(list, 'devices_low_bat');
2424
+ });
2425
+ break;
2426
+
2427
+ case 'offlineDevices':
2428
+ checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkOffline${day}`] ? index : null)).filter((day) => day !== null));
2429
+
2430
+ if (checkDays.length === 0) {
2431
+ this.log.warn(`No days selected for daily offline devices message. Please check the instance configuration!`);
2432
+ return;
2433
+ }
2434
+ this.log.debug(`Number of selected days for daily offline devices message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2435
+
2436
+ schedule.scheduleJob(`2 ${this.config.checkSendOfflineTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2437
+ processDeviceList(this.offlineDevicesRaw, `Device`, 'LastContact');
2438
+
2439
+ await processNotification(list, 'offline_devices');
2440
+ });
2441
+ break;
2442
+
2443
+ case 'updateDevices':
2444
+ checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkUpgrade${day}`] ? index : null)).filter((day) => day !== null));
2445
+
2446
+ if (checkDays.length === 0) {
2447
+ this.log.warn(`No days selected for daily updatable devices message. Please check the instance configuration!`);
2448
+ return;
2449
+ }
2450
+ this.log.debug(`Number of selected days for daily updatable devices message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2451
+
2452
+ schedule.scheduleJob(`3 ${this.config.checkSendUpgradeTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2453
+ processDeviceList(this.upgradableDevicesRaw, 'Device');
2454
+
2455
+ await processNotification(list, 'available_updatable_devices');
2456
+ });
2457
+ break;
2458
+
2459
+ case 'updateAdapter':
2460
+ checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkAdapterUpdate${day}`] ? index : null)).filter((day) => day !== null));
2461
+
2462
+ if (checkDays.length === 0) {
2463
+ this.log.warn(`No days selected for daily adapter update message. Please check the instance configuration!`);
2464
+ return;
2465
+ }
2466
+ this.log.debug(`Number of selected days for daily adapter update message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2467
+
2468
+ schedule.scheduleJob(`4 ${this.config.checkSendAdapterUpdateTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2469
+ list = '';
2470
+ for (const id of this.listAdapterUpdates) {
2471
+ list = `${list}\n${id[translations.Adapter[this.config.userSelectedLanguage]]}: v${id[translations.Available_Version[this.config.userSelectedLanguage]]}`;
2472
+ }
2473
+ await processNotification(list, 'available_adapter_updates');
2474
+ });
2475
+ break;
2476
+
2477
+ case 'errorInstance':
2478
+ checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkFailedInstances${day}`] ? index : null)).filter((day) => day !== null));
2479
+
2480
+ if (checkDays.length === 0) {
2481
+ this.log.warn(`No days selected for daily instance error message. Please check the instance configuration!`);
2482
+ return;
2483
+ }
2484
+ this.log.debug(`Number of selected days for daily instance error message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2485
+
2486
+ schedule.scheduleJob(`5 ${this.config.checkSendInstanceFailedTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2487
+ processInstanceList(this.listErrorInstanceRaw, 'Status');
2488
+
2489
+ await processNotification(list, 'error_instances_msg');
2490
+ });
2491
+ break;
2492
+
2493
+ case 'deactivatedInstance':
2494
+ checkDays.push(...dayConfigKeys.map((day, index) => (this.config[`checkInstanceDeactivated${day}`] ? index : null)).filter((day) => day !== null));
2495
+
2496
+ if (checkDays.length === 0) {
2497
+ this.log.warn(`No days selected for daily instance deactivated message. Please check the instance configuration!`);
2498
+ return;
2499
+ }
2500
+ this.log.debug(`Number of selected days for daily instance deactivated message: ${checkDays.length}. Send Message on: ${checkDays.join(', ')} ...`);
2501
+
2502
+ schedule.scheduleJob(`5 ${this.config.checkSendInstanceDeactivatedTime.split(':').reverse().join(' ')} * * ${checkDays.join(',')}`, async () => {
2503
+ processInstanceList(this.listDeactivatedInstances);
2504
+
2505
+ await processNotification(list, 'deactivated_instances_msg');
2506
+ });
2507
+ break;
2508
+ }
2509
+ }
2510
+
2511
+ async getPreviousCronRun(lastCronRun) {
2512
+ try {
2513
+ let interval;
2514
+ // cron-parser v4: parseExpression() – v5: CronExpressionParser.parse()
2515
+ if (typeof cronParserLib.parseExpression === 'function') {
2516
+ interval = cronParserLib.parseExpression(lastCronRun);
2517
+ } else if (cronParserLib.CronExpressionParser && typeof cronParserLib.CronExpressionParser.parse === 'function') {
2518
+ interval = cronParserLib.CronExpressionParser.parse(lastCronRun);
2519
+ } else {
2520
+ throw new Error('cron-parser: no compatible API found (parseExpression / CronExpressionParser.parse)');
2521
+ }
2522
+ const previous = interval.prev();
2523
+
2524
+ // Differenz in ms seit dem vorherigen Cron-Zeitpunkt
2525
+ return Date.now() - previous.getTime();
2526
+ } catch (error) {
2527
+ this.log.error(`[getPreviousCronRun] - ${error}`);
2528
+ return null;
2529
+ }
2530
+ }
2531
+
2532
+
2533
+ /**
2534
+ * @param {() => void} callback
2535
+ */
2536
+ onUnload(callback) {
2537
+ try {
2538
+ this.log.debug('clearing timeouts');
2539
+
2540
+ isUnloaded = true;
2541
+
2542
+ if (this.refreshDataTimeout) {
2543
+ this.clearTimeout(this.refreshDataTimeout);
2544
+ this.refreshDataTimeout = null;
2545
+ }
2546
+
2547
+ this.log.info('cleaned everything up...');
2548
+
2549
+ callback();
2550
+ } catch (e) {
2551
+ callback();
2552
+ }
2553
+ }
2470
2554
  }
2471
2555
 
2472
2556
  if (require.main !== module) {
2473
- // Export the constructor in compact mode
2474
- /**
2475
- * @param {Partial<utils.AdapterOptions>} [options]
2476
- */
2477
- module.exports = (options) => new DeviceWatcher(options);
2557
+ // Export the constructor in compact mode
2558
+ /**
2559
+ * @param {Partial<utils.AdapterOptions>} [options]
2560
+ */
2561
+ module.exports = (options) => new DeviceWatcher(options);
2478
2562
  } else {
2479
- // otherwise start the instance directly
2480
- new DeviceWatcher();
2563
+ // otherwise start the instance directly
2564
+ new DeviceWatcher();
2481
2565
  }