iobroker.device-watcher 2.15.11 → 2.15.13

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,6 +191,12 @@ 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.13 (2026-05-13)
195
+ * (arteck) fix new devices
196
+
197
+ ### 2.15.12 (2026-05-06)
198
+ * (arteck) fix hueExtended battery check
199
+
194
200
  ### 2.15.11 (2026-05-06)
195
201
  * (arteck)
196
202
 
@@ -201,13 +207,6 @@ This adapter would not have been possible without the great work of Christian Be
201
207
  ### 2.15.9 (2026-04-22)
202
208
  * (arteck) new xsense (v. 0.4.0) structure, plz update before
203
209
 
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
210
-
211
210
  ## License
212
211
 
213
212
  MIT License
package/io-package.json CHANGED
@@ -1,85 +1,46 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "device-watcher",
4
- "version": "2.15.11",
4
+ "version": "2.15.13",
5
5
  "news": {
6
+ "2.15.13": {
7
+ "en": "fix new devices",
8
+ "de": "neue geräte installieren",
9
+ "ru": "ремонт новых устройств",
10
+ "pt": "corrigir novos dispositivos",
11
+ "nl": "nieuwe apparaten herstellen",
12
+ "fr": "correction de nouveaux dispositifs",
13
+ "it": "fissare nuovi dispositivi",
14
+ "es": "nuevos dispositivos",
15
+ "pl": "naprawić nowe urządzenia",
16
+ "uk": "фіксувати нові пристрої",
17
+ "zh-cn": "修复新设备"
18
+ },
19
+ "2.15.12": {
20
+ "en": "fix hueExtended battery check",
21
+ "de": "hueExtended Akku Check",
22
+ "ru": "проверка батареи Extended battery check",
23
+ "pt": "corrigir matizExtended verificação da bateria",
24
+ "nl": "fix hueExtended batterijcontrole",
25
+ "fr": "fix hueVérification étendue de la batterie",
26
+ "it": "correggere il controllo della batteria estesa",
27
+ "es": "hue de fijaciónComprobar la batería",
28
+ "pl": "fix hueExtended kontrola baterii",
29
+ "uk": "fix hueExtended перевірка батареї",
30
+ "zh-cn": "修复 hue 扩展电池检查"
31
+ },
6
32
  "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为单位的固定组"
33
+ "en": "fix",
34
+ "de": "fix",
35
+ "ru": "fix",
36
+ "pt": "fix",
37
+ "nl": "fix",
38
+ "fr": "fix",
39
+ "it": "fix",
40
+ "es": "fix",
41
+ "pl": "fix",
42
+ "uk": "fix",
43
+ "zh-cn": "fix"
83
44
  },
84
45
  "2.15.5": {
85
46
  "en": "fix admin",
package/lib/crud.js CHANGED
@@ -1204,18 +1204,28 @@ async function createData(adaptr, i) {
1204
1204
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1205
1205
  }
1206
1206
  break;
1207
- case 'hueExt':
1208
- case 'mihomeVacuum':
1209
- case 'mqttNuki':
1210
- case 'loqedSmartLock':
1211
- deviceBatteryStateDP = shortCurrDeviceString + adaptr.selAdapter[i].battery;
1207
+ case 'hueExt':
1208
+ // hue-extended: battery is at currDeviceString level (e.g. hue-extended.0.lights.1.config.battery)
1209
+ // NOT shortCurrDeviceString (which is the lights/sensors folder, e.g. hue-extended.0.lights)
1210
+ deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery;
1211
+ deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1212
+
1213
+ if (deviceBatteryState === undefined) {
1214
+ deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery2;
1212
1215
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1213
-
1214
- if (deviceBatteryState === undefined) {
1215
- deviceBatteryStateDP = shortCurrDeviceString + adaptr.selAdapter[i].battery2;
1216
- deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1217
- }
1218
- break;
1216
+ }
1217
+ break;
1218
+ case 'mihomeVacuum':
1219
+ case 'mqttNuki':
1220
+ case 'loqedSmartLock':
1221
+ deviceBatteryStateDP = shortCurrDeviceString + adaptr.selAdapter[i].battery;
1222
+ deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1223
+
1224
+ if (deviceBatteryState === undefined) {
1225
+ deviceBatteryStateDP = shortCurrDeviceString + adaptr.selAdapter[i].battery2;
1226
+ deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1227
+ }
1228
+ break;
1219
1229
  case 'homee': // only battery devices, structure problem like homee.0.*.BatteryLevel-964
1220
1230
  const devicePrefix = `${currDeviceString}.BatteryLevel-`;
1221
1231
  const listeDP = await adaptr.getObjectViewAsync('system', 'state', {
package/main.js CHANGED
@@ -86,6 +86,9 @@ class DeviceWatcher extends utils.Adapter {
86
86
  // Check if main function is running
87
87
  this.mainRunning = false;
88
88
 
89
+ // Pending rescan flag (set if a new device was detected while main() was running)
90
+ this.pendingRescan = false;
91
+
89
92
  this.on('ready', this.onReady.bind(this));
90
93
  this.on('stateChange', this.onStateChange.bind(this));
91
94
  this.on('objectChange', this.onObjectChange.bind(this));
@@ -280,6 +283,13 @@ class DeviceWatcher extends utils.Adapter {
280
283
  }
281
284
  this.mainRunning = false;
282
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
+ }
283
293
  } //<--End of main function
284
294
 
285
295
  // If you need to react to object changes, uncomment the following block and the corresponding line in the constructor.
@@ -302,10 +312,23 @@ class DeviceWatcher extends utils.Adapter {
302
312
  if (!this.mainRunning) {
303
313
  await this.main();
304
314
  } else {
305
- return;
315
+ this.pendingRescan = true;
306
316
  }
307
317
  } else {
308
- return;
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
+ }
309
332
  }
310
333
  }
