iobroker.zigbee2mqtt 3.2.6 → 3.2.8
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 +7 -9
- package/io-package.json +27 -27
- package/lib/imageController.js +22 -1
- package/lib/statesController.js +45 -11
- package/lib/websocketController.js +9 -0
- package/main.js +86 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -30,6 +30,13 @@ check you VM Settings
|
|
|
30
30
|
[Adapter Documentation](https://github.com/arteck/ioBroker.zigbee2mqtt/blob/main/docs/wiki.md)
|
|
31
31
|
|
|
32
32
|
## Changelog
|
|
33
|
+
### 3.2.8 (2026-09-19)
|
|
34
|
+
* (arteck) fix also fpor dummy mqtt if ws selected
|
|
35
|
+
|
|
36
|
+
### 3.2.7 (2026-09-19)
|
|
37
|
+
* (arteck) fix available status direct after new device is paired
|
|
38
|
+
* (arteck) fix internal mqtt handling
|
|
39
|
+
|
|
33
40
|
### 3.2.6 (2026-08-20)
|
|
34
41
|
* (arteck) add wait time for internal mqtt server into settings
|
|
35
42
|
|
|
@@ -39,15 +46,6 @@ check you VM Settings
|
|
|
39
46
|
### 3.2.4 (2026-06-26)
|
|
40
47
|
* (arteck) Dependencies have been updated
|
|
41
48
|
|
|
42
|
-
### 3.2.3 (2026-06-25)
|
|
43
|
-
* (arteck) typo
|
|
44
|
-
* (arteck) fix some warnings
|
|
45
|
-
* (arteck) fix internal mqtt
|
|
46
|
-
* (arteck) fix languages
|
|
47
|
-
|
|
48
|
-
### 3.2.2 (2026-05-26)
|
|
49
|
-
* (arteck) Dependencies have been updated
|
|
50
|
-
|
|
51
49
|
## License
|
|
52
50
|
|
|
53
51
|
MIT License
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zigbee2mqtt",
|
|
4
|
-
"version": "3.2.
|
|
4
|
+
"version": "3.2.8",
|
|
5
5
|
"news": {
|
|
6
|
+
"3.2.8": {
|
|
7
|
+
"en": "fix also fpor dummy mqtt if ws selected",
|
|
8
|
+
"de": "Fix auch für Dummy-MQT, falls gewählt",
|
|
9
|
+
"ru": "исправить также фиктивный matt, если мы выбрали",
|
|
10
|
+
"pt": "fixe também para dummy matt se eu escolher",
|
|
11
|
+
"nl": "ook opgelost voor dummy-mqt, indien geselecteerd",
|
|
12
|
+
"fr": "fix aussi pour le faux MQT si sélectionné",
|
|
13
|
+
"it": "fix anche per dummy mtt se scelto",
|
|
14
|
+
"es": "corregir también para MQT ficticio si lo elegí",
|
|
15
|
+
"pl": "napraw również fpor dummy mqtt, jeśli wybrano ws",
|
|
16
|
+
"uk": "Виправте також фпор фіктивний mqtt, якщо вибрано WS",
|
|
17
|
+
"zh-cn": "如果我们选择了,还修复了虚拟 mqtt 的问题"
|
|
18
|
+
},
|
|
19
|
+
"3.2.7": {
|
|
20
|
+
"en": "fix available status direct after new device is paired\nfix internal mqtt handling",
|
|
21
|
+
"de": "Verfügbarkeit direkt nach Kopplung korrigieren\nFix internes MQTT-Handling",
|
|
22
|
+
"ru": "исправить доступный статус сразу после сопряжения нового устройства\nисправить внутреннюю обработку mqtt",
|
|
23
|
+
"pt": "corrige o status disponível logo após o emparelhamento\nfixe o uso interno de matt",
|
|
24
|
+
"nl": "de beschikbare status herstellen direct nadat een nieuw apparaat is gekoppeld\ninterne mqtt-afhandeling repareren",
|
|
25
|
+
"fr": "corriger le statut disponible dès le jumelage d'un nouvel appareil\ngestion interne des tapis",
|
|
26
|
+
"it": "aggiusta subito dopo l'abbinamento del nuovo dispositivo\nfix gestione interna mqtt",
|
|
27
|
+
"es": "arreglar el estado disponible justo después de emparejar el nuevo\narreglar el manejo interno de MQTT",
|
|
28
|
+
"pl": "napraw dostępny status bezpośrednio po sparowaniu nowego urządzenia\nnaprawić wewnętrzną obsługę mqtt",
|
|
29
|
+
"uk": "виправлення доступного стану безпосередньо після створення пари нового пристрою\nвиправлено внутрішню обробку mqtt",
|
|
30
|
+
"zh-cn": "新设备配对后直接修复可用状态\n修复内部 mqtt 处理"
|
|
31
|
+
},
|
|
6
32
|
"3.2.6": {
|
|
7
33
|
"en": "add wait time for internal mqtt server into settings",
|
|
8
34
|
"de": "Füge in den Einstellungen eine Wartezeit für den internen MQTT-Server hinzu.",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Zaktualizowano zależności",
|
|
68
94
|
"uk": "Залежність було оновлено",
|
|
69
95
|
"zh-cn": "依赖关系已更新"
|
|
70
|
-
},
|
|
71
|
-
"3.2.1": {
|
|
72
|
-
"en": "Adapter requires node.js >= 22 now\nupd device manager\nfix aedes-persistence\nfix illuminance",
|
|
73
|
-
"de": "Adapter benötigt node.js >= 22 jetzt\nupd device manager\naedes-persistence reparieren\nbefestigungsleuchten",
|
|
74
|
-
"ru": "Адаптер требует node.js >= 22 сейчас\nобновленный device manager\nфиксировать стойкость\nфиксировать освещенность",
|
|
75
|
-
"pt": "Adaptador requer nod.js >= 22 agora\ngerenciador de dispositivos upd\ncorrigir aedes-persistência\nfixar a iluminação",
|
|
76
|
-
"nl": "Voor de adapter zijn node.js < 22 nu nodig\nupd apparaatbeheer\nfix aedes-persistence\nfix verlichtingssterkte",
|
|
77
|
-
"fr": "Adaptateur nécessite node.js >= 22 maintenant\ngestionnaire de périphérique mis à jour\nfixer la persistance des aedes\nfixer l'éclairage",
|
|
78
|
-
"it": "Adattatore richiede node.js >= 22 ora\nupd device manager\nfissare aedes-persistenza\nilluminazione di correzione",
|
|
79
|
-
"es": "Adaptador requiere node.js ю= 22 ahora\nupd device manager\narreglar aedes-persistence\niluminación de fijación",
|
|
80
|
-
"pl": "Adapter wymaga node.js > = 22\nmenedżer urządzenia upd\nfix aedes- trwałość\npodświetlenie",
|
|
81
|
-
"uk": "Адаптер вимагає node.js >= 22 тепер\nдиспетчер пристроїв\nфіксувати адисистентність\nфіксувати ілюмінанс",
|
|
82
|
-
"zh-cn": "适配器需要节点.js 现在22\n上调设备管理器\n固定死因-持久性\n修补灯光"
|
|
83
|
-
},
|
|
84
|
-
"3.2.0": {
|
|
85
|
-
"en": "del deprectated setStateAsync",
|
|
86
|
-
"de": "del deprected setStateAsync",
|
|
87
|
-
"ru": "обсуждение StateAsync",
|
|
88
|
-
"pt": "del deprectated setStateAsync",
|
|
89
|
-
"nl": "del deprectated setStateAsync",
|
|
90
|
-
"fr": "set dépréciéStateAsync",
|
|
91
|
-
"it": "del set deprettatoStateAsync",
|
|
92
|
-
"es": "del deprectated setStateAsync",
|
|
93
|
-
"pl": "del deprectated setStateAsync",
|
|
94
|
-
"uk": "deprectated setStateAsync",
|
|
95
|
-
"zh-cn": "已贬值的设置"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"messages": [
|
package/lib/imageController.js
CHANGED
|
@@ -13,6 +13,13 @@ class ImageController {
|
|
|
13
13
|
*/
|
|
14
14
|
constructor(adapter) {
|
|
15
15
|
this.adapter = adapter;
|
|
16
|
+
// PERFORMANCE/MEMORY: In-Memory-Cache für bereits erzeugte Base64-Icons.
|
|
17
|
+
// Ohne diesen Cache würde bei JEDEM WS-Reconnect (Z2M sendet dann erneut
|
|
18
|
+
// das komplette bridge/devices-Payload) für JEDES Gerät erneut
|
|
19
|
+
// sharp(...).metadata()/.resize() aufgerufen – native libvips-Allokationen,
|
|
20
|
+
// die bei häufigen Reconnects zu spürbarem RAM-Zuwachs des Adapterprozesses
|
|
21
|
+
// führen können, da der native Speicher nicht sofort ans OS zurückgegeben wird.
|
|
22
|
+
this._iconCache = new Map();
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
/**
|
|
@@ -112,6 +119,14 @@ class ImageController {
|
|
|
112
119
|
const z2mIconFileNamePNG = `${this.sanitizeZ2MDeviceName(z2mModel)}.png`;
|
|
113
120
|
const slsIconFileName = `${this.sanitizeModelIDForImageUrl(device.model_id || z2mModel)}.png`;
|
|
114
121
|
|
|
122
|
+
// MEMORY-FIX: Bereits erzeugtes Icon (Base64) aus dem In-Memory-Cache verwenden.
|
|
123
|
+
// Der Cache-Key basiert auf Modellname + Zielgröße, da beides das Ergebnis beeinflusst.
|
|
124
|
+
const cacheKey = `${z2mModel}::${imageSize}`;
|
|
125
|
+
const cached = this._iconCache.get(cacheKey);
|
|
126
|
+
if (cached !== undefined) {
|
|
127
|
+
return cached;
|
|
128
|
+
}
|
|
129
|
+
|
|
115
130
|
let iconFileName = await this.getExistingIconFileName(z2mIconFileNameJPG, z2mIconFileNamePNG, slsIconFileName);
|
|
116
131
|
let iconFound = true;
|
|
117
132
|
|
|
@@ -137,6 +152,8 @@ class ImageController {
|
|
|
137
152
|
|
|
138
153
|
if (!iconFound || !iconFileName) {
|
|
139
154
|
this.adapter.log.warn(`Failed to download image for device model: ${device.definition.model} - ${device.definition.description}`);
|
|
155
|
+
// Fehlgeschlagene Downloads NICHT dauerhaft cachen – evtl. ist die Quelle beim
|
|
156
|
+
// nächsten Reconnect wieder erreichbar.
|
|
140
157
|
return '';
|
|
141
158
|
}
|
|
142
159
|
|
|
@@ -164,7 +181,11 @@ class ImageController {
|
|
|
164
181
|
await this.adapter.writeFileAsync(this.adapter.namespace, iconFileName, icon.file);
|
|
165
182
|
}
|
|
166
183
|
|
|
167
|
-
|
|
184
|
+
const dataUrl = `data:image/png;base64,${icon.file.toString('base64')}`;
|
|
185
|
+
// MEMORY-FIX: Ergebnis für dieses Modell/diese Größe cachen, damit bei künftigen
|
|
186
|
+
// WS-Reconnects (erneutes bridge/devices) kein weiterer sharp()-Aufruf nötig ist.
|
|
187
|
+
this._iconCache.set(cacheKey, dataUrl);
|
|
188
|
+
return dataUrl;
|
|
168
189
|
}
|
|
169
190
|
|
|
170
191
|
/**
|
package/lib/statesController.js
CHANGED
|
@@ -23,6 +23,13 @@ class StatesController {
|
|
|
23
23
|
this.logCustomizations = logCustomizations;
|
|
24
24
|
this.createCache = createCache;
|
|
25
25
|
this.incStatsQueue = [];
|
|
26
|
+
// Max. Verweildauer einer Nachricht in der incStatsQueue, bevor sie verworfen wird.
|
|
27
|
+
// Zeit- statt zählerbasiert, da neu angelernte Geräte während des Interviews mehrere
|
|
28
|
+
// bridge/devices-Zyklen (und damit processQueue()-Aufrufe) benötigen können, bis das
|
|
29
|
+
// Geräteobjekt/die States angelegt sind. Ein reiner Aufruf-Zähler hätte z.B. das
|
|
30
|
+
// "available: true" der Retained-".../availability"-Nachricht vorzeitig verworfen,
|
|
31
|
+
// wodurch der available-State neu angelernter Geräte dauerhaft auf "false" hängen blieb.
|
|
32
|
+
this.QUEUE_MAX_AGE_MS = 5 * 60 * 1000; // 5 Minuten
|
|
26
33
|
this.timeOutCache = {};
|
|
27
34
|
// Einmalig berechnen – wird nur bei Konfigurationsänderung ungültig
|
|
28
35
|
this._debugDeviceList = logCustomizations.debugDevices
|
|
@@ -127,18 +134,28 @@ class StatesController {
|
|
|
127
134
|
// Wenn das Gerät (noch) nicht bekannt ist: Message in Queue stellen.
|
|
128
135
|
// Existiert für dieses Topic bereits ein Eintrag, wird er mit den aktuellen
|
|
129
136
|
// Payload-Daten überschrieben, damit wir stets den neuesten Stand verarbeiten.
|
|
137
|
+
//
|
|
138
|
+
// Wichtig: Die Verwerf-Zeit basiert auf einem ZEITSTEMPEL (_queuedAt), nicht auf
|
|
139
|
+
// einem Aufruf-Zähler. Grund: Bei neu angelernten Geräten kann bridge/devices
|
|
140
|
+
// während des Interviews mehrfach gesendet werden (und processQueue() wird auch
|
|
141
|
+
// durch bridge/devices-Events ANDERER Geräte ausgelöst). Ein reiner Zähler
|
|
142
|
+
// würde die z.B. per Retained-MQTT-Topic ".../availability" bereits eingetroffene
|
|
143
|
+
// "available: true"-Nachricht des neuen Geräts vorzeitig verwerfen, bevor das
|
|
144
|
+
// Interview abgeschlossen und das Geräteobjekt angelegt ist – Folge: das
|
|
145
|
+
// "available"-State bleibt dauerhaft auf dem Default-Wert "false" stehen.
|
|
130
146
|
const existingIdx = this.incStatsQueue.findIndex((x) => x && x.topic === messageObj.topic);
|
|
131
147
|
if (existingIdx !== -1) {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
const queuedAt = this.incStatsQueue[existingIdx]._queuedAt || Date.now();
|
|
149
|
+
const age = Date.now() - queuedAt;
|
|
150
|
+
if (age > this.QUEUE_MAX_AGE_MS) {
|
|
151
|
+
this.adapter.log.warn(`incStatsQueue: dropping message for unknown device ${messageObj.topic} after ${Math.round(age / 1000)}s`);
|
|
135
152
|
this.incStatsQueue.splice(existingIdx, 1);
|
|
136
153
|
return;
|
|
137
154
|
}
|
|
138
|
-
this.incStatsQueue[existingIdx] = { ...messageObj,
|
|
155
|
+
this.incStatsQueue[existingIdx] = { ...messageObj, _queuedAt: queuedAt };
|
|
139
156
|
} else {
|
|
140
157
|
if (this.incStatsQueue.length < 500) {
|
|
141
|
-
this.incStatsQueue.push({ ...messageObj,
|
|
158
|
+
this.incStatsQueue.push({ ...messageObj, _queuedAt: Date.now() });
|
|
142
159
|
} else {
|
|
143
160
|
this.adapter.log.warn(`incStatsQueue is full (500), dropping message for ${messageObj.topic}`);
|
|
144
161
|
}
|
|
@@ -168,15 +185,16 @@ class StatesController {
|
|
|
168
185
|
if (queuedThisRound) {return;}
|
|
169
186
|
const existingIdx = this.incStatsQueue.findIndex((x) => x && x.topic === msg.topic);
|
|
170
187
|
if (existingIdx !== -1) {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
188
|
+
const queuedAt = this.incStatsQueue[existingIdx]._queuedAt || Date.now();
|
|
189
|
+
const age = Date.now() - queuedAt;
|
|
190
|
+
if (age > this.QUEUE_MAX_AGE_MS) {
|
|
191
|
+
this.adapter.log.warn(`incStatsQueue: dropping message for ${msg.topic} after ${Math.round(age / 1000)}s (state not yet created)`);
|
|
174
192
|
this.incStatsQueue.splice(existingIdx, 1);
|
|
175
193
|
return;
|
|
176
194
|
}
|
|
177
|
-
this.incStatsQueue[existingIdx] = { ...msg,
|
|
195
|
+
this.incStatsQueue[existingIdx] = { ...msg, _queuedAt: queuedAt };
|
|
178
196
|
} else if (this.incStatsQueue.length < 500) {
|
|
179
|
-
this.incStatsQueue.push({ ...msg,
|
|
197
|
+
this.incStatsQueue.push({ ...msg, _queuedAt: Date.now() });
|
|
180
198
|
} else {
|
|
181
199
|
this.adapter.log.warn(`incStatsQueue is full, dropping message for ${msg.topic}`);
|
|
182
200
|
}
|
|
@@ -213,9 +231,25 @@ class StatesController {
|
|
|
213
231
|
|
|
214
232
|
// COLLECTION: Alle non-action state write-Promises sammeln und dann parallel ausführen
|
|
215
233
|
const stateWrites = [];
|
|
216
|
-
// Flag für available-Update (wenn last_seen vorhanden und config aktiv
|
|
234
|
+
// Flag für available-Update (wenn last_seen vorhanden und config aktiv, oder generell
|
|
235
|
+
// wenn echte Nutzdaten empfangen wurden – siehe Begründung weiter unten)
|
|
217
236
|
let needSetAvailableTrue = false;
|
|
218
237
|
|
|
238
|
+
// Bugfix: Wenn eine Nachricht ausschließlich das "available"-Property enthält
|
|
239
|
+
// (z.B. aus dem ".../availability"-Topic oder bridge/event device_leave), soll
|
|
240
|
+
// dessen Wert unverändert übernommen werden. Enthält die Nachricht jedoch
|
|
241
|
+
// zusätzliche/echte Nutzdaten (state, brightness, temperature, ...), beweist das
|
|
242
|
+
// bereits, dass das Gerät aktiv kommuniziert hat – unabhängig davon, ob Zigbee2MQTT
|
|
243
|
+
// das (per Default deaktivierte) "availability"-Feature nutzt oder last_seen sendet
|
|
244
|
+
// (advanced.last_seen ist bei Z2M ebenfalls per Default "disable"). Ohne diesen Fix
|
|
245
|
+
// bleibt der "available"-State nach einem MQTT-/Adapter-Neustart dauerhaft auf
|
|
246
|
+
// "false", obwohl die Geräte normal erreichbar und schaltbar sind.
|
|
247
|
+
const payloadKeys = Object.keys(messageObj.payload);
|
|
248
|
+
const hasOnlyAvailableKey = payloadKeys.length > 0 && payloadKeys.every((k) => k === 'available');
|
|
249
|
+
if (!hasOnlyAvailableKey && payloadKeys.length > 0 && !device.ieee_address.includes('group')) {
|
|
250
|
+
needSetAvailableTrue = true;
|
|
251
|
+
}
|
|
252
|
+
|
|
219
253
|
for (let [key, value] of Object.entries(messageObj.payload)) {
|
|
220
254
|
if (value === undefined || value === null) {
|
|
221
255
|
continue;
|
|
@@ -77,6 +77,15 @@ class WebsocketController {
|
|
|
77
77
|
this.adapter.log.info('Connect to Zigbee2MQTT over websocket connection.');
|
|
78
78
|
this.sendPingToServer();
|
|
79
79
|
this.wsHeartbeat();
|
|
80
|
+
// Einmaliger Fallback-Check: kommt innerhalb von 5s kein bridge/devices
|
|
81
|
+
// (z.B. weil Z2M nach einem dummyMqtt-Neuaufbau noch nicht "bereit" ist),
|
|
82
|
+
// versuche die Geräteliste über die Z2M-WebUI-REST-API nachzuladen.
|
|
83
|
+
// Nur relevant, wenn der Adapter selbst einen (dummy) MQTT-Broker verwaltet –
|
|
84
|
+
// bei rein externem MQTT-Server (dummyMqtt=false) bleibt dieser über einen
|
|
85
|
+
// Adapter-Neustart hinweg unberührt, ein Fallback ist dort nicht nötig.
|
|
86
|
+
if (this.adapter.config.dummyMqtt === true && typeof this.adapter.scheduleDeviceReloadFallback === 'function') {
|
|
87
|
+
this.adapter.scheduleDeviceReloadFallback();
|
|
88
|
+
}
|
|
80
89
|
});
|
|
81
90
|
|
|
82
91
|
this.wsClient.on('pong', () => {
|
package/main.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// you need to create an adapter
|
|
5
5
|
const core = require('@iobroker/adapter-core');
|
|
6
6
|
const mqtt = require('mqtt');
|
|
7
|
+
const axios = require('axios');
|
|
7
8
|
const utils = require('./lib/utils');
|
|
8
9
|
const schedule = require('node-schedule');
|
|
9
10
|
const checkConfig = require('./lib/check').checkConfig;
|
|
@@ -41,6 +42,14 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
41
42
|
this.mqttServerController = null;
|
|
42
43
|
this.messageParseMutex = Promise.resolve();
|
|
43
44
|
this.mqttReconnectAttempts = 0;
|
|
45
|
+
// Fallback: Wenn nach dem Verbindungsaufbau kein bridge/devices eintrifft
|
|
46
|
+
// (z.B. weil bei "intmqtt" der interne Broker samt Retained-Nachrichten neu
|
|
47
|
+
// erzeugt wurde und Zigbee2MQTT diese nicht von sich aus erneut published),
|
|
48
|
+
// wird EINMALIG versucht, die Geräteliste über die Z2M-WebUI-REST-API
|
|
49
|
+
// nachzuladen. Es wird NIE erneut onReady()/mqtt.connect() aufgerufen, um
|
|
50
|
+
// doppelte Clients/Listener bzw. Port-Konflikte (EADDRINUSE bei intmqtt) zu
|
|
51
|
+
// vermeiden.
|
|
52
|
+
this.deviceReloadTimer = null;
|
|
44
53
|
|
|
45
54
|
this.on('ready', () => {
|
|
46
55
|
this.onReady().catch((e) => this.log.error(`onReady error: ${e}`));
|
|
@@ -159,6 +168,10 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
159
168
|
this.log.error(`MQTT subscribe error: ${err && err.message ? err.message : String(err)}`);
|
|
160
169
|
}
|
|
161
170
|
});
|
|
171
|
+
// Einmaliger Fallback-Check: kommt innerhalb von 5s kein bridge/devices
|
|
172
|
+
// (z.B. verlorene Retained-Messages bei intmqtt-Neustart), versuche die
|
|
173
|
+
// Geräteliste über die Z2M-WebUI-REST-API nachzuladen.
|
|
174
|
+
this.scheduleDeviceReloadFallback();
|
|
162
175
|
});
|
|
163
176
|
|
|
164
177
|
this.mqttClient.on('reconnect', () => {
|
|
@@ -259,6 +272,71 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
259
272
|
this.websocketController.initWsClient();
|
|
260
273
|
}
|
|
261
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Plant einen einmaligen Fallback-Timer: Kommt innerhalb von 5 Sekunden kein
|
|
277
|
+
* bridge/devices von Zigbee2MQTT an (deviceCache bleibt leer), wird versucht,
|
|
278
|
+
* die Geräteliste über die Z2M-WebUI-REST-API (`/api/devices`) nachzuladen.
|
|
279
|
+
* Wird KEIN neuer MQTT-Client / Broker erzeugt – nur ein einmaliger HTTP-Call.
|
|
280
|
+
*/
|
|
281
|
+
scheduleDeviceReloadFallback() {
|
|
282
|
+
if (this.deviceReloadTimer) {
|
|
283
|
+
clearTimeout(this.deviceReloadTimer);
|
|
284
|
+
}
|
|
285
|
+
this.deviceReloadTimer = setTimeout(() => {
|
|
286
|
+
this.deviceReloadTimer = null;
|
|
287
|
+
this.tryFallbackDeviceReload().catch((e) => this.log.error(`tryFallbackDeviceReload error: ${e}`));
|
|
288
|
+
}, 5000);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Lädt die Geräteliste einmalig über die Z2M-WebUI-REST-API nach, falls nach
|
|
293
|
+
* dem Verbindungsaufbau (MQTT-connect ODER WebSocket-open) innerhalb von 5s kein
|
|
294
|
+
* bridge/devices empfangen wurde. Wird u.a. benötigt, wenn bei "intmqtt" der interne
|
|
295
|
+
* Broker (In-Memory-Persistenz) neu erzeugt wurde und Zigbee2MQTT die Retained-
|
|
296
|
+
* Nachrichten nicht von sich aus erneut published, oder wenn bei "ws" (inkl.
|
|
297
|
+
* dummyMqtt) Z2M nach einem Verbindungsabbruch nicht sofort den vollständigen
|
|
298
|
+
* State über die WebSocket-API nachsendet.
|
|
299
|
+
*/
|
|
300
|
+
async tryFallbackDeviceReload() {
|
|
301
|
+
if (this.deviceCache.length > 0) {
|
|
302
|
+
// bridge/devices ist zwischenzeitlich normal eingetroffen – nichts zu tun
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (!this.config.webUIServer || !this.config.webUIPort) {
|
|
306
|
+
this.log.debug('Fallback device reload skipped – WebUI address/port not configured.');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.log.warn(
|
|
310
|
+
'No bridge/devices received from Zigbee2MQTT within 5s – trying fallback via WebUI REST API. ' +
|
|
311
|
+
'This can happen if the internal MQTT broker was recreated (adapter restart) while Zigbee2MQTT itself keeps running.'
|
|
312
|
+
);
|
|
313
|
+
try {
|
|
314
|
+
const scheme = this.config.webUIScheme || 'http';
|
|
315
|
+
const url = `${scheme}://${this.config.webUIServer}:${this.config.webUIPort}/api/devices`;
|
|
316
|
+
const response = await axios.get(url, { timeout: 5000 });
|
|
317
|
+
const devices = response && response.data;
|
|
318
|
+
if (!Array.isArray(devices) || devices.length === 0) {
|
|
319
|
+
this.log.warn('Fallback device reload: WebUI API returned no devices.');
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (!this.deviceController || !this.statesController) {
|
|
323
|
+
this.log.debug('Fallback device reload: controllers not yet initialized, aborting.');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
await this.deviceController.createDeviceDefinitions(devices);
|
|
327
|
+
await this.deviceController.createOrUpdateDevices();
|
|
328
|
+
await this.statesController.subscribeWritableStates();
|
|
329
|
+
await this.statesController.processQueue();
|
|
330
|
+
this.log.info(`Fallback device reload completed – ${devices.length} device(s) imported via WebUI API.`);
|
|
331
|
+
} catch (e) {
|
|
332
|
+
this.log.error(
|
|
333
|
+
`Fallback device reload via WebUI API failed: ${e && e.message ? e.message : String(e)}. ` +
|
|
334
|
+
'Please verify that the Zigbee2MQTT frontend is enabled and reachable, or restart Zigbee2MQTT once ' +
|
|
335
|
+
'to force it to re-publish bridge/devices.'
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
262
340
|
/**
|
|
263
341
|
* Parst eine eingehende MQTT- oder WebSocket-Nachricht von Zigbee2MQTT
|
|
264
342
|
* und leitet sie an den zuständigen Controller weiter.
|
|
@@ -319,6 +397,10 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
319
397
|
break;
|
|
320
398
|
}
|
|
321
399
|
case 'bridge/devices':
|
|
400
|
+
if (this.deviceReloadTimer) {
|
|
401
|
+
clearTimeout(this.deviceReloadTimer);
|
|
402
|
+
this.deviceReloadTimer = null;
|
|
403
|
+
}
|
|
322
404
|
await this.deviceController.createDeviceDefinitions(messageObj.payload);
|
|
323
405
|
await this.deviceController.createOrUpdateDevices();
|
|
324
406
|
await this.deviceController.checkAndProgressDeviceRemove();
|
|
@@ -546,6 +628,10 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
546
628
|
*/
|
|
547
629
|
async onUnload(callback) {
|
|
548
630
|
try {
|
|
631
|
+
if (this.deviceReloadTimer) {
|
|
632
|
+
clearTimeout(this.deviceReloadTimer);
|
|
633
|
+
this.deviceReloadTimer = null;
|
|
634
|
+
}
|
|
549
635
|
if (['exmqtt', 'intmqtt'].includes(this.config.connectionType)) {
|
|
550
636
|
if (this.mqttClient && this.mqttClient.connected) {
|
|
551
637
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zigbee2mqtt",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.8",
|
|
4
4
|
"description": "Zigbee2MQTT adapter for ioBroker",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Dennis Rathjen and Arthur Rupp",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@iobroker/adapter-core": "^3.4.3",
|
|
28
|
-
"@iobroker/dm-utils": "^3.
|
|
28
|
+
"@iobroker/dm-utils": "^3.2.0",
|
|
29
29
|
"aedes": "^0.51.3",
|
|
30
30
|
"aedes-persistence": "^9.1.2",
|
|
31
|
-
"axios": "^1.
|
|
31
|
+
"axios": "^1.20.0",
|
|
32
32
|
"mqtt": "^5.15.2",
|
|
33
33
|
"node-schedule": "^2.1.1",
|
|
34
|
-
"sharp": "^0.
|
|
34
|
+
"sharp": "^0.35.4",
|
|
35
35
|
"ws": "^8.21.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"@alcalzone/release-script-plugin-manual-review": "^5.2.0",
|
|
42
42
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
43
43
|
"@iobroker/eslint-config": "^2.3.4",
|
|
44
|
-
"@iobroker/testing": "^
|
|
45
|
-
"@tsconfig/node22": "^22.0.
|
|
46
|
-
"@types/node": "^25.9.
|
|
44
|
+
"@iobroker/testing": "^6.2.1",
|
|
45
|
+
"@tsconfig/node22": "^22.0.6",
|
|
46
|
+
"@types/node": "^25.9.6",
|
|
47
47
|
"@types/node-schedule": "^2.1.8",
|
|
48
48
|
"typescript": "~6.0.3"
|
|
49
49
|
},
|