iobroker.device-watcher 2.15.5 → 2.15.12

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,21 +191,21 @@ 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.5 (2026-01-12)
195
- * (arteck) fix admin
194
+ ### 2.15.12 (2026-05-06)
195
+ * (arteck) fix hueExtended battery check
196
196
 
197
- ### 2.15.4 (2026-01-11)
198
- * (arteck) define subscribe rules new
197
+ ### 2.15.11 (2026-05-06)
198
+ * (arteck)
199
199
 
200
- ### 2.15.3 (2026-01-10)
201
- * (arteck) fix ping adapter, fritzdec
200
+ ### 2.15.10 (2026-05-06)
201
+ - (copilot) Adapter requires node.js >= 22 now
202
+ * (arteck) fix adapter crash after delete a device
202
203
 
203
- ### 2.15.2 (2026-01-10)
204
- * (arteck) fix cronjob response
204
+ ### 2.15.9 (2026-04-22)
205
+ * (arteck) new xsense (v. 0.4.0) structure, plz update before
205
206
 
206
- ### 2.15.1 (2026-01-10)
207
- * (arteck) fix instance check
208
- * (arteck) fix admin json
207
+ ### 2.15.8 (2026-04-18)
208
+ * (arteck) fix cronParserLib.parseExpression message
209
209
 
210
210
  ## License