311
334
  } catch (error) {
@@ -366,6 +389,21 @@ class DeviceWatcher extends utils.Adapter {
366
389
  =============================================*/
367
390
  if (Array.from(this.listAllDevicesRaw.values()).some((obj) => Object.values(obj).includes(id))) {
368
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
+ }
369
407
  }
370
408
  } catch (error) {
371
409
  this.log.error(`Issue at state change: ${id}`);
@@ -1085,76 +1123,113 @@ class DeviceWatcher extends utils.Adapter {
1085
1123
  async theLists(device) {
1086
1124
  // Raw List with all devices for user
1087
1125
  if (device.Status !== 'disabled') {
1088
- this.listAllDevicesUserRaw.push({
1089
- Device: device.Device,
1090
- Adapter: device.Adapter,
1091
- Instance: device.instance,
1092
- 'Instance connected': device.instanceDeviceConnected,
1093
- isBatteryDevice: device.isBatteryDevice,
1094
- Battery: device.Battery,
1095
- BatteryRaw: device.BatteryRaw,
1096
- BatteryUnitRaw: device.BatteryUnitRaw,
1097
- isLowBat: device.LowBat,
1098
- 'Signal strength': device.SignalStrength,
1099
- 'Signal strength Raw': device.SignalStrengthRaw,
1100
- 'Last contact': device.LastContact,
1101
- 'Update Available': device.Upgradable,
1102
- Status: device.Status,
1103
- });
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
+ }
1104
1149
 
1105
1150
  // List with all devices
1106
- this.listAllDevices.push({
1107
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1108
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1109
- [translations.Battery[this.config.userSelectedLanguage]]: device.Battery,
1110
- [translations.Signal_strength[this.config.userSelectedLanguage]]: device.SignalStrength,
1111
- [translations.Last_Contact[this.config.userSelectedLanguage]]: device.LastContact,
1112
- [translations.Status[this.config.userSelectedLanguage]]: device.Status,
1113
- });
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
+ }
1114
1164
 
1115
1165
  // LinkQuality lists
1116
1166
  if (device.SignalStrength !== ' - ') {
1117
- this.linkQualityDevices.push({
1118
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1119
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1120
- [translations.Signal_strength[this.config.userSelectedLanguage]]: device.SignalStrength,
1121
- });
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
+ }
1122
1177
  }
1123
1178
 
1124
1179
  // Battery lists
1125
1180
  if (device.isBatteryDevice) {
1126
- this.batteryPowered.push({
1127
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1128
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1129
- [translations.Battery[this.config.userSelectedLanguage]]: device.Battery,
1130
- [translations.Status[this.config.userSelectedLanguage]]: device.Status,
1131
- });
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
+ }
1132
1192
  }
1133
1193
 
1134
1194
  // Low Bat lists
1135
1195
  if (device.LowBat && device.Status !== 'Offline') {
1136
- this.batteryLowPowered.push({
1137
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1138
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1139
- [translations.Battery[this.config.userSelectedLanguage]]: device.Battery,
1140
- });
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
+ }
1141
1206
  }
1142
1207
 
1143
1208
  // Offline List
1144
1209
  if (device.Status === 'Offline') {
1145
- this.offlineDevices.push({
1146
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1147
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1148
- [translations.Last_Contact[this.config.userSelectedLanguage]]: device.LastContact,
1149
- });
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
+ }
1150
1220
  }
1151
1221
 
1152
1222
  // Device update List
1153
1223
  if (device.Upgradable === true || device.Upgradable === 1) {
1154
- this.upgradableList.push({
1155
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1156
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1157
- });
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
+ }
1158
1233
  }
1159
1234
  }
1160
1235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.device-watcher",
3
- "version": "2.15.11",
3
+ "version": "2.15.13",
4
4
  "description": "Watchdog for devices",
5
5
  "author": "Christian Behrends <mail@christian-behrends.de>",
6
6
  "contributors": [
@@ -38,9 +38,9 @@
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.12.0",
41
+ "@types/node": "^24.12.2",
42
42
  "@types/node-schedule": "^2.1.8",
43
- "typescript": "~6.0.2"
43
+ "typescript": "~6.0.3"
44
44
  },
45
45
  "main": "main.js",
46
46
  "files": [