iobroker.zigbee2mqtt 3.0.21 → 3.1.5

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.
@@ -2,65 +2,85 @@ const axios = require('axios').default;
2
2
  const sharp = require('sharp');
3
3
 
4
4
  /**
5
- *
5
+ * Lädt Geräte-Icons von zigbee2mqtt.io herunter, cacht sie im ioBroker-Dateisystem
6
+ * und skaliert sie auf die konfigurierte Größe.
6
7
  */
7
8
  class ImageController {
8
9
  /**
10
+ * Erstellt eine neue ImageController-Instanz.
9
11
  *
10
- * @param adapter
12
+ * @param {object} adapter Die ioBroker-Adapter-Instanz
11
13
  */
12
14
  constructor(adapter) {
13
15
  this.adapter = adapter;
14
16
  }
15
17
 
16
18
  /**
19
+ * Bereinigt eine Model-ID für die Verwendung in einer URL (ersetzt '/' durch '_').
17
20
  *
18
- * @param modelName
21
+ * @param {string} modelName Die zu bereinigende Model-ID
22
+ * @returns {string} Bereinigte Model-ID oder 'NA' wenn leer
19
23
  */
20
24
  sanitizeModelIDForImageUrl(modelName) {
21
- const modelNameString = modelName.replace('/', '_');
25
+ if (!modelName) { return 'NA'; }
22
26
  // eslint-disable-next-line no-control-regex
23
- return modelNameString.replace(/\u0000/g, '');
27
+ return modelName.replace(/\//g, '_').replace(/\u0000/g, '');
24
28
  }
25
29
 
26
30
  /**
31
+ * Bereinigt einen Z2M-Gerätenamen für die Verwendung in einer URL.
27
32
  *
28
- * @param deviceName
33
+ * @param {string} deviceName Der zu bereinigende Gerätename
34
+ * @returns {string} Bereinigter Name oder 'NA' wenn leer
29
35
  */
30
36
  sanitizeZ2MDeviceName(deviceName) {
31
- const deviceNameString = deviceName.replace(/:|\s|\//g, '-');
37
+ if (!deviceName) { return 'NA'; }
32
38
  // eslint-disable-next-line no-control-regex
33
- return deviceName ? deviceNameString.replace(/\u0000/g, '') : 'NA';
39
+ return deviceName.replace(/:|\s|\//g, '-').replace(/\u0000/g, '');
34
40
  }
35
41
 
36
42
  /**
43
+ * Erstellt die zigbee2mqtt.io-Bild-URL für ein Gerät.
37
44
  *
38
- * @param device
45
+ * @param {object} device Das Geräteobjekt aus dem Z2M-Payload
46
+ * @param {string} ext Dateiendung ('jpg' oder 'png')
47
+ * @returns {string|undefined} Die Bild-URL oder undefined wenn kein Modell vorhanden
39
48
  */
40
- getZ2mDeviceImageModelJPG(device) {
49
+ _buildZ2mImageUrl(device, ext) {
41
50
  if (device && device.definition && device.definition.model) {
42
- const icoString = `https://www.zigbee2mqtt.io/images/devices/${this.sanitizeZ2MDeviceName(device.definition.model)}.jpg`;
51
+ const icoString = `https://www.zigbee2mqtt.io/images/devices/${this.sanitizeZ2MDeviceName(device.definition.model)}.${ext}`;
43
52
  // eslint-disable-next-line no-control-regex
44
53
  return icoString.replace(/\u0000/g, '');
45
54
  }
55
+ return undefined;
56
+ }
57
+
58
+ /**
59
+ * Gibt die JPG-Bild-URL des Geräts auf zigbee2mqtt.io zurück.
60
+ *
61
+ * @param {object} device Das Geräteobjekt
62
+ * @returns {string|undefined} Die JPG-URL oder undefined
63
+ */
64
+ getZ2mDeviceImageModelJPG(device) {
65
+ return this._buildZ2mImageUrl(device, 'jpg');
46
66
  }
47
67
 
48
68
  /**
69
+ * Gibt die PNG-Bild-URL des Geräts auf zigbee2mqtt.io zurück.
49
70
  *
50
- * @param device
71
+ * @param {object} device Das Geräteobjekt
72
+ * @returns {string|undefined} Die PNG-URL oder undefined
51
73
  */
52
74
  getZ2mDeviceImageModelPNG(device) {
53
- if (device && device.definition && device.definition.model) {
54
- const icoString = `https://www.zigbee2mqtt.io/images/devices/${this.sanitizeZ2MDeviceName(device.definition.model)}.png`;
55
- // eslint-disable-next-line no-control-regex
56
- return icoString.replace(/\u0000/g, '');
57
- }
75
+ return this._buildZ2mImageUrl(device, 'png');
58
76
  }
59
77
 
60
78
 
61
79
  /**
80
+ * Gibt die SLS-Bild-URL des Geräts (alternative Quelle) zurück.
62
81
  *
63
- * @param device
82
+ * @param {object} device Das Geräteobjekt mit model_id-Feld
83
+ * @returns {string|undefined} Die Bild-URL oder undefined
64
84
  */
65
85
  getSlsDeviceImage(device) {
66
86
  if (device && device.model_id) {
@@ -68,22 +88,29 @@ class ImageController {
68
88
  // eslint-disable-next-line no-control-regex
69
89
  return icoString.replace(/\u0000/g, '');
70
90
  }
91
+ return undefined;
71
92
  }
72
93
 
73
94
  /**
95
+ * Lädt das Icon eines Geräts herunter (falls noch nicht gecacht), skaliert es
96
+ * auf die konfigurierte Größe und gibt es als Base64-Data-URL zurück.
74
97
  *
75
- * @param device
98
+ * @param {object} device Das Geräteobjekt aus dem Z2M-bridge/devices-Payload
99
+ * @returns {Promise<string>} Base64-kodiertes Icon als Data-URL oder ''
76
100
  */
77
101
  async getDeviceIcon(device) {
78
102
  if (!this.adapter.config.useDeviceIcons) {
79
- return '';
80
- }
81
-
82
- const imageSize = this.adapter.config.deviceIconsSize;
103
+ return '';
104
+ }
105
+ if (!device || !device.definition || !device.definition.model) {
106
+ return '';
107
+ }
83
108
 
84
- const z2mIconFileNameJPG = `${this.sanitizeZ2MDeviceName(device.definition.model)}.jpg`;
85
- const z2mIconFileNamePNG = `${this.sanitizeZ2MDeviceName(device.definition.model)}.png`;
86
- const slsIconFileName = `${this.sanitizeModelIDForImageUrl(device.model_id)}.png`;
109
+ const imageSize = this.adapter.config.deviceIconsSize;
110
+ const z2mModel = device.definition.model || '';
111
+ const z2mIconFileNameJPG = `${this.sanitizeZ2MDeviceName(z2mModel)}.jpg`;
112
+ const z2mIconFileNamePNG = `${this.sanitizeZ2MDeviceName(z2mModel)}.png`;
113
+ const slsIconFileName = `${this.sanitizeModelIDForImageUrl(device.model_id || z2mModel)}.png`;
87
114
 
88
115
  let iconFileName = await this.getExistingIconFileName(z2mIconFileNameJPG, z2mIconFileNamePNG, slsIconFileName);
89
116
  let iconFound = true;
@@ -93,7 +120,7 @@ return '';
93
120
  this.getZ2mDeviceImageModelJPG(device),
94
121
  this.getZ2mDeviceImageModelPNG(device),
95
122
  this.getSlsDeviceImage(device)
96
- ];
123
+ ].filter(Boolean);
97
124
 
98
125
  for (const iconUrl of iconUrls) {
99
126
  try {
@@ -103,47 +130,48 @@ return '';
103
130
  break;
104
131
  }
105
132
  } catch (ex) {
106
- // check next pic
133
+ this.adapter.log.debug(`Icon download failed for ${iconUrl}: ${ex && ex.message ? ex.message : ex}`);
107
134
  }
108
135
  }
109
136
  }
110
137
 
111
- if (!iconFound) {
138
+ if (!iconFound || !iconFileName) {
112
139
  this.adapter.log.warn(`Failed to download image for device model: ${device.definition.model} - ${device.definition.description}`);
113
140
  return '';
114
- }
115
- // Load image from the Meta-Store
116
- const icon = await this.adapter.readFileAsync(this.adapter.namespace, iconFileName);
117
- // Load Image Metadata
118
- const origIconMeta = await sharp(icon.file).metadata();
119
- // Check whether the image needs to be resized
120
- if (
121
- (origIconMeta.height && origIconMeta.height > imageSize) ||
122
- (origIconMeta.width && origIconMeta.width > imageSize)
123
- ) {
124
- // Resize image to 28x28 pixel
125
- this.adapter.log.info(
126
- `Resize image for device model ${device.definition.model} from: ${origIconMeta.width}x${origIconMeta.height} to ${imageSize}x${imageSize}`
127
- );
128
- icon.file = await sharp(icon.file)
129
- .resize({
130
- width: imageSize,
131
- height: imageSize,
132
- fit: sharp.fit.cover,
133
- position: sharp.strategy.entropy,
134
- })
135
- .toBuffer();
136
- // Replace the original image with the resize image.
137
- await this.adapter.writeFileAsync(this.adapter.namespace, iconFileName, icon.file);
138
- }
141
+ }
139
142
 
140
- // Create and output Base64
141
- return `data:image/png;base64,${icon.file.toString('base64')}`;
142
-
143
+ const icon = await this.adapter.readFileAsync(this.adapter.namespace, iconFileName);
144
+ if (!icon || !icon.file) {
145
+ this.adapter.log.warn(`Failed to read icon file: ${iconFileName}`);
146
+ return '';
147
+ }
148
+ const origIconMeta = await sharp(icon.file).metadata();
149
+ if (
150
+ (origIconMeta.height && origIconMeta.height > imageSize) ||
151
+ (origIconMeta.width && origIconMeta.width > imageSize)
152
+ ) {
153
+ this.adapter.log.info(
154
+ `Resize image for device model ${device.definition.model} from: ${origIconMeta.width}x${origIconMeta.height} to ${imageSize}x${imageSize}`
155
+ );
156
+ icon.file = await sharp(icon.file)
157
+ .resize({
158
+ width: imageSize,
159
+ height: imageSize,
160
+ fit: sharp.fit.cover,
161
+ position: sharp.strategy.entropy,
162
+ })
163
+ .toBuffer();
164
+ await this.adapter.writeFileAsync(this.adapter.namespace, iconFileName, icon.file);
165
+ }
166
+
167
+ return `data:image/png;base64,${icon.file.toString('base64')}`;
143
168
  }
169
+
144
170
  /**
171
+ * Extrahiert den Dateinamen (mit Erweiterung) aus einer URL.
145
172
  *
146
- * @param url
173
+ * @param {string} url Die vollständige URL
174
+ * @returns {string} Dateiname (z.B. "TRADFRI_bulb.jpg")
147
175
  */
148
176
  getFileNameWithExtension(url) {
149
177
  const path = new URL(url).pathname;
@@ -153,26 +181,30 @@ return '';
153
181
  }
154
182
 
155
183
  /**
184
+ * Lädt ein Icon von der angegebenen URL herunter und speichert es im ioBroker-Dateisystem.
156
185
  *
157
- * @param adapter
158
- * @param url
159
- * @param namespace
186
+ * @param {object} adapter Die ioBroker-Adapter-Instanz
187
+ * @param {string} url Download-URL des Icons
188
+ * @param {string} namespace ioBroker-Namespace (z.B. "zigbee2mqtt.0")
189
+ * @returns {Promise<boolean>} true bei Erfolg, false bei Fehler
160
190
  */
161
191
  async downloadIcon(adapter, url, namespace) {
162
192
  try {
163
- const res = await axios.get(url, { responseType: 'arraybuffer' });
193
+ const res = await axios.get(url, { responseType: 'arraybuffer', timeout: 10000 });
164
194
  await adapter.writeFileAsync(namespace, this.getFileNameWithExtension(url), res.data);
165
195
  return true;
166
196
  } catch (ex) {
167
- //adapter.log.warn(ex);
168
197
  return false;
169
198
  }
170
199
  }
200
+
171
201
  /**
202
+ * Prüft welcher lokale Icon-Dateiname bereits im ioBroker-Dateisystem existiert.
172
203
  *
173
- * @param z2mIconFileNameJPG
174
- * @param z2mIconFileNamePNG
175
- * @param slsIconFileName
204
+ * @param {string} z2mIconFileNameJPG JPG-Dateiname (Z2M-Format)
205
+ * @param {string} z2mIconFileNamePNG PNG-Dateiname (Z2M-Format)
206
+ * @param {string} slsIconFileName PNG-Dateiname (SLS-Format)
207
+ * @returns {Promise<string|null>} Erster gefundener Dateiname oder null
176
208
  */
177
209
  async getExistingIconFileName(z2mIconFileNameJPG, z2mIconFileNamePNG, slsIconFileName) {
178
210
  if (await this.adapter.fileExistsAsync(this.adapter.namespace, z2mIconFileNameJPG)) {
package/lib/messages.js CHANGED
@@ -1,31 +1,35 @@
1
1
  /**
2
+ * Gibt die aktuelle Adapter-Konfiguration formatiert als Log-Block aus.
2
3
  *
3
- * @param config
4
- * @param log
4
+ * @param {object} config Die Adapter-Konfiguration (this.config)
5
+ * @param {object} log Das ioBroker-Log-Objekt (this.log)
5
6
  */
6
- async function adapterInfo(config, log) {
7
+ function adapterInfo(config, log) {
8
+ if (!config || !log) {
9
+ return;
10
+ }
7
11
  log.info('================================= Adapter Config =================================');
8
12
  log.info(`|| Zigbee2MQTT Frontend Scheme: ${config.webUIScheme}`);
9
13
  log.info(`|| Zigbee2MQTT Frontend Server: ${config.webUIServer}`);
10
14
  log.info(`|| Zigbee2MQTT Frontend Port: ${config.webUIPort}`);
11
15
  log.info(`|| Zigbee2MQTT Connection Type: ${config.connectionType}`);
12
- if (config.connectionType == 'ws') {
16
+ if (config.connectionType === 'ws') {
13
17
  log.info(`|| Zigbee2MQTT Websocket Scheme: ${config.wsScheme}`);
14
18
  log.info(`|| Zigbee2MQTT Websocket Server: ${config.wsServerIP}`);
15
19
  log.info(`|| Zigbee2MQTT Websocket Port: ${config.wsServerPort}`);
16
20
  log.info(`|| Zigbee2MQTT Websocket Auth-Token: ${config.wsTokenEnabled ? 'use' : 'unused'}`);
17
21
  log.info(`|| Zigbee2MQTT Websocket Dummy MQTT-Server: ${config.dummyMqtt ? 'activated' : 'deactivated'}`);
18
- if (config.dummyMqtt == true) {
22
+ if (config.dummyMqtt === true) {
19
23
  log.info(`|| Zigbee2MQTT Dummy MQTT IP-Bind: ${config.mqttServerIPBind}`);
20
24
  log.info(`|| Zigbee2MQTT Dummy MQTT Port: ${config.mqttServerPort}`);
21
25
  }
22
- } else if (config.connectionType == 'exmqtt') {
23
- log.info(`|| Zigbee2MQTT Externanl MQTT Server: ${config.externalMqttServerIP}`);
24
- log.info(`|| Zigbee2MQTT Externanl MQTT Port: ${config.externalMqttServerPort}`);
26
+ } else if (config.connectionType === 'exmqtt') {
27
+ log.info(`|| Zigbee2MQTT External MQTT Server: ${config.externalMqttServerIP}`);
28
+ log.info(`|| Zigbee2MQTT External MQTT Port: ${config.externalMqttServerPort}`);
25
29
  log.info(
26
- `|| Zigbee2MQTT Externanl MQTT Credentials: ${config.externalMqttServerCredentials ? 'use' : 'unused'}`
30
+ `|| Zigbee2MQTT External MQTT Credentials: ${config.externalMqttServerCredentials ? 'use' : 'unused'}`
27
31
  );
28
- } else if (config.connectionType == 'intmqtt') {
32
+ } else if (config.connectionType === 'intmqtt') {
29
33
  log.info(`|| Zigbee2MQTT Internal MQTT IP-Bind: ${config.mqttServerIPBind}`);
30
34
  log.info(`|| Zigbee2MQTT Internal MQTT Port: ${config.mqttServerPort}`);
31
35
  }
@@ -47,19 +51,25 @@ async function adapterInfo(config, log) {
47
51
  }
48
52
 
49
53
  /**
54
+ * Gibt die Zigbee2MQTT-Bridge-Informationen formatiert als Log-Block aus.
50
55
  *
51
- * @param payload
52
- * @param log
56
+ * @param {object} payload Der Payload des bridge/info-Topics
57
+ * @param {object} log Das ioBroker-Log-Objekt (this.log)
53
58
  */
54
- async function zigbee2mqttInfo(payload, log) {
59
+ function zigbee2mqttInfo(payload, log) {
60
+ if (!payload || !log) {
61
+ return;
62
+ }
55
63
  log.info('============================ Zigbee2MQTT Information =============================');
56
64
  log.info(`|| Zigbee2MQTT Version: ${payload.version} `);
57
- log.info(
58
- `|| Coordinator type: ${payload.coordinator.type} Version: ${payload.coordinator.meta.revision} Serial: ${payload.config.serial.port}`
59
- );
60
- log.info(
61
- `|| Network panid ${payload.network.pan_id} channel: ${payload.network.channel} ext_pan_id: ${payload.network.extended_pan_id}`
62
- );
65
+ const coordType = payload.coordinator && payload.coordinator.type ? payload.coordinator.type : 'unknown';
66
+ const coordRev = payload.coordinator && payload.coordinator.meta ? payload.coordinator.meta.revision : 'unknown';
67
+ const serialPort = payload.config && payload.config.serial ? payload.config.serial.port : 'unknown';
68
+ const panId = payload.network ? payload.network.pan_id : 'unknown';
69
+ const channel = payload.network ? payload.network.channel : 'unknown';
70
+ const extPanId = payload.network ? payload.network.extended_pan_id : 'unknown';
71
+ log.info(`|| Coordinator type: ${coordType} Version: ${coordRev} Serial: ${serialPort}`);
72
+ log.info(`|| Network panid ${panId} channel: ${channel} ext_pan_id: ${extPanId}`);
63
73
  log.info('==================================================================================');
64
74
  }
65
75
 
@@ -1,65 +1,162 @@
1
+ 'use strict';
2
+
1
3
  const core = require('@iobroker/adapter-core');
2
- const Aedes = require('aedes');
3
- const net = require('net');
4
- let mqttServer;
4
+ const { Aedes } = require('aedes');
5
+ const net = require('node:net');
5
6
 
6
7
  /**
7
- *
8
+ * Verwaltet den internen oder Dummy-MQTT-Server (Aedes) für den Zigbee2MQTT-Adapter.
9
+ * Wird im intmqtt- und ws-Modus (dummyMqtt) verwendet.
8
10
  */
9
11
  class MqttServerController {
10
12
  /**
13
+ * Erstellt eine neue MqttServerController-Instanz.
11
14
  *
12
- * @param adapter
15
+ * @param {object} adapter Die ioBroker-Adapter-Instanz
13
16
  */
14
17
  constructor(adapter) {
15
- this.adapter = adapter;
18
+ this.adapter = adapter;
19
+ this.mqttServer = null;
20
+ this.aedesBroker = null;
21
+ }
22
+
23
+ /**
24
+ * Prüft ob Port und Bind-Adresse konfiguriert sind.
25
+ *
26
+ * @returns {boolean} true wenn die Konfiguration vollständig ist, sonst false
27
+ */
28
+ _checkConfig() {
29
+ if (!this.adapter.config.mqttServerPort || !this.adapter.config.mqttServerIPBind) {
30
+ this.adapter.log.error('MQTT server config incomplete (mqttServerPort / mqttServerIPBind missing).');
31
+ return false;
32
+ }
33
+ return true;
16
34
  }
17
35
 
18
36
  /**
37
+ * Hängt error- und close-Event-Handler an den internen net.Server.
19
38
  *
39
+ * @param {string} label Bezeichnung des Servers für Log-Ausgaben (z.B. "MQTT-Server")
40
+ */
41
+ _attachServerEvents(label) {
42
+ if (!this.mqttServer) {
43
+ return;
44
+ }
45
+ this.mqttServer.on('error', (err) => {
46
+ this.adapter.log.error(`${label} error: ${err && err.message ? err.message : String(err)}`);
47
+ });
48
+ this.mqttServer.on('close', () => {
49
+ this.adapter.log.debug(`${label} closed.`);
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Startet den internen MQTT-Server mit nedb-Persistenz (aedes + aedes-persistence-nedb).
55
+ * Wartet auf den erfolgreichen listen()-Aufruf bevor die Methode zurückkehrt.
20
56
  */
21
57
  async createMQTTServer() {
58
+ if (!this._checkConfig()) {
59
+ return;
60
+ }
22
61
  try {
23
62
  const NedbPersistence = require('aedes-persistence-nedb');
24
63
  const db = new NedbPersistence({
25
64
  path: `${core.getAbsoluteInstanceDataDir(this.adapter)}/mqttData`,
26
65
  prefix: '',
27
- });
28
- const aedes = Aedes({ persistence: db });
29
- mqttServer = net.createServer(aedes.handle);
30
- mqttServer.listen(this.adapter.config.mqttServerPort, this.adapter.config.mqttServerIPBind, () => {
31
- this.adapter.log.info(
32
- `Starting MQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`
66
+ });
67
+ // aedes-persistence-nedb does not implement the async setup() interface
68
+ // required by aedes v1.x → no-op shim
69
+ if (!db.setup) {
70
+ db.setup = async () => {};
71
+ }
72
+ this.aedesBroker = await Aedes.createBroker({ persistence: db });
73
+ this.mqttServer = net.createServer(this.aedesBroker.handle);
74
+ this._attachServerEvents('MQTT-Server');
75
+
76
+ await new Promise((resolve, reject) => {
77
+ this.mqttServer.listen(
78
+ this.adapter.config.mqttServerPort,
79
+ this.adapter.config.mqttServerIPBind,
80
+ () => {
81
+ this.adapter.log.info(
82
+ `Starting MQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`
83
+ );
84
+ resolve();
85
+ }
33
86
  );
87
+ this.mqttServer.once('error', reject);
34
88
  });
35
89
  } catch (err) {
36
- this.adapter.log.error(err);
90
+ this.adapter.log.error(`createMQTTServer error: ${err && err.message ? err.message : String(err)}`);
91
+ // Sicherstellen dass kein halboffener Server übrig bleibt
92
+ this.closeServer();
37
93
  }
38
94
  }
39
95
 
40
96
  /**
41
- *
97
+ * Startet einen Dummy-MQTT-Server ohne Persistenz.
98
+ * Wird im WebSocket-Modus (dummyMqtt=true) benötigt damit Z2M einen MQTT-Broker findet.
42
99
  */
43
100
  async createDummyMQTTServer() {
101
+ if (!this._checkConfig()) {
102
+ return;
103
+ }
44
104
  try {
45
- const aedes = Aedes();
46
- mqttServer = net.createServer(aedes.handle);
47
- mqttServer.listen(this.adapter.config.mqttServerPort, this.adapter.config.mqttServerIPBind, () => {
48
- this.adapter.log.info(
49
- `Starting DummyMQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`
105
+ this.aedesBroker = await Aedes.createBroker();
106
+ this.mqttServer = net.createServer(this.aedesBroker.handle);
107
+ this._attachServerEvents('DummyMQTT-Server');
108
+
109
+ await new Promise((resolve, reject) => {
110
+ this.mqttServer.listen(
111
+ this.adapter.config.mqttServerPort,
112
+ this.adapter.config.mqttServerIPBind,
113
+ () => {
114
+ this.adapter.log.info(
115
+ `Starting DummyMQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`
116
+ );
117
+ resolve();
118
+ }
50
119
  );
120
+ this.mqttServer.once('error', reject);
51
121
  });
52
122
  } catch (err) {
53
- this.adapter.log.error(err);
123
+ this.adapter.log.error(`createDummyMQTTServer error: ${err && err.message ? err.message : String(err)}`);
124
+ this.closeServer();
54
125
  }
55
126
  }
56
127
 
57
128
  /**
58
- *
129
+ * Schließt den MQTT-Server und den Aedes-Broker sicher.
130
+ * Wird beim Adapter-Stop oder nach einem Fehler beim Start aufgerufen.
59
131
  */
60
132
  closeServer() {
61
- if (mqttServer && !mqttServer.close()) {
62
- mqttServer.close();
133
+ if (this.mqttServer) {
134
+ const server = this.mqttServer;
135
+ this.mqttServer = null;
136
+ try {
137
+ server.close((err) => {
138
+ if (err) {
139
+ this.adapter.log.debug(`MQTT server close callback error: ${err.message}`);
140
+ }
141
+ });
142
+ } catch (err) {
143
+ this.adapter.log.error(`closeServer mqttServer.close() error: ${err}`);
144
+ }
145
+ }
146
+ if (this.aedesBroker) {
147
+ const broker = this.aedesBroker;
148
+ this.aedesBroker = null;
149
+ try {
150
+ broker.close((err) => {
151
+ if (err) {
152
+ this.adapter.log.debug(`Aedes broker close callback error: ${err}`);
153
+ } else {
154
+ this.adapter.log.debug('Aedes broker closed.');
155
+ }
156
+ });
157
+ } catch (err) {
158
+ this.adapter.log.error(`closeServer aedesBroker.close() error: ${err}`);
159
+ }
63
160
  }
64
161
  }
65
162
  }
@@ -40,9 +40,8 @@ function getStateDefinition(model) {
40
40
  const stateDef = nonGenDevStatesDefs[model];
41
41
  if (stateDef) {
42
42
  return stateDef;
43
- }
44
- return [];
45
-
43
+ }
44
+ return [];
46
45
  }
47
46
 
48
47
  module.exports = {