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 +11 -11
- package/io-package.json +28 -67
- package/lib/adapterArray.js +13 -10
- package/lib/crud.js +101 -16
- package/lib/tools.js +20 -4
- package/lib/translations.js +1 -1
- package/main.js +195 -93
- package/package.json +9 -11
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.
|
|
195
|
-
* (arteck) fix
|
|
194
|
+
### 2.15.12 (2026-05-06)
|
|
195
|
+
* (arteck) fix hueExtended battery check
|
|
196
196
|
|
|
197
|
-
### 2.15.
|
|
198
|
-
* (arteck)
|
|
197
|
+
### 2.15.11 (2026-05-06)
|
|
198
|
+
* (arteck)
|
|
199
199
|
|
|
200
|
-
### 2.15.
|
|
201
|
-
|
|
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.
|
|
204
|
-
* (arteck)
|
|
204
|
+
### 2.15.9 (2026-04-22)
|
|
205
|
+
* (arteck) new xsense (v. 0.4.0) structure, plz update before
|
|
205
206
|
|
|
206
|
-
### 2.15.
|
|
207
|
-
* (arteck) fix
|
|
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.
|
|
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.
|
|
127
|
+
"admin": ">=7.7.22"
|
|
167
128
|
}
|
|
168
129
|
],
|
|
169
130
|
"messages": [
|
package/lib/adapterArray.js
CHANGED
|
@@ -374,15 +374,18 @@ const adapterArray = {
|
|
|
374
374
|
},
|
|
375
375
|
matter: {
|
|
376
376
|
adapterKey: 'matterDevices',
|
|
377
|
-
|
|
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: '
|
|
381
|
+
adapter: 'Matter',
|
|
380
382
|
rssiState: 'none',
|
|
381
|
-
battery: '
|
|
382
|
-
|
|
383
|
-
|
|
383
|
+
battery: 'none',
|
|
384
|
+
battery2: 'none',
|
|
385
|
+
reach: '.info.connection',
|
|
386
|
+
isLowBat: 'none',
|
|
384
387
|
id: 'none',
|
|
385
|
-
upgrade: '
|
|
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
|
|
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.*.
|
|
777
|
-
timeSelector: '.
|
|
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
|
-
|
|
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
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
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
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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].
|
|
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
|
|
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
|
|
118
|
-
deviceData.UnreachState = await
|
|
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
|
|
157
|
+
if (await getTimestampConnectionDP(adaptr, deviceData.instanceDeviceConnectionDP, 50000)) {
|
|
142
158
|
await adaptr.sendStateNotifications('Devices', 'onlineStateDevice', deviceID, silentEnabled.telegramSilent);
|
|
143
159
|
}
|
|
144
160
|
}
|
package/lib/translations.js
CHANGED
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 [
|
|
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
|
-
|
|
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
|
|
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 === '
|
|
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 === '
|
|
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);
|
|
817
|
-
|
|
818
|
-
let lastContactString = `${this.formatDate(new Date(selector), 'hh:mm:ss')}`;
|
|
837
|
+
const lastContact = tools.getTimestamp(selector);
|
|
819
838
|
|
|
820
|
-
|
|
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 !=
|
|
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
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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.
|
|
1081
|
-
[translations.Device[
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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.
|
|
1092
|
-
[translations.Device[
|
|
1093
|
-
|
|
1094
|
-
|
|
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.
|
|
1101
|
-
[translations.Device[
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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.
|
|
1111
|
-
[translations.Device[
|
|
1112
|
-
|
|
1113
|
-
|
|
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.
|
|
1120
|
-
[translations.Device[
|
|
1121
|
-
|
|
1122
|
-
|
|
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.
|
|
1129
|
-
[translations.Device[
|
|
1130
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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 [
|
|
1590
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
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.
|
|
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": ">=
|
|
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": "^
|
|
31
|
+
"cron-parser": "^5.5.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@alcalzone/release-script": "^5.
|
|
35
|
-
"@alcalzone/release-script-plugin-iobroker": "^
|
|
36
|
-
"@alcalzone/release-script-plugin-license": "^
|
|
37
|
-
"@alcalzone/release-script-plugin-manual-review": "^
|
|
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.
|
|
41
|
+
"@types/node": "^24.12.2",
|
|
42
42
|
"@types/node-schedule": "^2.1.8",
|
|
43
|
-
"
|
|
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": [
|