iobroker.device-watcher 2.15.3 → 2.15.11

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/README.md CHANGED
@@ -191,27 +191,22 @@ This adapter would not have been possible without the great work of Christian Be
191
191
  Placeholder for the next version (at the beginning of the line):
192
192
  ### **WORK IN PROGRESS**
193
193
  -->
194
- ### 2.15.3 (2026-01-10)
195
- * (arteck) fix ping adapter, fritzdec
196
-
197
- ### 2.15.2 (2026-01-10)
198
- * (arteck) fix cronjob response
199
-
200
- ### 2.15.1 (2026-01-10)
201
- * (arteck) fix instance check
202
- * (arteck) fix admin json
203
-
204
- ### 2.15.0 (2026-01-07)
205
- * (arteck) fixed device array
206
- * (arteck) deleted zwave2mqtt Adapter
207
- * (arteck) added zwavews Adapter
208
- * (arteck) Dependencies have been updated
209
- * (arteck) added zwave2mqtt Adapter
210
- * (arteck) fixed all count devices
211
- * (arteck) corrected finder
212
-
213
- ### 2.14.1 (2025-11-13)
214
- * (arteck) corr cronparser
194
+ ### 2.15.11 (2026-05-06)
195
+ * (arteck)
196
+
197
+ ### 2.15.10 (2026-05-06)
198
+ - (copilot) Adapter requires node.js >= 22 now
199
+ * (arteck) fix adapter crash after delete a device
200
+
201
+ ### 2.15.9 (2026-04-22)
202
+ * (arteck) new xsense (v. 0.4.0) structure, plz update before
203
+
204
+ ### 2.15.8 (2026-04-18)
205
+ * (arteck) fix cronParserLib.parseExpression message
206
+
207
+ ### 2.15.7 (2026-04-18)
208
+ * (arteck) fix matter
209
+ * (arteck) fix cronParserLib.parseExpression message
215
210
 
216
211
  ## License
217
212
 
@@ -78,7 +78,7 @@
78
78
  attr: 'adapterKey',
79
79
  width: '1%',
80
80
  disabled: 'true',
81
- hidden: true,
81
+ hidden: 'true',
82
82
  },
83
83
  ],
84
84
  },
