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 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.6",
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": [
@@ -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
- return `data:image/png;base64,${icon.file.toString('base64')}`;
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
  /**
@@ -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 ttl = (this.incStatsQueue[existingIdx]._ttl || 0) + 1;
133
- if (ttl > 10) {
134
- this.adapter.log.warn(`incStatsQueue: dropping message for unknown device ${messageObj.topic} after ${ttl} retries`);
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, _ttl: ttl };
155
+ this.incStatsQueue[existingIdx] = { ...messageObj, _queuedAt: queuedAt };
139
156
  } else {
140
157
  if (this.incStatsQueue.length < 500) {
141
- this.incStatsQueue.push({ ...messageObj, _ttl: 1 });
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 ttl = (this.incStatsQueue[existingIdx]._ttl || 0) + 1;
172
- if (ttl > 10) {
173
- this.adapter.log.warn(`incStatsQueue: dropping message for ${msg.topic} after ${ttl} retries (state not yet created)`);
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, _ttl: ttl };
195
+ this.incStatsQueue[existingIdx] = { ...msg, _queuedAt: queuedAt };
178
196
  } else if (this.incStatsQueue.length < 500) {
179
- this.incStatsQueue.push({ ...msg, _ttl: 1 });
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.6",
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.1.3",
28
+ "@iobroker/dm-utils": "^3.2.0",
29
29
  "aedes": "^0.51.3",
30
30
  "aedes-persistence": "^9.1.2",
31
- "axios": "^1.18.1",
31
+ "axios": "^1.20.0",
32
32
  "mqtt": "^5.15.2",
33
33
  "node-schedule": "^2.1.1",
34
- "sharp": "^0.34.5",
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": "^5.3.0",
45
- "@tsconfig/node22": "^22.0.5",
46
- "@types/node": "^25.9.5",
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
  },