211
211
 
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "device-watcher",
4
- "version": "2.15.5",
4
+ "version": "2.15.12",
5
5
  "news": {
6
+ "2.15.12": {
7
+ "en": "fix hueExtended battery check",
8
+ "de": "hueExtended Akku Check",
9
+ "ru": "проверка батареи Extended battery check",
10
+ "pt": "corrigir matizExtended verificação da bateria",
11
+ "nl": "fix hueExtended batterijcontrole",
12
+ "fr": "fix hueVérification étendue de la batterie",
13
+ "it": "correggere il controllo della batteria estesa",
14
+ "es": "hue de fijaciónComprobar la batería",
15
+ "pl": "fix hueExtended kontrola baterii",
16
+ "uk": "fix hueExtended перевірка батареї",
17
+ "zh-cn": "修复 hue 扩展电池检查"
18
+ },
19
+ "2.15.11": {
20
+ "en": "fix",
21
+ "de": "fix",
22
+ "ru": "fix",
23
+ "pt": "fix",
24
+ "nl": "fix",
25
+ "fr": "fix",
26
+ "it": "fix",
27
+ "es": "fix",
28
+ "pl": "fix",
29
+ "uk": "fix",
30
+ "zh-cn": "fix"
31
+ },
6
32
  "2.15.5": {
7
33
  "en": "fix admin",
8
34
  "de": "admin",
@@ -15,71 +41,6 @@
15
41
  "pl": "fix admin",
16
42
  "uk": "адмін",
17
43
  "zh-cn": "固定管理员"
18
- },
19
- "2.15.4": {
20
- "en": "define subscribe rules new",
21
- "de": "anmelderegeln neu definieren",
22
- "ru": "новые правила подписки",
23
- "pt": "definir regras de subscrição novas",
24
- "nl": "nieuwe ondertekenregels definiëren",
25
- "fr": "définir des règles de souscription nouvelles",
26
- "it": "definire le regole di abbonamento",
27
- "es": "definir reglas de suscripción nuevas",
28
- "pl": "nowe zasady subskrypcji",
29
- "uk": "визначити правила підписки",
30
- "zh-cn": "定义订阅规则新"
31
- },
32
- "2.15.3": {
33
- "en": "fix ping adapter, fritzdec",
34
- "de": "fixierpistole, fritzdec",
35
- "ru": "адаптер для фиксации пингов, fritzdec",
36
- "pt": "corrigir adaptador de ping, fritzdec",
37
- "nl": "fix ping adapter, fritzdec",
38
- "fr": "adaptateur de fixation de ping, fritzdec",
39
- "it": "correzione adattatore ping, fritzdec",
40
- "es": "adaptador de fijación, fritzdec",
41
- "pl": "fix adapter ping, fritzdec",
42
- "uk": "фіксатор ping, fritzdec",
43
- "zh-cn": "修补 ping 适配器, fritzdec"
44
- },
45
- "2.15.2": {
46
- "en": "fix cronjob response",
47
- "de": "fix cronjob antwort",
48
- "ru": "реакция cronjob",
49
- "pt": "corrigir a resposta do cronjob",
50
- "nl": "cronjob respons repareren",
51
- "fr": "corriger la réponse cronjob",
52
- "it": "correzione di cronjob risposta",
53
- "es": "solución respuesta cronjob",
54
- "pl": "naprawić odpowiedź cronjob",
55
- "uk": "виправити cronjob відповідь",
56
- "zh-cn": "固定 curonjob 响应"
57
- },
58
- "2.15.1": {
59
- "en": "fix instance check\nfix admin json",
60
- "de": "fehlerbehebung\nadmin json",
61
- "ru": "установить проверку\nисполнитель: admin json",
62
- "pt": "corrigir verificação de instância\ncorrigir o administrador json",
63
- "nl": "fix instantiecontrole\nfix admin json",
64
- "fr": "correction de la vérification de l'instance\nréparer admin json",
65
- "it": "verifica dell'istanza di correzione\ncorrezione admin json",
66
- "es": "control de instancia de fijación\nadmin json",
67
- "pl": "fix instance check\nfix admin json",
68
- "uk": "перевірка екземпляра\nвиправлено admin json",
69
- "zh-cn": "修补实例检查\n固定管理员json"
70
- },
71
- "2.15.0": {
72
- "en": "fixed device array\ndeleted zwave2mqtt Adapter\nadded zwavews Adapter\nDependencies have been updated\nadded zwave2mqtt Adapter\nfixed all count devices\ncorrected finder",
73
- "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",
74
- "ru": "фиксированный массив устройств\nудаленный zwave2mqtt Адаптер\nдобавить zwavews Адаптер\nЗависимости были обновлены\nдобавить zwave2mqtt Адаптер\nфиксированные все счетные устройства\nисправленный искатель",
75
- "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",
76
- "nl": "vaste apparaatarray\nverwijderde zwave2mqtt Adapter\nzwavews toegevoegd Adapter\nAfhankelijkheden zijn bijgewerkt\nzwave2mqtt toegevoegd Adapter\nvaste alle telapparaten\ngecorrigeerde zoeker",
77
- "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",
78
- "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",
79
- "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",
80
- "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",
81
- "uk": "фіксований масив пристрою\nвидалено zwave2mqtt Адаптери\nдодано zwavews Адаптери\nЗалежність було оновлено\nдодано zwave2mqtt Адаптери\nфіксовані всі пристрої підрахунку\nвиправлений пошук",
82
- "zh-cn": "固定设备阵列\n删除 zwave2mqtt 适配器\n添加z波长 适配器\n依赖关系已更新\n添加 zwave2mqtt 适配器\n固定所有计数设备\n更正的查找者"
83
44
  }
84
45
  },
85
46
  "titleLang": {
@@ -163,7 +124,7 @@
163
124
  ],
164
125
  "globalDependencies": [
165
126
  {
166
- "admin": ">=7.6.17"
127
+ "admin": ">=7.7.22"
167
128
  }
168
129
  ],
169
130
  "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',
@@ -738,7 +741,7 @@ const adapterArray = {
738
741
  },