package/io-package.json CHANGED
@@ -1,59 +1,98 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "device-watcher",
4
- "version": "2.15.3",
4
+ "version": "2.15.11",
5
5
  "news": {
6
- "2.15.3": {
7
- "en": "fix ping adapter, fritzdec",
8
- "de": "fixierpistole, fritzdec",
9
- "ru": "адаптер для фиксации пингов, fritzdec",
10
- "pt": "corrigir adaptador de ping, fritzdec",
11
- "nl": "fix ping adapter, fritzdec",
12
- "fr": "adaptateur de fixation de ping, fritzdec",
13
- "it": "correzione adattatore ping, fritzdec",
14
- "es": "adaptador de fijación, fritzdec",
15
- "pl": "fix adapter ping, fritzdec",
16
- "uk": "фіксатор ping, fritzdec",
17
- "zh-cn": "修补 ping 适配器, fritzdec"
18
- },
19
- "2.15.2": {
20
- "en": "fix cronjob response",
21
- "de": "fix cronjob antwort",
22
- "ru": "реакция cronjob",
23
- "pt": "corrigir a resposta do cronjob",
24
- "nl": "cronjob respons repareren",
25
- "fr": "corriger la réponse cronjob",
26
- "it": "correzione di cronjob risposta",
27
- "es": "solución respuesta cronjob",
28
- "pl": "naprawić odpowiedź cronjob",
29
- "uk": "виправити cronjob відповідь",
30
- "zh-cn": "固定 curonjob 响应"
31
- },
32
- "2.15.1": {
33
- "en": "fix instance check\nfix admin json",
34
- "de": "fehlerbehebung\nadmin json",
35
- "ru": "установить проверку\nисполнитель: admin json",
36
- "pt": "corrigir verificação de instância\ncorrigir o administrador json",
37
- "nl": "fix instantiecontrole\nfix admin json",
38
- "fr": "correction de la vérification de l'instance\nréparer admin json",
39
- "it": "verifica dell'istanza di correzione\ncorrezione admin json",
40
- "es": "control de instancia de fijación\nadmin json",
41
- "pl": "fix instance check\nfix admin json",
42
- "uk": "перевірка екземпляра\nвиправлено admin json",
43
- "zh-cn": "修补实例检查\n固定管理员json"
44
- },
45
- "2.15.0": {
46
- "en": "fixed device array\ndeleted zwave2mqtt Adapter\nadded zwavews Adapter\nDependencies have been updated\nadded zwave2mqtt Adapter\nfixed all count devices\ncorrected finder",
47
- "de": "Geräteanordnung bereinigt\nzwave2mqt Adapter gelöscht\nzwavews Adapter hinzugefügt\nAbhängigkeiten wurden aktualisiert\nzwave2mqtt Adapter hinzugefügt\nalle Zählgeräte überarbeitet\nfinder korrigiert",
48
- "ru": "фиксированный массив устройств\nудаленный zwave2mqtt Адаптер\nдобавить zwavews Адаптер\nЗависимости были обновлены\nдобавить zwave2mqtt Адаптер\nфиксированные все счетные устройства\nисправленный искатель",
49
- "pt": "arranjo fixo do dispositivo\napagado zwave2mqtt Adaptador\nzwavews adicionados Adaptador\nAs dependências foram atualizadas\nadicionado zwave2mqtt Adaptador\nfixou todos os dispositivos de contagem\nlocalizador corrigido",
50
- "nl": "vaste apparaatarray\nverwijderde zwave2mqtt Adapter\nzwavews toegevoegd Adapter\nAfhankelijkheden zijn bijgewerkt\nzwave2mqtt toegevoegd Adapter\nvaste alle telapparaten\ngecorrigeerde zoeker",
51
- "fr": "tableau de périphériques fixes\nsupprimé zwave2mqtt Adaptateur\nzwavews ajoutés Adaptateur\nLes dépendances ont été actualisées\najouté zwave2mqtt Adaptateur\nfixe tous les appareils de comptage\ncorrecteur",
52
- "it": "array di dispositivi fissi\ncancellato zwave2mqt Adattatore\naggiunto zwavews Adattatore\nLe dipendenze sono state aggiornate\naggiunto zwave2mqt Adattatore\nfissi tutti i dispositivi di conteggio\ncercatore corretto",
53
- "es": "array de dispositivo fijo\nborrado zwave2mqt Adaptador\nañadido zwavews Adaptador\nSe han actualizado las dependencias\nzwave2mqt Adaptador\nfijo todos los dispositivos de cuenta\nbuscador corregido",
54
- "pl": "stała tablica urządzeń\nusunięty zwave2mqtt Adapter\ndodane zwavews Adapter\nZaktualizowano zależności\ndodane zwave2mqtt Adapter\nwszystkie urządzenia liczące\npoprawiony wykrywacz",
55
- "uk": "фіксований масив пристрою\nвидалено zwave2mqtt Адаптери\nдодано zwavews Адаптери\nЗалежність було оновлено\nдодано zwave2mqtt Адаптери\nфіксовані всі пристрої підрахунку\nвиправлений пошук",
56
- "zh-cn": "固定设备阵列\n删除 zwave2mqtt 适配器\n添加z波长 适配器\n依赖关系已更新\n添加 zwave2mqtt 适配器\n固定所有计数设备\n更正的查找者"
6
+ "2.15.11": {
7
+ "en": "",
8
+ "de": "",
9
+ "ru": "",
10
+ "pt": "",
11
+ "nl": "",
12
+ "fr": "",
13
+ "it": "",
14
+ "es": "",
15
+ "pl": "",
16
+ "uk": "",
17
+ "zh-cn": ""
18
+ },
19
+ "2.15.10": {
20
+ "en": "Adapter requires node.js >= 22 now\nfix adapter crash after delete a device",
21
+ "de": "Adapter benötigt node.js >= 22 jetzt\nfix adapter crash nach dem löschen eines geräts",
22
+ "ru": "Адаптер требует node.js >= 22 сейчас\nисправить сбой адаптера после удаления устройства",
23
+ "pt": "Adaptador requer nod.js >= 22 agora\ncorrigir falha do adaptador após apagar um dispositivo",
24
+ "nl": "Voor de adapter zijn node.js < 22 nu nodig\nfix adapter crash na het verwijderen van een apparaat",
25
+ "fr": "Adaptateur nécessite node.js >= 22 maintenant\ncorrection du crash de l'adaptateur après suppression d'un périphérique",
26
+ "it": "Adattatore richiede node.js >= 22 ora\nfissare l'arresto dell'adattatore dopo eliminare un dispositivo",
27
+ "es": "Adaptador requiere node.js ю= 22 ahora\najuste de bloqueo del adaptador después de eliminar un dispositivo",
28
+ "pl": "Adapter wymaga node.js > = 22\nnaprawić awarię adaptera po usunięciu urządzenia",
29
+ "uk": "Адаптер вимагає node.js >= 22 тепер\nвиправлено аварійний перехід після видалення пристрою",
30
+ "zh-cn": "适配器需要节点.js 现在22\n删除设备后修复适配器崩溃"
31
+ },
32
+ "2.15.9": {
33
+ "en": "new xsense (v. 0.4.0) structure, plz update before",
34
+ "de": "neue xsense (v. 0.4.0) struktur, plz update vor",
35
+ "ru": "новая структура xsense (v. 0.4.0), обновление plz до",
36
+ "pt": "nova estrutura xsense (v. 0.4.0), atualização plz antes",
37
+ "nl": "nieuwe xsense (v. 0.4.0) structuur, plz update voor",
38
+ "fr": "nouvelle structure xsense (v. 0.4.0), mise à jour plz",
39
+ "it": "nuova struttura xsense (v. 0.4.0), aggiornamento plz prima",
40
+ "es": "nueva estructura xsense (v. 0.4.0)",
41
+ "pl": "nowa struktura xsense (v. 0.4.0), aktualizacja plz przed",
42
+ "uk": "новий xsense (v. 0.4.0) структура, оновлення plz перед",
43
+ "zh-cn": "新的 xsense (v. 04. 0) 结构, plz 更新前"
44
+ },
45
+ "2.15.8": {
46
+ "en": "fix cronParserLib.parseExpression message",
47
+ "de": "cronParserLib.parseExpressionsnachricht",
48
+ "ru": "исправить сообщение cronParserLib.parse",
49
+ "pt": "corrigir cronParserLib.parseExpression message",
50
+ "nl": "fix cronParserLib.parseExpressiebericht",
51
+ "fr": "correction du message cronParserLib.parseExpression",
52
+ "it": "correzione cronParserLib.parseExpression messaggio",
53
+ "es": "fijar cronParserLib.parse",
54
+ "pl": "naprawić wiadomość cronParserLib.parseExpression",
55
+ "uk": "виправлено cronParserLib.parseExpression повідомлення",
56
+ "zh-cn": "修补 cronParserLib.parsepression 信件"
57
+ },
58
+ "2.15.7": {
59
+ "en": "fix matter \n",
60
+ "de": "fixkosten\n",
61
+ "ru": "исправлять\n",
62
+ "pt": "corrigir a matéria\n",
63
+ "nl": "fix materie\n",
64
+ "fr": "fixer la matière\n",
65
+ "it": "risolvere la questione\n",
66
+ "es": "arregla la materia\n",
67
+ "pl": "materia stabilna\n",
68
+ "uk": "фіксувати матерію\n",
69
+ "zh-cn": "固定事项\n"
70
+ },
71
+ "2.15.6": {
72
+ "en": "Adapter requires admin >= 7.7.22 now\nfix instanz restart\nfix cronParserLib.parseExpression message\nfix group in zigbee2mqtt",
73
+ "de": "Adapter benötigt admin >= 7.7.22 jetzt\ninstanz restart\ncronParserLib.parseExpressionsnachricht\nfix group in zigbee2mqtt",
74
+ "ru": "Адаптер требует администратора >= 7.7.22\nвосстановление instanz\nисправить сообщение cronParserLib.parse\nфиксированная группа в zigbee2mqtt",
75
+ "pt": "Adaptador requer admin >= 7.7.22 agora\ncorrigir instanz reiniciar\ncorrigir cronParserLib.parseExpression message\ngrupo de correção em zigbee2mqtt",
76
+ "nl": "Adapter vereist admin < 7.7.22 nu\nfix instanz herstart\nfix cronParserLib.parseExpressiebericht\nfix groep in zigbee2mqtt",
77
+ "fr": "Adaptateur nécessite admin >= 7.7.22 maintenant\ncorrection du redémarrage de l'instanz\ncorrection du message cronParserLib.parseExpression\nfixer le groupe dans zigbee2mqtt",
78
+ "it": "Adattatore richiede admin >= 7.7.22 ora\nfix instanz riavvio\ncorrezione cronParserLib.parseExpression messaggio\ngruppo di correzione in zigbee2mqt",
79
+ "es": "El adaptador requiere administrador= 7.7.22 ahora\nfijación instanz restart\nfijar cronParserLib.parse\ngrupo de fijación en zigbee2mqt",
80
+ "pl": "Adapter wymaga admin > = 7.7.22\nfix instanz restart\nnaprawić wiadomość cronParserLib.parseExpression\nfix group in zigbee2mqtt",
81
+ "uk": "Адаптер вимагає адмін >= 7.7.22 тепер\nвиправити instanz перезавантаження\nвиправлено cronParserLib.parseExpression повідомлення\nфіксувати групу в zigbee2mqtt",
82
+ "zh-cn": "适任者需要管理员 \\ 7.7.22 现在\n修复即时状态重新启动\n修补 cronParserLib.parsepression 信件\n以zigbee2mqtt为单位的固定组"
83
+ },
84
+ "2.15.5": {
85
+ "en": "fix admin",
86
+ "de": "admin",
87
+ "ru": "исправить admin",
88
+ "pt": "corrigir administrador",
89
+ "nl": "fix admin",
90
+ "fr": "réparer l'administration",
91
+ "it": "fix admin",
92
+ "es": "admin",
93
+ "pl": "fix admin",
94
+ "uk": "адмін",
95
+ "zh-cn": "固定管理员"
57
96
  }
58
97
  },
59
98
  "titleLang": {
@@ -137,7 +176,7 @@
137
176
  ],
138
177
  "globalDependencies": [
139
178
  {
140
- "admin": ">=7.6.17"
179
+ "admin": ">=7.7.22"
141
180
  }
142
181
  ],
143
182
  "messages": [
@@ -374,15 +374,18 @@ const adapterArray = {
374
374
  },
375
375
  matter: {
376
376
  adapterKey: 'matterDevices',
377
- selektor: 'matter.*.online',
377
+ // Selektor: matter.0.controller.<nodeId>.info.connection (boolean, true = online)
378
+ selektor: 'matter.*.controller.*.info.connection',
379
+ timeSelector: '.info.connection',
378
380
  adapterID: 'matter',
379
- adapter: 'matter',
381
+ adapter: 'Matter',
380
382
  rssiState: 'none',
381
- battery: '.BATTERY',
382
- reach: '.connection',
383
- isLowBat: '.LOWBAT',
383
+ battery: 'none',
384
+ battery2: 'none',
385
+ reach: '.info.connection',
386
+ isLowBat: 'none',
384
387
  id: 'none',
385
- upgrade: 'none',
388
+ upgrade: '.info.updateAvailable',
386
389
  },
387
390
  maxcube: {
388
391
  adapterKey: 'maxcubeDevices',
@@ -512,6 +515,7 @@ const adapterArray = {
512
515
  battery: 'none',
513
516
  reach: '.alive',
514
517
  isLowBat: 'none',
518
+ upgrade: 'none',
515
519
  },
516
520
  proxmox: {
517
521
  adapterKey: 'proxmoxDevices',
@@ -737,7 +741,7 @@ const adapterArray = {
737
741
  },
738
742
  xsense: {
739
743
  adapterKey: 'xsenseDevices',
740
- selektor: 'xsense.*.online',
744
+ selektor: 'xsense.*.*.online',
741
745
  timeSelector: '.online',
742
746
  adapterID: 'xsense',
743
747
  adapter: 'XSense',
@@ -772,8 +776,8 @@ const adapterArray = {
772
776
  },
773
777
  zigbee2MQTT: {
774
778
  adapterKey: 'zigbee2mqttDevices',
775
- selektor: 'zigbee2mqtt.*.link_quality',
776
- timeSelector: '.link_quality',
779
+ selektor: 'zigbee2mqtt.*.available',
780
+ timeSelector: '.last_seen',
777
781
  adapterID: 'zigbee2MQTT',
778
782
  adapter: 'Zigbee2MQTT',
779
783
  battery: '.battery',
@@ -793,7 +797,7 @@ const adapterArray = {
793
797
  rssiState: 'none',
794
798
  isLowBat: '.Battery.isLow',
795
799
  },
796
- zwavews: {
800
+ zwavews: {
797
801
  adapterKey: 'zwavewsDevices',
798
802
  selektor: 'zwavews.*.ready',
799
803
  timeSelector: '.status',
package/lib/crud.js CHANGED
@@ -1071,6 +1071,44 @@ async function createData(adaptr, i) {
1071
1071
  if (id.endsWith('.')) {
1072
1072
  continue;
1073
1073
  }
1074
+
1075
+ // matter: nur matter.<inst>.controller.<nodeId>.info.connection zulassen
1076
+ if (adapterID === 'matter') {
1077
+ const parts = id.split('.');
1078
+ if (parts.length !== 6 || parts[2] !== 'controller' || parts[4] !== 'info' || parts[5] !== 'connection') {
1079
+ continue;
1080
+ }
1081
+ }
1082
+
1083
+ // zigbee2MQTT: Bridge/Coordinator/System-Objekte ausfiltern (kein echter Gerätepfad)
1084
+ if (adapterID === 'zigbee2MQTT') {
1085
+ const parts = id.split('.');
1086
+ // Pfad muss mindestens 4 Teile haben: zigbee2mqtt.0.<device>.<state>
1087
+ if (parts.length < 4) {
1088
+ continue;
1089
+ }
1090
+ // Bridge und System-Objekte ausschließen
1091
+ const deviceSegment = parts[2].toLowerCase();
1092
+ if (deviceSegment === 'bridge' || deviceSegment === 'coordinator' || deviceSegment === 'info' || deviceSegment === 'groups') {
1093
+ continue;
1094
+ }
1095
+ // Zigbee2MQTT Gruppen ausschließen: Gruppen haben native.type === 'group'
1096
+ // Gruppen liegen direkt auf Instanz-Ebene z.B. zigbee2mqtt.0.büro
1097
+ const currDevStr = id.slice(0, id.lastIndexOf('.'));
1098
+ const deviceObj = await adaptr.getForeignObjectAsync(currDevStr);
1099
+ if (deviceObj && deviceObj.native && deviceObj.native.type === 'group') {
1100
+ adaptr.log.debug(`[createData zigbee2MQTT] Skipping group (native.type): ${currDevStr}`);
1101
+ continue;
1102
+ }
1103
+ // Fallback: Gruppen haben weder last_seen noch link_quality → kein echtes Gerät
1104
+ const hasLastSeen = await tools.getInitValue(adaptr, `${currDevStr}.last_seen`);
1105
+ const hasLinkQuality = await tools.getInitValue(adaptr, `${currDevStr}.link_quality`);
1106
+ if (hasLastSeen === undefined && hasLinkQuality === undefined) {
1107
+ adaptr.log.debug(`[createData zigbee2MQTT] Skipping group (no last_seen/link_quality): ${currDevStr}`);
1108
+ continue;
1109
+ }
1110
+ }
1111
+
1074
1112
  const mainSelector = id;
1075
1113
 
1076
1114
  /*=============================================
@@ -1192,17 +1230,39 @@ async function createData(adaptr, i) {
1192
1230
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1193
1231
  break;
1194
1232
 
1233
+ case 'matter': {
1234
+ // matter: Batterie liegt unter shortCurrDeviceString.PowerSource-N.BATTERY
1235
+ // N ist variabel -> dynamisch per getObjectViewAsync suchen
1236
+ const matterBattPrefix = `${shortCurrDeviceString}.PowerSource-`;
1237
+ const matterBattList = await adaptr.getObjectViewAsync('system', 'state', {
1238
+ startkey: `${matterBattPrefix}`,
1239
+ endkey: `${matterBattPrefix}\u9999`,
1240
+ });
1241
+ // ersten BATTERY-State nehmen
1242
+ const matterBattRow = matterBattList.rows.find((r) => r.id.endsWith('.BATTERY'));
1243
+ if (matterBattRow) {
1244
+ deviceBatteryStateDP = matterBattRow.id;
1245
+ deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1246
+ }
1247
+ break;
1248
+ }
1249
+
1195
1250
  default:
1196
1251
  deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery;
1197
- deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1198
-
1199
- if (deviceBatteryState === undefined) {
1200
- deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery2;
1252
+ if (adaptr.selAdapter[i].battery === 'none') {
1253
+ // adapter has no battery percentage DP (e.g. hmiP) – skip lookups
1254
+ deviceBatteryState = undefined;
1255
+ } else {
1201
1256
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1202
1257
 
1203
1258
  if (deviceBatteryState === undefined) {
1204
- deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery3;
1259
+ deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery2;
1205
1260
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1261
+
1262
+ if (deviceBatteryState === undefined) {
1263
+ deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery3;
1264
+ deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1265
+ }
1206
1266
  }
1207
1267
  }
1208
1268
  break;
@@ -1221,6 +1281,21 @@ async function createData(adaptr, i) {
1221
1281
  deviceLowBatState = await tools.getInitValue(adaptr, isLowBatDP);
1222
1282
  }
1223
1283
  }
1284
+
1285
+ // matter: LOWBAT dynamisch über PowerSource-N.LOWBAT suchen
1286
+ if (adapterID === 'matter' && deviceLowBatState === undefined) {
1287
+ const matterLowBatPrefix = `${shortCurrDeviceString}.PowerSource-`;
1288
+ const matterLowBatList = await adaptr.getObjectViewAsync('system', 'state', {
1289
+ startkey: matterLowBatPrefix,
1290
+ endkey: `${matterLowBatPrefix}\u9999`,
1291
+ });
1292
+ const matterLowBatRow = matterLowBatList.rows.find((r) => r.id.endsWith('.LOWBAT'));
1293
+ if (matterLowBatRow) {
1294
+ isLowBatDP = matterLowBatRow.id;
1295
+ deviceLowBatState = await tools.getInitValue(adaptr, isLowBatDP);
1296
+ }
1297
+ }
1298
+
1224
1299
  if (deviceLowBatState === undefined) {
1225
1300
  isLowBatDP = 'none';
1226
1301
  }
@@ -1229,9 +1304,15 @@ async function createData(adaptr, i) {
1229
1304
  faultReportingState = await tools.getInitValue(adaptr, faultReportingDP);
1230
1305
 
1231
1306
  //subscribe to states
1232
- adaptr.subscribeForeignStates(deviceBatteryStateDP);
1233
- adaptr.subscribeForeignStates(isLowBatDP);
1234
- adaptr.subscribeForeignStates(faultReportingDP);
1307
+ if (!deviceBatteryStateDP.endsWith('undefined')) {
1308
+ adaptr.subscribeForeignStates(deviceBatteryStateDP);
1309
+ }
1310
+ if (!isLowBatDP.endsWith('undefined') && isLowBatDP !== 'none') {
1311
+ adaptr.subscribeForeignStates(isLowBatDP);
1312
+ }
1313
+ if (!faultReportingDP.endsWith('undefined')) {
1314
+ adaptr.subscribeForeignStates(faultReportingDP);
1315
+ }
1235
1316
 
1236
1317
  const batteryData = await adaptr.getBatteryData(deviceBatteryState, deviceLowBatState, faultReportingState, adapterID);
1237
1318
 
@@ -1313,7 +1394,13 @@ async function createData(adaptr, i) {
1313
1394
  isUpgradable = await adaptr.checkDeviceUpdate(adapterID, deviceUpdateSelector);
1314
1395
  }
1315
1396
 
1316
- adaptr.subscribeForeignStates(deviceUpdateDP);
1397
+ if (!deviceUpdateDP.endsWith('undefined')) {
1398
+ if (!deviceUpdateDP.endsWith('none')) {
1399
+ if (!deviceUpdateDP.endsWith('null')) {
1400
+ adaptr.subscribeForeignStates(deviceUpdateDP);
1401
+ }
1402
+ }
1403
+ }
1317
1404
  }
1318
1405
 
1319
1406
  /*=============================================
package/lib/tools.js CHANGED
@@ -11,7 +11,15 @@ async function isDisabledDevice(adaptr, treeDP) {
11
11
 
12
12
  const device = await adaptr.getForeignObject(treeDP);
13
13
 
14
- if (device.native.deviceRemoved == true || device.common.desc.includes('disabled') || device.common.desc.includes('Deaktiviert') || device.common.desc.includes('disabled')) {
14
+ if (!device || !device.common) {
15
+ return isDisabled;
16
+ }
17
+
18
+ const desc = device.common.desc ?? '';
19
+ const descStr = typeof desc === 'object' ? JSON.stringify(desc) : String(desc);
20
+ const deviceRemoved = device.native && device.native.deviceRemoved === true;
21
+
22
+ if (deviceRemoved || descStr.includes('disabled') || descStr.includes('Deaktiviert')) {
15
23
  isDisabled = true;
16
24
  }
17
25
  return isDisabled;
@@ -114,11 +122,19 @@ async function countDevices(adaptr) {
114
122
  */
115
123
  async function checkLastContact(adaptr) {
116
124
  for (const [deviceID, deviceData] of adaptr.listAllDevicesRaw.entries()) {
117
- if (deviceData.instanceDeviceConnected !== false && deviceData.instanceDeviceConnected != undefined) {
118
- deviceData.UnreachState = await this.getInitValue(adaptr, deviceData.UnreachDP);
125
+ if (deviceData.instanceDeviceConnected !== false && deviceData.instanceDeviceConnected !== undefined) {
126
+ deviceData.UnreachState = await getInitValue(adaptr, deviceData.UnreachDP);
119
127
 
120
128
  const gefundenerAdapter = Object.values(adapterArray).find((adapter) => adapter.adapterID === deviceData.adapterID);
129
+ if (!gefundenerAdapter) {
130
+ adaptr.log.warn(`[checkLastContact] - adapter not found for adapterID: ${deviceData.adapterID}, skipping device ${deviceID}`);
131
+ continue;
132
+ }
121
133
  const silentEnabled = Object.values(adaptr.config.tableDevices).find((adapter) => adapter.adapterKey === gefundenerAdapter.adapterKey);
134
+ if (!silentEnabled) {
135
+ adaptr.log.warn(`[checkLastContact] - device config not found for adapterKey: ${gefundenerAdapter.adapterKey}, skipping device ${deviceID}`);
136
+ continue;
137
+ }
122
138
 
123
139
  const oldContactState = deviceData.Status;
124
140
 
@@ -138,7 +154,7 @@ async function checkLastContact(adaptr) {
138
154
  }
139
155
  if (adaptr.config.checkSendOfflineMsg && oldContactState !== deviceData.Status && !adaptr.blacklistNotify.includes(deviceData.Path)) {
140
156
  // check if the generally deviceData connected state is for a while true
141
- if (await this.getTimestampConnectionDP(adaptr, deviceData.instanceDeviceConnectionDP, 50000)) {
157
+ if (await getTimestampConnectionDP(adaptr, deviceData.instanceDeviceConnectionDP, 50000)) {
142
158
  await adaptr.sendStateNotifications('Devices', 'onlineStateDevice', deviceID, silentEnabled.telegramSilent);
143
159
  }
144
160
  }
@@ -582,7 +582,7 @@ const translations = {
582
582
  'zh-cn': '链接质量设备',
583
583
  },
584
584
  low: {
585
- en: 'low',
585
+ en: 'Low',
586
586
  de: 'Niedrige',
587
587
  ru: 'низкая',
588
588
  pt: 'baixa',
package/main.js CHANGED
@@ -118,7 +118,7 @@ class DeviceWatcher extends utils.Adapter {
118
118
  // create list with enabled adapters for monitor devices
119
119
  for (const device of Object.values(this.config.tableDevices)) {
120
120
  if (device.enabled) {
121
- for (const [adapterName, adapter] of Object.entries(adapterArray)) {
121
+ for (const [_adapterName, adapter] of Object.entries(adapterArray)) {
122
122
  if (String(adapter.adapterKey).toLowerCase() === String(device.adapterKey).toLowerCase()) {
123
123
  this.selAdapter.push(adapter);
124
124
  this.adapterSelected.push(adapter.adapterKey);
@@ -325,6 +325,14 @@ class DeviceWatcher extends utils.Adapter {
325
325
  if (this.listAllDevicesRaw.has(id)) {
326
326
  this.listAllDevicesRaw.delete(id);
327
327
  }
328
+ // also remove all child devices if a parent/adapter object was deleted
329
+ const idPrefix = `${id }.`;
330
+ for (const key of this.listAllDevicesRaw.keys()) {
331
+ if (key.startsWith(idPrefix)) {
332
+ this.log.debug(`[onObjectChange] removing child device from map: ${key}`);
333
+ this.listAllDevicesRaw.delete(key);
334
+ }
335
+ }
328
336
 
329
337
  //unsubscribe of Objects and states
330
338
  this.unsubscribeForeignObjects(id);
@@ -450,6 +458,9 @@ class DeviceWatcher extends utils.Adapter {
450
458
  }
451
459
  }
452
460
  break;
461
+ default:
462
+ this.log.warn(`[onMessage] Unknown command: ${obj.command}`);
463
+ break;
453
464
  }
454
465
  }
455
466
 
@@ -531,6 +542,7 @@ class DeviceWatcher extends utils.Adapter {
531
542
  // Get ID with short currDeviceString from objectjson
532
543
  case 'hueExt':
533
544
  case 'hmrpc':
545
+ case 'matter':
534
546
  case 'nukiExt':
535
547
  case 'wled':
536
548
  case 'mqttNuki':
@@ -740,10 +752,16 @@ class DeviceWatcher extends utils.Adapter {
740
752
  default:
741
753
  if (deviceBatteryState === undefined) {
742
754
  if (deviceLowBatState !== undefined) {
743
- if (deviceLowBatState !== true && deviceLowBatState !== 'NORMAL' && deviceLowBatState !== 1) {
755
+ // Explicit OK states: false, 'NORMAL', 0 (some adapters use 0=ok)
756
+ if (
757
+ deviceLowBatState === false ||
758
+ deviceLowBatState === 'NORMAL' ||
759
+ deviceLowBatState === 0
760
+ ) {
744
761
  batteryHealth = 'ok';
745
762
  isBatteryDevice = true;
746
- } else if (deviceLowBatState !== true) {
763
+ } else {
764
+ // true, 1, any other string != 'NORMAL' → low
747
765
  batteryHealth = 'low';
748
766
  isBatteryDevice = true;
749
767
  }
@@ -790,11 +808,14 @@ class DeviceWatcher extends utils.Adapter {
790
808
  }
791
809
  break;
792
810
  default:
793
- if (typeof deviceLowBatState === 'number' && deviceLowBatState === 0) {
811
+ if (typeof deviceLowBatState === 'boolean' && deviceLowBatState === true) {
812
+ // true = low bat
794
813
  lowBatIndicator = true;
795
814
  } else if (typeof deviceLowBatState === 'string' && deviceLowBatState !== 'NORMAL') {
815
+ // any string other than 'NORMAL' = low bat
796
816
  lowBatIndicator = true;
797
- } else if (typeof deviceLowBatState === 'boolean' && deviceLowBatState) {
817
+ } else if (typeof deviceLowBatState === 'number' && deviceLowBatState === 1) {
818
+ // 1 = low bat (0 = ok), consistent with getBatteryData
798
819
  lowBatIndicator = true;
799
820
  }
800
821
  }
@@ -813,20 +834,16 @@ class DeviceWatcher extends utils.Adapter {
813
834
  * @param {object} selector - Selector
814
835
  */
815
836
  async getLastContact(selector) {
816
- const lastContact = tools.getTimestamp(selector); // z. B. Differenz in Sekunden?
817
-
818
- let lastContactString = `${this.formatDate(new Date(selector), 'hh:mm:ss')}`;
837
+ const lastContact = tools.getTimestamp(selector);
819
838
 
820
- // Falls du die vergangene Zeit in Sekunden anzeigen willst:
821
- if (Math.round(lastContact) >= 0) {
822
- lastContactString = `${Math.round(lastContact)} ${translations.secs[this.config.userSelectedLanguage]}`;
823
- }
839
+ let lastContactString;
824
840
 
825
- // Optional: Wenn du ab einem bestimmten Wert lieber Minuten oder Stunden willst
826
841
  if (lastContact >= 3600) {
827
842
  lastContactString = `${(lastContact / 3600).toFixed(1)} ${translations.hours[this.config.userSelectedLanguage]}`;
828
- } else {
843
+ } else if (lastContact >= 60) {
829
844
  lastContactString = `${Math.round(lastContact / 60)} ${translations.minits[this.config.userSelectedLanguage]}`;
845
+ } else {
846
+ lastContactString = `${Math.round(lastContact)} ${translations.secs[this.config.userSelectedLanguage]}`;
830
847
  }
831
848
 
832
849
  return lastContactString;
@@ -842,7 +859,7 @@ class DeviceWatcher extends utils.Adapter {
842
859
  const deviceTimeSelector = await this.getForeignStateAsync(timeSelector);
843
860
  const deviceUnreachSelector = await this.getForeignStateAsync(treeDP);
844
861
 
845
- const lastDeviceUnreachStateChange = deviceUnreachSelector != undefined ? tools.getTimestamp(deviceUnreachSelector.lc) : tools.getTimestamp(timeSelector.ts);
862
+ const lastDeviceUnreachStateChange = deviceUnreachSelector?.lc != null ? tools.getTimestamp(deviceUnreachSelector.lc) : tools.getTimestamp(deviceTimeSelector?.ts ?? Date.now());
846
863
 
847
864
  // ignore disabled device from zigbee2MQTT
848
865
  if (adapterID === 'zigbee2MQTT') {
@@ -875,7 +892,15 @@ class DeviceWatcher extends utils.Adapter {
875
892
  }
876
893
 
877
894
  const gefundenerAdapter = Object.values(adapterArray).find((adapter) => adapter.adapterID === adapterID);
895
+ if (!gefundenerAdapter) {
896
+ this.log.warn(`[getOnlineState] - adapter not found in adapterArray for adapterID: ${adapterID}`);
897
+ return [lastContactString ?? ' - ', deviceState, linkQualitySet];
898
+ }
878
899
  const device = Object.values(this.config.tableDevices).find((adapter) => adapter.adapterKey === gefundenerAdapter.adapterKey);
900
+ if (!device) {
901
+ this.log.warn(`[getOnlineState] - device config not found for adapterKey: ${gefundenerAdapter.adapterKey}`);
902
+ return [lastContactString ?? ' - ', deviceState, linkQualitySet];
903
+ }
879
904
  const maxSecondDevicesOffline = device.maxSecondDevicesOffline;
880
905
 
881
906
  switch (adapterID) {
@@ -931,6 +956,7 @@ class DeviceWatcher extends utils.Adapter {
931
956
  case 'apcups':
932
957
  case 'hue':
933
958
  case 'hueExt':
959
+ case 'matter':
934
960
  case 'ping':
935
961
  case 'deconz':
936
962
  case 'shelly':
@@ -1087,7 +1113,7 @@ class DeviceWatcher extends utils.Adapter {
1087
1113
  });
1088
1114
 
1089
1115
  // LinkQuality lists
1090
- if (device.SignalStrength != ' - ') {
1116
+ if (device.SignalStrength !== ' - ') {
1091
1117
  this.linkQualityDevices.push({
1092
1118
  [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1093
1119
  [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
@@ -1138,7 +1164,6 @@ class DeviceWatcher extends utils.Adapter {
1138
1164
  * @param {ioBroker.State} state
1139
1165
  */
1140
1166
  async renewDeviceData(id, state) {
1141
- const regex = /^([^.]+\.\d+\.[^.]+)/;
1142
1167
  let batteryData;
1143
1168
  let signalData;
1144
1169
  let oldLowBatState;
@@ -1153,7 +1178,15 @@ class DeviceWatcher extends utils.Adapter {
1153
1178
 
1154
1179
  if (deviceData) {
1155
1180
  const gefundenerAdapter = Object.values(adapterArray).find((adapter) => adapter.adapterID === deviceData.adapterID);
1181
+ if (!gefundenerAdapter) {
1182
+ this.log.warn(`[renewDeviceData] - adapter not found for adapterID: ${deviceData.adapterID}`);
1183
+ return;
1184
+ }
1156
1185
  const silentEnabled = Object.values(this.config.tableDevices).find((adapter) => adapter.adapterKey === gefundenerAdapter.adapterKey);
1186
+ if (!silentEnabled) {
1187
+ this.log.warn(`[renewDeviceData] - device config not found for adapterKey: ${gefundenerAdapter.adapterKey}`);
1188
+ return;
1189
+ }
1157
1190
 
1158
1191
  // On statechange update available datapoint
1159
1192
  switch (id) {
@@ -1188,7 +1221,8 @@ class DeviceWatcher extends utils.Adapter {
1188
1221
  if (deviceData.isBatteryDevice) {
1189
1222
  oldLowBatState = deviceData.LowBat;
1190
1223
  if (state.val === 0 && deviceData.BatteryRaw >= 5) {
1191
- return;
1224
+ // Glitch-Filter: ignore single 0-value if battery was above 5 before
1225
+ break;
1192
1226
  }
1193
1227
  batteryData = await this.getBatteryData(state.val, oldLowBatState, deviceData.faultReport, deviceData.adapterID);
1194
1228
 
@@ -1264,7 +1298,7 @@ class DeviceWatcher extends utils.Adapter {
1264
1298
  deviceData.rssiPeerSelectorHMRPC,
1265
1299
  );
1266
1300
 
1267
- if (contactData !== undefined || contactData !== null) {
1301
+ if (contactData !== undefined && contactData !== null) {
1268
1302
  deviceData.LastContact = contactData[0];
1269
1303
  deviceData.Status = contactData[1];
1270
1304
  deviceData.SignalStrength = contactData[2];
@@ -1323,7 +1357,7 @@ class DeviceWatcher extends utils.Adapter {
1323
1357
  const devicesState = await this.getForeignStateAsync(instanceConnectedDeviceDP);
1324
1358
 
1325
1359
  let instanceConnectedDeviceVal;
1326
- if (instanceConnectedDeviceDP !== undefined && devicesState !== null && typeof devicesState.val === 'boolean') {
1360
+ if (devicesState !== null && typeof devicesState.val === 'boolean') {
1327
1361
  instanceConnectedDeviceVal = await tools.getInitValue(this, instanceConnectedDeviceDP);
1328
1362
  } else {
1329
1363
  instanceConnectedDeviceVal = 'N/A';
@@ -1549,13 +1583,19 @@ class DeviceWatcher extends utils.Adapter {
1549
1583
  await this.delay(instanceErrorTime);
1550
1584
  const daemonIsAliveAfterSecondDelay = await this.checkDaemonIsHealthy(instanceID);
1551
1585
 
1552
- if (daemonIsAliveAfterSecondDelay[0] && !daemonIsAliveAfterSecondDelay[1]) {
1553
- isAlive = Boolean(daemonIsAliveAfterSecondDelay[0]);
1554
- isHealthy = Boolean(daemonIsAliveAfterSecondDelay[1]);
1555
- instanceStatusString = String(daemonIsAliveAfterSecondDelay[2]);
1556
- connectedToHost = Boolean(daemonIsAliveAfterSecondDelay[3]);
1557
- connectedToDevice = Boolean(daemonIsAliveAfterSecondDelay[4]);
1558
- }
1586
+ // nach allen Retries: Status übernehmen (egal ob erholt oder weiterhin fehlerhaft)
1587
+ isAlive = Boolean(daemonIsAliveAfterSecondDelay[0]);
1588
+ isHealthy = Boolean(daemonIsAliveAfterSecondDelay[1]);
1589
+ instanceStatusString = String(daemonIsAliveAfterSecondDelay[2]);
1590
+ connectedToHost = Boolean(daemonIsAliveAfterSecondDelay[3]);
1591
+ connectedToDevice = Boolean(daemonIsAliveAfterSecondDelay[4]);
1592
+ } else {
1593
+ // nach erstem Retry wieder gesund
1594
+ isAlive = Boolean(daemonIsAliveAfterDelay[0]);
1595
+ isHealthy = Boolean(daemonIsAliveAfterDelay[1]);
1596
+ instanceStatusString = String(daemonIsAliveAfterDelay[2]);
1597
+ connectedToHost = Boolean(daemonIsAliveAfterDelay[3]);
1598
+ connectedToDevice = Boolean(daemonIsAliveAfterDelay[4]);
1559
1599
  }
1560
1600
  } else {
1561
1601
  daemonIsNotAlive = await this.checkDaemonIsAlive(instanceID, instanceDeactivationTime);
@@ -1580,14 +1620,17 @@ class DeviceWatcher extends utils.Adapter {
1580
1620
  async getAdapterUpdateData(adapterUpdateListDP) {
1581
1621
  // Clear the existing adapter updates data
1582
1622
  let adapterUpdatesJsonRaw = [];
1583
- let adapterJsonList;
1623
+ let adapterJsonList = {};
1584
1624
 
1585
1625
  // Fetch the adapter updates list
1586
1626
  const adapterUpdatesListVal = await this.getForeignStatesAsync(adapterUpdateListDP);
1587
1627
 
1588
- // Extract adapter data from the list
1589
- for (const [id, value] of Object.entries(adapterUpdatesListVal)) {
1590
- adapterJsonList = tools.parseData(value.val);
1628
+ // Extract adapter data from the list - merge all admin instances
1629
+ for (const [_id, value] of Object.entries(adapterUpdatesListVal)) {
1630
+ const parsed = tools.parseData(value.val);
1631
+ if (parsed && typeof parsed === 'object') {
1632
+ Object.assign(adapterJsonList, parsed);
1633
+ }
1591
1634
  }
1592
1635
 
1593
1636
  // Populate the adapter updates data
@@ -1817,8 +1860,24 @@ class DeviceWatcher extends utils.Adapter {
1817
1860
  // send message when instance was deactivated
1818
1861
  if (this.config.checkSendInstanceDeactivatedMsg && !instanceData.isAlive) {
1819
1862
  if (this.blacklistInstancesNotify.includes(instanceID)) {
1820
- return;
1863
+ break;
1821
1864
  }
1865
+ // Restart-Erkennung: Toleranzzeit abwarten und prüfen ob Instanz schon wieder läuft
1866
+ const restartTolerance = this.userTimeInstancesList.has(instanceID)
1867
+ ? this.userTimeInstancesList.get(instanceID).deactivationTime * 1000
1868
+ : this.config.offlineTimeInstances * 1000;
1869
+
1870
+ this.log.debug(`[renewInstanceData] Instance ${instanceID} went offline - waiting ${restartTolerance}ms to check for restart...`);
1871
+ await this.delay(restartTolerance);
1872
+
1873
+ const aliveAfterWait = await tools.getInitValue(this, `system.adapter.${instanceID}.alive`);
1874
+ if (aliveAfterWait) {
1875
+ // Instanz ist bereits wieder online → war nur ein Neustart
1876
+ this.log.debug(`[renewInstanceData] Instance ${instanceID} is back online after restart. No deactivation notification sent.`);
1877
+ await checkInstance(instanceID, instanceData);
1878
+ break;
1879
+ }
1880
+
1822
1881
  await this.sendStateNotifications('Instances', 'deactivatedInstance', instanceID);
1823
1882
  }
1824
1883
  }
@@ -2291,18 +2350,24 @@ class DeviceWatcher extends utils.Adapter {
2291
2350
  }
2292
2351
  }
2293
2352
  async getPreviousCronRun(lastCronRun) {
2294
- try {
2295
- const cronParser = cronParserLib.parseExpression ? cronParserLib : cronParserLib.default;
2296
-
2297
- const interval = cronParser.parseExpression(lastCronRun);
2298
- const previous = interval.prev();
2299
-
2300
- // Differenz in ms seit dem vorherigen Cron-Zeitpunkt
2301
- return Date.now() - previous.getTime();
2302
- } catch (error) {
2303
- this.log.error(`[getPreviousCronRun] - ${error}`);
2304
- return null;
2305
- }
2353
+ try {
2354
+ let interval;
2355
+ // cron-parser v4: parseExpression() – v5: CronExpressionParser.parse()
2356
+ if (typeof cronParserLib.parseExpression === 'function') {
2357
+ interval = cronParserLib.parseExpression(lastCronRun);
2358
+ } else if (cronParserLib.CronExpressionParser && typeof cronParserLib.CronExpressionParser.parse === 'function') {
2359
+ interval = cronParserLib.CronExpressionParser.parse(lastCronRun);
2360
+ } else {
2361
+ throw new Error('cron-parser: no compatible API found (parseExpression / CronExpressionParser.parse)');
2362
+ }
2363
+ const previous = interval.prev();
2364
+
2365
+ // Differenz in ms seit dem vorherigen Cron-Zeitpunkt
2366
+ return Date.now() - previous.getTime();
2367
+ } catch (error) {
2368
+ this.log.error(`[getPreviousCronRun] - ${error}`);
2369
+ return null;
2370
+ }
2306
2371
  }
2307
2372
 
2308
2373
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.device-watcher",
3
- "version": "2.15.3",
3
+ "version": "2.15.11",
4
4
  "description": "Watchdog for devices",
5
5
  "author": "Christian Behrends <mail@christian-behrends.de>",
6
6
  "contributors": [
@@ -23,26 +23,24 @@
23
23
  "url": "https://github.com/iobroker-community-adapters/ioBroker.device-watcher"
24
24
  },
25
25
  "engines": {
26
- "node": ">=20"
26
+ "node": ">=22"
27
27
  },
28
28
  "dependencies": {
29
29
  "@iobroker/adapter-core": "^3.3.2",
30
30
  "node-schedule": "^2.1.1",
31
- "cron-parser": "^4.9.0"
31
+ "cron-parser": "^5.5.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@alcalzone/release-script": "^5.0.0",
35
- "@alcalzone/release-script-plugin-iobroker": "^4.0.0",
36
- "@alcalzone/release-script-plugin-license": "^4.0.0",
37
- "@alcalzone/release-script-plugin-manual-review": "^4.0.0",
34
+ "@alcalzone/release-script": "^5.1.1",
35
+ "@alcalzone/release-script-plugin-iobroker": "^5.1.2",
36
+ "@alcalzone/release-script-plugin-license": "^5.1.1",
37
+ "@alcalzone/release-script-plugin-manual-review": "^5.1.1",
38
38
  "@iobroker/adapter-dev": "^1.5.0",
39
39
  "@iobroker/eslint-config": "^2.1.0",
40
40
  "@iobroker/testing": "^5.2.2",
41
- "@types/node": "^24.10.4",
41
+ "@types/node": "^24.12.0",
42
42
  "@types/node-schedule": "^2.1.8",
43
- "@typescript-eslint/eslint-plugin": "^8.52.0",
44
- "@typescript-eslint/parser": "^8.48.1",
45
- "typescript": "~5.9.3"
43
+ "typescript": "~6.0.2"
46
44
  },
47
45
  "main": "main.js",
48
46
  "files": [