739
742
  xsense: {
740
743
  adapterKey: 'xsenseDevices',
741
- selektor: 'xsense.*.online',
744
+ selektor: 'xsense.*.*.online',
742
745
  timeSelector: '.online',
743
746
  adapterID: 'xsense',
744
747
  adapter: 'XSense',
@@ -773,8 +776,8 @@ const adapterArray = {
773
776
  },
774
777
  zigbee2MQTT: {
775
778
  adapterKey: 'zigbee2mqttDevices',
776
- selektor: 'zigbee2mqtt.*.link_quality',
777
- timeSelector: '.link_quality',
779
+ selektor: 'zigbee2mqtt.*.available',
780
+ timeSelector: '.last_seen',
778
781
  adapterID: 'zigbee2MQTT',
779
782
  adapter: 'Zigbee2MQTT',
780
783
  battery: '.battery',
@@ -794,7 +797,7 @@ const adapterArray = {
794
797
  rssiState: 'none',
795
798
  isLowBat: '.Battery.isLow',
796
799
  },
797
- zwavews: {
800
+ zwavews: {
798
801
  adapterKey: 'zwavewsDevices',
799
802
  selektor: 'zwavews.*.ready',
800
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
  /*=============================================
@@ -1166,18 +1204,28 @@ async function createData(adaptr, i) {
1166
1204
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1167
1205
  }
1168
1206
  break;
1169
- case 'hueExt':
1170
- case 'mihomeVacuum':
1171
- case 'mqttNuki':
1172
- case 'loqedSmartLock':
1173
- 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;
1174
1215
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1175
-
1176
- if (deviceBatteryState === undefined) {
1177
- deviceBatteryStateDP = shortCurrDeviceString + adaptr.selAdapter[i].battery2;
1178
- deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1179
- }
1180
- 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;
1181
1229
  case 'homee': // only battery devices, structure problem like homee.0.*.BatteryLevel-964
1182
1230
  const devicePrefix = `${currDeviceString}.BatteryLevel-`;
1183
1231
  const listeDP = await adaptr.getObjectViewAsync('system', 'state', {
@@ -1192,17 +1240,39 @@ async function createData(adaptr, i) {
1192
1240
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1193
1241
  break;
1194
1242
 
1243
+ case 'matter': {
1244
+ // matter: Batterie liegt unter shortCurrDeviceString.PowerSource-N.BATTERY
1245
+ // N ist variabel -> dynamisch per getObjectViewAsync suchen
1246
+ const matterBattPrefix = `${shortCurrDeviceString}.PowerSource-`;
1247
+ const matterBattList = await adaptr.getObjectViewAsync('system', 'state', {
1248
+ startkey: `${matterBattPrefix}`,
1249
+ endkey: `${matterBattPrefix}\u9999`,
1250
+ });
1251
+ // ersten BATTERY-State nehmen
1252
+ const matterBattRow = matterBattList.rows.find((r) => r.id.endsWith('.BATTERY'));
1253
+ if (matterBattRow) {
1254
+ deviceBatteryStateDP = matterBattRow.id;
1255
+ deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1256
+ }
1257
+ break;
1258
+ }
1259
+
1195
1260
  default:
1196
1261
  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;
1262
+ if (adaptr.selAdapter[i].battery === 'none') {
1263
+ // adapter has no battery percentage DP (e.g. hmiP) – skip lookups
1264
+ deviceBatteryState = undefined;
1265
+ } else {
1201
1266
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1202
1267
 
1203
1268
  if (deviceBatteryState === undefined) {
1204
- deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery3;
1269
+ deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery2;
1205
1270
  deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1271
+
1272
+ if (deviceBatteryState === undefined) {
1273
+ deviceBatteryStateDP = currDeviceString + adaptr.selAdapter[i].battery3;
1274
+ deviceBatteryState = await tools.getInitValue(adaptr, deviceBatteryStateDP);
1275
+ }
1206
1276
  }
1207
1277
  }
1208
1278
  break;
@@ -1221,6 +1291,21 @@ async function createData(adaptr, i) {
1221
1291
  deviceLowBatState = await tools.getInitValue(adaptr, isLowBatDP);
1222
1292
  }
1223
1293
  }
1294
+
1295
+ // matter: LOWBAT dynamisch über PowerSource-N.LOWBAT suchen
1296
+ if (adapterID === 'matter' && deviceLowBatState === undefined) {
1297
+ const matterLowBatPrefix = `${shortCurrDeviceString}.PowerSource-`;
1298
+ const matterLowBatList = await adaptr.getObjectViewAsync('system', 'state', {
1299
+ startkey: matterLowBatPrefix,
1300
+ endkey: `${matterLowBatPrefix}\u9999`,
1301
+ });
1302
+ const matterLowBatRow = matterLowBatList.rows.find((r) => r.id.endsWith('.LOWBAT'));
1303
+ if (matterLowBatRow) {
1304
+ isLowBatDP = matterLowBatRow.id;
1305
+ deviceLowBatState = await tools.getInitValue(adaptr, isLowBatDP);
1306
+ }
1307
+ }
1308
+
1224
1309
  if (deviceLowBatState === undefined) {
1225
1310
  isLowBatDP = 'none';
1226
1311
  }
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':
@@ -1059,76 +1085,113 @@ class DeviceWatcher extends utils.Adapter {
1059
1085
  async theLists(device) {
1060
1086
  // Raw List with all devices for user
1061
1087
  if (device.Status !== 'disabled') {
1062
- this.listAllDevicesUserRaw.push({
1063
- Device: device.Device,
1064
- Adapter: device.Adapter,
1065
- Instance: device.instance,
1066
- 'Instance connected': device.instanceDeviceConnected,
1067
- isBatteryDevice: device.isBatteryDevice,
1068
- Battery: device.Battery,
1069
- BatteryRaw: device.BatteryRaw,
1070
- BatteryUnitRaw: device.BatteryUnitRaw,
1071
- isLowBat: device.LowBat,
1072
- 'Signal strength': device.SignalStrength,
1073
- 'Signal strength Raw': device.SignalStrengthRaw,
1074
- 'Last contact': device.LastContact,
1075
- 'Update Available': device.Upgradable,
1076
- Status: device.Status,
1077
- });
1088
+ // Deduplication: some adapters (e.g. hmrpc with multiple channels, hue-extended with
1089
+ // devices appearing under both lights and sensors) create multiple Map entries for the
1090
+ // same physical device. Use Path as unique key to prevent duplicate list entries.
1091
+ const lang = this.config.userSelectedLanguage;
1092
+ const alreadyInUserRaw = this.listAllDevicesUserRaw.some((d) => d.Device === device.Device && d.Adapter === device.Adapter);
1093
+ if (!alreadyInUserRaw) {
1094
+ this.listAllDevicesUserRaw.push({
1095
+ Device: device.Device,
1096
+ Adapter: device.Adapter,
1097
+ Instance: device.instance,
1098
+ 'Instance connected': device.instanceDeviceConnected,
1099
+ isBatteryDevice: device.isBatteryDevice,
1100
+ Battery: device.Battery,
1101
+ BatteryRaw: device.BatteryRaw,
1102
+ BatteryUnitRaw: device.BatteryUnitRaw,
1103
+ isLowBat: device.LowBat,
1104
+ 'Signal strength': device.SignalStrength,
1105
+ 'Signal strength Raw': device.SignalStrengthRaw,
1106
+ 'Last contact': device.LastContact,
1107
+ 'Update Available': device.Upgradable,
1108
+ Status: device.Status,
1109
+ });
1110
+ }
1078
1111
 
1079
1112
  // List with all devices
1080
- this.listAllDevices.push({
1081
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1082
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1083
- [translations.Battery[this.config.userSelectedLanguage]]: device.Battery,
1084
- [translations.Signal_strength[this.config.userSelectedLanguage]]: device.SignalStrength,
1085
- [translations.Last_Contact[this.config.userSelectedLanguage]]: device.LastContact,
1086
- [translations.Status[this.config.userSelectedLanguage]]: device.Status,
1087
- });
1113
+ const alreadyInAll = this.listAllDevices.some(
1114
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1115
+ );
1116
+ if (!alreadyInAll) {
1117
+ this.listAllDevices.push({
1118
+ [translations.Device[lang]]: device.Device,
1119
+ [translations.Adapter[lang]]: device.Adapter,
1120
+ [translations.Battery[lang]]: device.Battery,
1121
+ [translations.Signal_strength[lang]]: device.SignalStrength,
1122
+ [translations.Last_Contact[lang]]: device.LastContact,
1123
+ [translations.Status[lang]]: device.Status,
1124
+ });
1125
+ }
1088
1126
 
1089
1127
  // LinkQuality lists
1090
- if (device.SignalStrength != ' - ') {
1091
- this.linkQualityDevices.push({
1092
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1093
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1094
- [translations.Signal_strength[this.config.userSelectedLanguage]]: device.SignalStrength,
1095
- });
1128
+ if (device.SignalStrength !== ' - ') {
1129
+ const alreadyInLQ = this.linkQualityDevices.some(
1130
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1131
+ );
1132
+ if (!alreadyInLQ) {
1133
+ this.linkQualityDevices.push({
1134
+ [translations.Device[lang]]: device.Device,
1135
+ [translations.Adapter[lang]]: device.Adapter,
1136
+ [translations.Signal_strength[lang]]: device.SignalStrength,
1137
+ });
1138
+ }
1096
1139
  }
1097
1140
 
1098
1141
  // Battery lists
1099
1142
  if (device.isBatteryDevice) {
1100
- this.batteryPowered.push({
1101
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1102
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1103
- [translations.Battery[this.config.userSelectedLanguage]]: device.Battery,
1104
- [translations.Status[this.config.userSelectedLanguage]]: device.Status,
1105
- });
1143
+ const alreadyInBat = this.batteryPowered.some(
1144
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1145
+ );
1146
+ if (!alreadyInBat) {
1147
+ this.batteryPowered.push({
1148
+ [translations.Device[lang]]: device.Device,
1149
+ [translations.Adapter[lang]]: device.Adapter,
1150
+ [translations.Battery[lang]]: device.Battery,
1151
+ [translations.Status[lang]]: device.Status,
1152
+ });
1153
+ }
1106
1154
  }
1107
1155
 
1108
1156
  // Low Bat lists
1109
1157
  if (device.LowBat && device.Status !== 'Offline') {
1110
- this.batteryLowPowered.push({
1111
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1112
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1113
- [translations.Battery[this.config.userSelectedLanguage]]: device.Battery,
1114
- });
1158
+ const alreadyInLowBat = this.batteryLowPowered.some(
1159
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1160
+ );
1161
+ if (!alreadyInLowBat) {
1162
+ this.batteryLowPowered.push({
1163
+ [translations.Device[lang]]: device.Device,
1164
+ [translations.Adapter[lang]]: device.Adapter,
1165
+ [translations.Battery[lang]]: device.Battery,
1166
+ });
1167
+ }
1115
1168
  }
1116
1169
 
1117
1170
  // Offline List
1118
1171
  if (device.Status === 'Offline') {
1119
- this.offlineDevices.push({
1120
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1121
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1122
- [translations.Last_Contact[this.config.userSelectedLanguage]]: device.LastContact,
1123
- });
1172
+ const alreadyOffline = this.offlineDevices.some(
1173
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1174
+ );
1175
+ if (!alreadyOffline) {
1176
+ this.offlineDevices.push({
1177
+ [translations.Device[lang]]: device.Device,
1178
+ [translations.Adapter[lang]]: device.Adapter,
1179
+ [translations.Last_Contact[lang]]: device.LastContact,
1180
+ });
1181
+ }
1124
1182
  }
1125
1183
 
1126
1184
  // Device update List
1127
1185
  if (device.Upgradable === true || device.Upgradable === 1) {
1128
- this.upgradableList.push({
1129
- [translations.Device[this.config.userSelectedLanguage]]: device.Device,
1130
- [translations.Adapter[this.config.userSelectedLanguage]]: device.Adapter,
1131
- });
1186
+ const alreadyUpgradable = this.upgradableList.some(
1187
+ (d) => d[translations.Device[lang]] === device.Device && d[translations.Adapter[lang]] === device.Adapter,
1188
+ );
1189
+ if (!alreadyUpgradable) {
1190
+ this.upgradableList.push({
1191
+ [translations.Device[lang]]: device.Device,
1192
+ [translations.Adapter[lang]]: device.Adapter,
1193
+ });
1194
+ }
1132
1195
  }
1133
1196
  }
1134
1197
  }
@@ -1138,7 +1201,6 @@ class DeviceWatcher extends utils.Adapter {
1138
1201
  * @param {ioBroker.State} state
1139
1202
  */
1140
1203
  async renewDeviceData(id, state) {
1141
- const regex = /^([^.]+\.\d+\.[^.]+)/;
1142
1204
  let batteryData;
1143
1205
  let signalData;
1144
1206
  let oldLowBatState;
@@ -1153,7 +1215,15 @@ class DeviceWatcher extends utils.Adapter {
1153
1215
 
1154
1216
  if (deviceData) {
1155
1217
  const gefundenerAdapter = Object.values(adapterArray).find((adapter) => adapter.adapterID === deviceData.adapterID);
1218
+ if (!gefundenerAdapter) {
1219
+ this.log.warn(`[renewDeviceData] - adapter not found for adapterID: ${deviceData.adapterID}`);
1220
+ return;
1221
+ }
1156
1222
  const silentEnabled = Object.values(this.config.tableDevices).find((adapter) => adapter.adapterKey === gefundenerAdapter.adapterKey);
1223
+ if (!silentEnabled) {
1224
+ this.log.warn(`[renewDeviceData] - device config not found for adapterKey: ${gefundenerAdapter.adapterKey}`);
1225
+ return;
1226
+ }
1157
1227
 
1158
1228
  // On statechange update available datapoint
1159
1229
  switch (id) {
@@ -1188,7 +1258,8 @@ class DeviceWatcher extends utils.Adapter {
1188
1258
  if (deviceData.isBatteryDevice) {
1189
1259
  oldLowBatState = deviceData.LowBat;
1190
1260
  if (state.val === 0 && deviceData.BatteryRaw >= 5) {
1191
- return;
1261
+ // Glitch-Filter: ignore single 0-value if battery was above 5 before
1262
+ break;
1192
1263
  }
1193
1264
  batteryData = await this.getBatteryData(state.val, oldLowBatState, deviceData.faultReport, deviceData.adapterID);
1194
1265
 
@@ -1264,7 +1335,7 @@ class DeviceWatcher extends utils.Adapter {
1264
1335
  deviceData.rssiPeerSelectorHMRPC,
1265
1336
  );
1266
1337
 
1267
- if (contactData !== undefined || contactData !== null) {
1338
+ if (contactData !== undefined && contactData !== null) {
1268
1339
  deviceData.LastContact = contactData[0];
1269
1340
  deviceData.Status = contactData[1];
1270
1341
  deviceData.SignalStrength = contactData[2];
@@ -1323,7 +1394,7 @@ class DeviceWatcher extends utils.Adapter {
1323
1394
  const devicesState = await this.getForeignStateAsync(instanceConnectedDeviceDP);
1324
1395
 
1325
1396
  let instanceConnectedDeviceVal;
1326
- if (instanceConnectedDeviceDP !== undefined && devicesState !== null && typeof devicesState.val === 'boolean') {
1397
+ if (devicesState !== null && typeof devicesState.val === 'boolean') {
1327
1398
  instanceConnectedDeviceVal = await tools.getInitValue(this, instanceConnectedDeviceDP);
1328
1399
  } else {
1329
1400
  instanceConnectedDeviceVal = 'N/A';
@@ -1549,13 +1620,19 @@ class DeviceWatcher extends utils.Adapter {
1549
1620
  await this.delay(instanceErrorTime);
1550
1621
  const daemonIsAliveAfterSecondDelay = await this.checkDaemonIsHealthy(instanceID);
1551
1622
 
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
- }
1623
+ // nach allen Retries: Status übernehmen (egal ob erholt oder weiterhin fehlerhaft)
1624
+ isAlive = Boolean(daemonIsAliveAfterSecondDelay[0]);
1625
+ isHealthy = Boolean(daemonIsAliveAfterSecondDelay[1]);
1626
+ instanceStatusString = String(daemonIsAliveAfterSecondDelay[2]);
1627
+ connectedToHost = Boolean(daemonIsAliveAfterSecondDelay[3]);
1628
+ connectedToDevice = Boolean(daemonIsAliveAfterSecondDelay[4]);
1629
+ } else {
1630
+ // nach erstem Retry wieder gesund
1631
+ isAlive = Boolean(daemonIsAliveAfterDelay[0]);
1632
+ isHealthy = Boolean(daemonIsAliveAfterDelay[1]);
1633
+ instanceStatusString = String(daemonIsAliveAfterDelay[2]);
1634
+ connectedToHost = Boolean(daemonIsAliveAfterDelay[3]);
1635
+ connectedToDevice = Boolean(daemonIsAliveAfterDelay[4]);
1559
1636
  }
1560
1637
  } else {
1561
1638
  daemonIsNotAlive = await this.checkDaemonIsAlive(instanceID, instanceDeactivationTime);
@@ -1580,14 +1657,17 @@ class DeviceWatcher extends utils.Adapter {
1580
1657
  async getAdapterUpdateData(adapterUpdateListDP) {
1581
1658
  // Clear the existing adapter updates data
1582
1659
  let adapterUpdatesJsonRaw = [];
1583
- let adapterJsonList;
1660
+ let adapterJsonList = {};
1584
1661
 
1585
1662
  // Fetch the adapter updates list
1586
1663
  const adapterUpdatesListVal = await this.getForeignStatesAsync(adapterUpdateListDP);
1587
1664
 
1588
- // Extract adapter data from the list
1589
- for (const [id, value] of Object.entries(adapterUpdatesListVal)) {
1590
- adapterJsonList = tools.parseData(value.val);
1665
+ // Extract adapter data from the list - merge all admin instances
1666
+ for (const [_id, value] of Object.entries(adapterUpdatesListVal)) {
1667
+ const parsed = tools.parseData(value.val);
1668
+ if (parsed && typeof parsed === 'object') {
1669
+ Object.assign(adapterJsonList, parsed);
1670
+ }
1591
1671
  }
1592
1672
 
1593
1673
  // Populate the adapter updates data
@@ -1817,8 +1897,24 @@ class DeviceWatcher extends utils.Adapter {
1817
1897
  // send message when instance was deactivated
1818
1898
  if (this.config.checkSendInstanceDeactivatedMsg && !instanceData.isAlive) {
1819
1899
  if (this.blacklistInstancesNotify.includes(instanceID)) {
1820
- return;
1900
+ break;
1901
+ }
1902
+ // Restart-Erkennung: Toleranzzeit abwarten und prüfen ob Instanz schon wieder läuft
1903
+ const restartTolerance = this.userTimeInstancesList.has(instanceID)
1904
+ ? this.userTimeInstancesList.get(instanceID).deactivationTime * 1000
1905
+ : this.config.offlineTimeInstances * 1000;
1906
+
1907
+ this.log.debug(`[renewInstanceData] Instance ${instanceID} went offline - waiting ${restartTolerance}ms to check for restart...`);
1908
+ await this.delay(restartTolerance);
1909
+
1910
+ const aliveAfterWait = await tools.getInitValue(this, `system.adapter.${instanceID}.alive`);
1911
+ if (aliveAfterWait) {
1912
+ // Instanz ist bereits wieder online → war nur ein Neustart
1913
+ this.log.debug(`[renewInstanceData] Instance ${instanceID} is back online after restart. No deactivation notification sent.`);
1914
+ await checkInstance(instanceID, instanceData);
1915
+ break;
1821
1916
  }
1917
+
1822
1918
  await this.sendStateNotifications('Instances', 'deactivatedInstance', instanceID);
1823
1919
  }
1824
1920
  }
@@ -2291,18 +2387,24 @@ class DeviceWatcher extends utils.Adapter {
2291
2387
  }
2292
2388
  }
2293
2389
  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
- }
2390
+ try {
2391
+ let interval;
2392
+ // cron-parser v4: parseExpression() – v5: CronExpressionParser.parse()
2393
+ if (typeof cronParserLib.parseExpression === 'function') {
2394
+ interval = cronParserLib.parseExpression(lastCronRun);
2395
+ } else if (cronParserLib.CronExpressionParser && typeof cronParserLib.CronExpressionParser.parse === 'function') {
2396
+ interval = cronParserLib.CronExpressionParser.parse(lastCronRun);
2397
+ } else {
2398
+ throw new Error('cron-parser: no compatible API found (parseExpression / CronExpressionParser.parse)');
2399
+ }
2400
+ const previous = interval.prev();
2401
+
2402
+ // Differenz in ms seit dem vorherigen Cron-Zeitpunkt
2403
+ return Date.now() - previous.getTime();
2404
+ } catch (error) {
2405
+ this.log.error(`[getPreviousCronRun] - ${error}`);
2406
+ return null;
2407
+ }
2306
2408
  }
2307
2409
 
2308
2410
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.device-watcher",
3
- "version": "2.15.5",
3
+ "version": "2.15.12",
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.2",
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.3"
46
44
  },
47
45
  "main": "main.js",
48
46
  "files": [