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.
@@ -6,17 +6,32 @@ const rgb = require('./rgb.js');
6
6
  const ImageController = require('./imageController').ImageController;
7
7
 
8
8
  /**
9
+ * Prüft ob eine ieee_address in der kommaseparierten debugDevices-Liste enthalten ist.
10
+ * String.includes() würde Substring-Matches liefern (z.B. "0x12" trifft "0x1234").
9
11
  *
12
+ * @param {string} debugDevices
13
+ * @param {string} address
14
+ * @returns {boolean}
15
+ */
16
+ function isDebugDevice(debugDevices, address) {
17
+ if (!debugDevices || !address) { return false; }
18
+ return String(debugDevices).split(',').map((s) => s.trim()).includes(address);
19
+ }
20
+
21
+ /**
22
+ * Verwaltet das Erstellen und Aktualisieren von Geräte- und Gruppenobjekten in ioBroker
23
+ * basierend auf den Zigbee2MQTT-Exposes.
10
24
  */
11
25
  class DeviceController {
12
26
  /**
27
+ * Erstellt eine neue DeviceController-Instanz.
13
28
  *
14
- * @param adapter
15
- * @param deviceCache
16
- * @param groupCache
17
- * @param config
18
- * @param logCustomizations
19
- * @param createCache
29
+ * @param {object} adapter Die ioBroker-Adapter-Instanz
30
+ * @param {Array} deviceCache Gemeinsamer Cache aller bekannten Geräte
31
+ * @param {Array} groupCache Gemeinsamer Cache aller bekannten Gruppen
32
+ * @param {object} config Adapter-Konfiguration
33
+ * @param {object} logCustomizations Debug/Filter-Einstellungen (debugDevices, logfilter)
34
+ * @param {object} createCache Cache bereits erstellter ioBroker-Objekte
20
35
  */
21
36
  constructor(adapter, deviceCache, groupCache, config, logCustomizations, createCache) {
22
37
  this.adapter = adapter;
@@ -29,29 +44,41 @@ class DeviceController {
29
44
  }
30
45
 
31
46
  /**
47
+ * Erstellt Geräte-Definitionen aus dem bridge/devices-Payload von Zigbee2MQTT
48
+ * und befüllt den deviceCache neu.
32
49
  *
33
- * @param devicesMessage
50
+ * @param {Array} devicesMessage Array mit Gerätedaten aus dem bridge/devices-Topic
34
51
  */
35
52
  async createDeviceDefinitions(devicesMessage) {
53
+ // Fix 1: devicesMessage kann null/kein Array sein (z.B. leerer bridge/devices payload)
54
+ if (!Array.isArray(devicesMessage)) {
55
+ this.adapter.log.warn('createDeviceDefinitions: payload ist kein Array');
56
+ return;
57
+ }
36
58
  utils.clearArray(this.deviceCache);
37
59
  for (const devicesMessag of devicesMessage) {
38
- if (this.logCustomizations.debugDevices.includes(devicesMessag.ieee_address)) {
60
+ if (!devicesMessag || !devicesMessag.ieee_address) {
61
+ continue;
62
+ }
63
+
64
+ if (isDebugDevice(this.logCustomizations.debugDevices, devicesMessag.ieee_address)) {
39
65
  this.adapter.log.warn(
40
66
  `--->>> fromZ2M -> ${devicesMessag.ieee_address} exposes: ${JSON.stringify(devicesMessag)}`
41
67
  );
42
68
  }
43
69
 
44
70
  if (devicesMessag.definition != null) {
45
- // if the device is already present in the cache, remove it
46
71
  this.removeDeviceByIeee(this.deviceCache, devicesMessag.ieee_address);
47
72
 
48
- if (devicesMessag.definition.exposes) {
73
+ // Fix 3: exposes muss auch ein Array sein
74
+ if (Array.isArray(devicesMessag.definition.exposes)) {
49
75
  try {
50
76
  const newDevice = await createDeviceFromExposes(devicesMessag, this.adapter);
51
77
  newDevice.icon = await this.imageController.getDeviceIcon(devicesMessag);
52
78
  this.deviceCache.push(newDevice);
53
79
  } catch (err) {
54
- this.adapter.log.warn(`Cannot ${devicesMessag.friendly_name} create Device from Exposes!`);
80
+ // Fix 2: friendly_name im Fehlerlog verwenden
81
+ this.adapter.log.warn(`Cannot create Device from Exposes for ${devicesMessag.friendly_name || devicesMessag.ieee_address}!`);
55
82
  this.adapter.log.debug(JSON.stringify(devicesMessag));
56
83
  this.adapter.log.debug(err);
57
84
  }
@@ -61,29 +88,29 @@ class DeviceController {
61
88
  }
62
89
 
63
90
  /**
91
+ * Erstellt die State-Definition für eine Zigbee2MQTT-Gruppe und legt sie im groupCache ab.
64
92
  *
65
- * @param groupID
66
- * @param ieee_address
67
- * @param scenes
93
+ * @param {string} groupID Friendly-Name der Gruppe (wird als ioBroker-ID verwendet)
94
+ * @param {string} ieee_address Interne Gruppen-ID (z.B. "group_1")
95
+ * @param {Array} scenes Liste der Szenen-Objekte ({id, name}) der Gruppe
68
96
  */
69
- async defineGroupDevice(groupID, ieee_address, scenes) {
70
- const brmPropName =
71
- this.adapter.config.brightnessMoveOnOff == true ? 'brightness_move_onoff' : 'brightness_move';
72
- const brsPropName =
73
- this.adapter.config.brightnessStepOnOff == true ? 'brightness_step_onoff' : 'brightness_step';
97
+ defineGroupDevice(groupID, ieee_address, scenes) {
98
+ const brmPropName = this.adapter.config.brightnessMoveOnOff === true ? 'brightness_move_onoff' : 'brightness_move';
99
+ const brsPropName = this.adapter.config.brightnessStepOnOff === true ? 'brightness_step_onoff' : 'brightness_step';
74
100
  const newDevice = {
75
101
  id: groupID,
76
102
  ieee_address: ieee_address,
77
103
  icon: undefined, // await imageController.getDeviceIcon(devicesMessag), device.definition.model
78
104
  optionsValues: {},
79
105
  states: [
80
- statesDefs.available,
81
- statesDefs.brightness,
82
- statesDefs.colortemp_move,
83
- statesDefs.transition,
106
+ // Fix 1: Klone aller statesDefs-Objekte – sonst werden geteilte Referenzen mutiert
107
+ Object.assign({}, statesDefs.available),
108
+ Object.assign({}, statesDefs.brightness),
109
+ Object.assign({}, statesDefs.colortemp_move),
110
+ Object.assign({}, statesDefs.transition),
84
111
  {
85
112
  id: 'state',
86
- prob: 'state',
113
+ prop: 'state',
87
114
  name: 'Switch state',
88
115
  options: ['transition'],
89
116
  icon: undefined,
@@ -133,29 +160,28 @@ class DeviceController {
133
160
  type: 'string',
134
161
  def: '#ff00ff',
135
162
  setter: (value) => {
136
- let xy = [0, 0];
163
+ // Fix 6: redundante [0,0]-Initialisierung entfernt
137
164
  const rgbcolor = colors.ParseColor(value);
138
- xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
165
+ const xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
139
166
  return {
140
167
  x: xy[0],
141
168
  y: xy[1],
142
169
  };
143
170
  },
144
171
  getter: (payload) => {
145
- if (payload.color_mode != 'xy' && this.config.colorTempSyncColor == false) {
172
+ if (payload.color_mode !== 'xy' && !this.config.colorTempSyncColor) {
146
173
  return undefined;
147
174
  }
148
- if (payload.color && payload.color.x && payload.color.y) {
175
+ // Fix 2: x=0 / y=0 sind gültige CIE-Koordinaten → != null statt truthy
176
+ if (payload.color && payload.color.x != null && payload.color.y != null) {
149
177
  const colorval = rgb.cie_to_rgb(payload.color.x, payload.color.y);
150
178
  return (
151
- `#${
152
- utils.decimalToHex(colorval[0])
153
- }${utils.decimalToHex(colorval[1])
154
- }${utils.decimalToHex(colorval[2])}`
179
+ `#${utils.decimalToHex(colorval[0])
180
+ }${utils.decimalToHex(colorval[1])
181
+ }${utils.decimalToHex(colorval[2])}`
155
182
  );
156
- }
157
- return undefined;
158
-
183
+ }
184
+ return undefined;
159
185
  },
160
186
  },
161
187
  {
@@ -168,22 +194,24 @@ class DeviceController {
168
194
  write: true,
169
195
  read: true,
170
196
  type: 'number',
171
- min: this.config.useKelvin == true ? utils.miredKelvinConversion(550) : 150,
172
- max: this.config.useKelvin == true ? utils.miredKelvinConversion(153) : 500,
173
- def: this.config.useKelvin == true ? utils.miredKelvinConversion(153) : 500,
174
- unit: this.config.useKelvin == true ? 'K' : 'mired',
197
+ min: this.config.useKelvin === true ? utils.miredKelvinConversion(550) : 150,
198
+ max: this.config.useKelvin === true ? utils.miredKelvinConversion(153) : 500,
199
+ def: this.config.useKelvin === true ? utils.miredKelvinConversion(153) : 500,
200
+ unit: this.config.useKelvin === true ? 'K' : 'mired',
175
201
  setter: (value) => {
176
202
  return utils.toMired(value);
177
203
  },
178
204
  getter: (payload) => {
179
- if (payload.color_mode != 'color_temp') {
205
+ if (payload.color_mode !== 'color_temp') {
180
206
  return undefined;
181
207
  }
182
- if (this.config.useKelvin == true) {
208
+ if (payload.color_temp == null) {
209
+ return undefined;
210
+ }
211
+ if (this.config.useKelvin === true) {
183
212
  return utils.miredKelvinConversion(payload.color_temp);
184
- }
185
- return payload.color_temp;
186
-
213
+ }
214
+ return payload.color_temp;
187
215
  },
188
216
  },
189
217
  {
@@ -226,7 +254,7 @@ class DeviceController {
226
254
  const sceneSate = {
227
255
  id: `scene_${scene.id}`,
228
256
  prop: `scene_recall`,
229
- name: scene.name,
257
+ name: scene.name || `Scene ${scene.id}`,
230
258
  icon: undefined,
231
259
  role: 'button',
232
260
  write: true,
@@ -244,148 +272,172 @@ class DeviceController {
244
272
  }
245
273
 
246
274
  /**
275
+ * Erstellt Gruppen-Definitionen aus dem bridge/groups-Payload von Zigbee2MQTT
276
+ * und befüllt den groupCache neu.
247
277
  *
248
- * @param groupsMessage
278
+ * @param {Array} groupsMessage Array mit Gruppendaten aus dem bridge/groups-Topic
249
279
  */
250
280
  async createGroupDefinitions(groupsMessage) {
281
+ // Fix 4: groupsMessage kann null/kein Array sein
282
+ if (!Array.isArray(groupsMessage)) {
283
+ this.adapter.log.warn('createGroupDefinitions: payload ist kein Array');
284
+ return;
285
+ }
251
286
  utils.clearArray(this.groupCache);
252
287
  for (const groupMessage of groupsMessage) {
253
- if (this.logCustomizations.debugDevices.includes(groupMessage.id)) {
288
+ // Fix 5: fehlende Pflichtfelder abfangen
289
+ if (!groupMessage || groupMessage.id == null || !groupMessage.friendly_name) {
290
+ continue;
291
+ }
292
+ if (isDebugDevice(this.logCustomizations.debugDevices, String(groupMessage.id))) {
254
293
  this.adapter.log.warn(`--->>> fromZ2M -> ${groupMessage.id} exposes: ${JSON.stringify(groupMessage)}`);
255
294
  }
256
- await this.defineGroupDevice(groupMessage.friendly_name, `group_${groupMessage.id}`, groupMessage.scenes);
295
+ this.defineGroupDevice(groupMessage.friendly_name, `group_${groupMessage.id}`, groupMessage.scenes || []);
257
296
  }
258
297
  }
259
298
 
260
299
  /**
261
- *
300
+ * Legt alle Geräte und Gruppen aus den Caches als ioBroker-Objekte an
301
+ * bzw. aktualisiert sie falls sich Name oder Beschreibung geändert haben.
262
302
  */
263
303
  async createOrUpdateDevices() {
264
- for (const device of this.groupCache.concat(this.deviceCache)) {
265
- let deviceName = await this.getDeviceName(device);
266
- let description = await this.getDeviceDescription(device);
304
+ // Gruppen und Geräte getrennt verarbeiten
305
+ for (const device of this.groupCache) {
306
+ await this._createOrUpdateSingleDevice(device, true);
307
+ }
308
+ for (const device of this.deviceCache) {
309
+ await this._createOrUpdateSingleDevice(device, false);
310
+ }
311
+ }
267
312
 
268
- if (deviceName == '' && device.description) {
269
- deviceName = device.description;
270
- description = '';
271
- }
313
+ /**
314
+ * Legt ein einzelnes Gerät oder eine Gruppe als ioBroker-Objekt an oder aktualisiert es.
315
+ *
316
+ * @param {object} device Das Gerät/Gruppen-Objekt aus dem Cache
317
+ * @param {boolean} isGroup true = Gruppe, false = echtes Gerät
318
+ */
319
+ async _createOrUpdateSingleDevice(device, isGroup) {
320
+ let deviceName = this.getDeviceName(device);
321
+ let description = this.getDeviceDescription(device);
272
322
 
273
- // Manipulate deviceName if the device is disabled, so the update of the device is triggered as well
274
- if (device.disabled && device.disabled == true) {
275
- if (this.config.useEventInDesc == true) {
276
- description = 'Device is disabled!';
277
- } else {
278
- deviceName = `[Disabled] ${deviceName}`;
279
- }
323
+ if (deviceName === '' && device.description) {
324
+ deviceName = device.description;
325
+ description = '';
326
+ }
327
+
328
+ // Disabled-Flag nur bei echten Geräten – Gruppen können nicht disabled sein
329
+ if (!isGroup && device.disabled === true) {
330
+ if (this.config.useEventInDesc === true) {
331
+ description = 'Device is disabled!';
332
+ } else {
333
+ // Fallback auf ieee_address wenn kein friendly_name vorhanden
334
+ const label = deviceName || device.ieee_address;
335
+ deviceName = `[Disabled] ${label}`;
280
336
  }
337
+ }
281
338
 
282
- if (!this.createCache[device.ieee_address] || this.createCache[device.ieee_address].name != deviceName || this.createCache[device.ieee_address].description != description) {
283
- const deviceObj = {
284
- type: 'device',
285
- common: {
286
- icon: device.icon,
287
- name: deviceName,
288
- desc: description,
289
- statusStates: { onlineId: '' },
290
- },
291
- native: {
292
- deviceRemoved: false,
293
- groupDevice: false,
294
- },
295
- };
339
+ if (!this.createCache[device.ieee_address] ||
340
+ this.createCache[device.ieee_address].name !== deviceName ||
341
+ this.createCache[device.ieee_address].description !== description) {
296
342
 
297
- // Group Device
298
- if (device.ieee_address.includes('group_')) {
299
- deviceObj.native.groupDevice = true;
300
- deviceObj.common.statusStates.onlineId = `${this.adapter.name}.${this.adapter.instance}.${device.ieee_address}.available`;
301
- }
302
- // Disabled Device
303
- else if (device.disabled || device.disabled == true) {
304
- // Placeholder for possible later logic
305
- }
306
- // Only the onlineId is set if the device is not disabled
307
- else {
308
- deviceObj.common.statusStates.onlineId = `${this.adapter.name}.${this.adapter.instance}.${device.ieee_address}.available`;
309
- }
310
- await this.adapter.extendObjectAsync(device.ieee_address, deviceObj);
311
- this.createCache[device.ieee_address] = { name: deviceName, description: description };
343
+ const deviceObj = {
344
+ type: 'device',
345
+ common: {
346
+ icon: device.icon,
347
+ name: deviceName,
348
+ desc: description,
349
+ statusStates: { onlineId: '' },
350
+ },
351
+ native: {
352
+ deviceRemoved: false,
353
+ groupDevice: isGroup,
354
+ },
355
+ };
356
+
357
+ if (isGroup || device.disabled !== true) {
358
+ deviceObj.common.statusStates.onlineId = `${this.adapter.name}.${this.adapter.instance}.${device.ieee_address}.available`;
312
359
  }
313
360
 
314
- // Here it is checked whether the scenes match the current data from z2m.
315
- // If necessary, scenes are automatically deleted from ioBroker.
316
- const sceneStates = await this.adapter.getStatesAsync(`${device.ieee_address}.scene_*`);
317
- const sceneIDs = Object.keys(sceneStates);
318
- for (const sceneID of sceneIDs) {
319
- const stateID = sceneID.split('.')[3];
320
- if (device.states.find((x) => x.id == stateID) == null) {
321
- this.adapter.delObject(sceneID);
322
- }
361
+ await this.adapter.extendObjectAsync(device.ieee_address, deviceObj);
362
+ // Bestehende State-Einträge im Cache bewahren nur name/description aktualisieren
363
+ if (!this.createCache[device.ieee_address]) {
364
+ this.createCache[device.ieee_address] = {};
323
365
  }
366
+ this.createCache[device.ieee_address].name = deviceName;
367
+ this.createCache[device.ieee_address].description = description;
368
+ }
324
369
 
325
- if (device != undefined && device.states != undefined) {
326
- for (const state of device.states) {
327
- if (state && (!this.createCache[device.ieee_address][state.id] || this.createCache[device.ieee_address][state.id].name != state.name)) {
328
- const iobState = {
329
- type: 'state',
330
- common: await this.copyAndCleanStateObj(state),
331
- native: {},
332
- };
370
+ if (!Array.isArray(device.states)) {
371
+ return;
372
+ }
333
373
 
334
- await this.adapter.extendObjectAsync(`${device.ieee_address}.${state.id}`, iobState);
335
- this.createCache[device.ieee_address][state.id] = {name: state.name, created: true};
336
- }
374
+ // Veraltete scene_*-States bereinigen – getForeignObjectsAsync mit vollem Namespace-Pfad
375
+ const sceneObjects = await this.adapter.getForeignObjectsAsync(
376
+ `${this.adapter.namespace}.${device.ieee_address}.scene_*`
377
+ );
378
+ if (sceneObjects) {
379
+ for (const sceneObjId of Object.keys(sceneObjects)) {
380
+ const parts = sceneObjId.split('.');
381
+ const stateID = parts[parts.length - 1];
382
+ if (!device.states.find((x) => x.id === stateID)) {
383
+ await this.adapter.delForeignObjectAsync(sceneObjId);
337
384
  }
338
385
  }
339
386
  }
340
- }
341
387
 
342
- /**
343
- *
344
- * @param messageObj
345
- */
346
- async renameDeviceInCache(messageObj) {
347
- const renamedDevice = this.groupCache
348
- .concat(this.deviceCache)
349
- .find((x) => x.id == messageObj.payload.data.from);
350
- if (renamedDevice) {
351
- renamedDevice.id = messageObj.payload.data.to;
388
+ for (const state of device.states) {
389
+ if (state && (!this.createCache[device.ieee_address][state.id] ||
390
+ this.createCache[device.ieee_address][state.id].name !== state.name)) {
391
+
392
+ const iobState = {
393
+ type: 'state',
394
+ common: this.copyAndCleanStateObj(state),
395
+ native: {},
396
+ };
397
+
398
+ await this.adapter.extendObjectAsync(`${device.ieee_address}.${state.id}`, iobState);
399
+ this.createCache[device.ieee_address][state.id] = { name: state.name, created: true };
400
+ }
352
401
  }
353
402
  }
354
403
 
355
404
  /**
356
- *
405
+ * Prüft alle in ioBroker vorhandenen Geräte-Objekte gegen den deviceCache und markiert
406
+ * nicht mehr vorhandene Geräte als "[Removed]" bzw. setzt available auf false.
357
407
  */
358
408
  async checkAndProgressDeviceRemove() {
359
- let description = '';
360
- let deviceName = '';
361
409
  let iobDevices = await this.adapter.getDevicesAsync();
362
- // Do not consider devices already marked as "deviceRemoved"
363
- iobDevices = iobDevices.filter((x) => x.native.deviceRemoved == false);
364
- // Do not consider groups
365
- iobDevices = iobDevices.filter((x) => x.native.groupDevice == false);
410
+ if (!iobDevices) {
411
+ return;
412
+ }
413
+ // Nur nicht-entfernte, echte Geräte (keine Gruppen) in einem Schritt filtern
414
+ iobDevices = iobDevices.filter((x) =>
415
+ x.native && x.native.deviceRemoved === false && x.native.groupDevice === false
416
+ );
366
417
 
367
418
  for (const iobDevice of iobDevices) {
368
- const ieee_address = iobDevice._id.split('.')[2];
369
- //Check whether the devices found from the object tree are also available in the DeviceCache
370
- if (!this.deviceCache.find((x) => x.ieee_address == ieee_address)) {
371
- deviceName = iobDevice.common.name;
419
+ const idParts = iobDevice._id.split('.');
420
+ if (idParts.length < 3) {continue;}
421
+ const ieee_address = idParts[2];
422
+
423
+ // Gruppen-Einträge im ioBroker-Baum explizit überspringen
424
+ if (ieee_address.startsWith('group_')) {continue;}
425
+
426
+ if (!this.deviceCache.find((x) => x.ieee_address === ieee_address)) {
427
+ let deviceName = iobDevice.common && iobDevice.common.name ? iobDevice.common.name : ieee_address;
428
+ let description = '';
372
429
 
373
- if (this.config.useEventInDesc == true) {
430
+ if (this.config.useEventInDesc === true) {
374
431
  description = 'Device was removed!';
375
432
  } else {
376
433
  deviceName = `[Removed] ${deviceName}`;
377
434
  }
378
435
 
379
- this.adapter.extendObject(`${ieee_address}`, {
380
- common: {
381
- name: deviceName,
382
- desc: description,
383
- },
384
- native: {
385
- deviceRemoved: true,
386
- },
436
+ await this.adapter.extendObjectAsync(ieee_address, {
437
+ common: { name: deviceName, desc: description },
438
+ native: { deviceRemoved: true },
387
439
  });
388
- this.adapter.setStateChangedAsync(`${ieee_address}.available`, false, true);
440
+ await this.adapter.setStateChangedAsync(`${ieee_address}.available`, false, true);
389
441
 
390
442
  delete this.createCache[ieee_address];
391
443
  }
@@ -393,22 +445,46 @@ class DeviceController {
393
445
  }
394
446
 
395
447
  /**
448
+ * Aktualisiert die Geräte-ID im Cache wenn ein Gerät in Zigbee2MQTT umbenannt wurde.
449
+ *
450
+ * @param {{ payload: { data: { from: string, to: string } } }} messageObj Die Rename-Nachricht
451
+ */
452
+ async renameDeviceInCache(messageObj) {
453
+ if (!messageObj.payload || !messageObj.payload.data) {
454
+ return;
455
+ }
456
+ if (!messageObj.payload.data.from || !messageObj.payload.data.to) {
457
+ return;
458
+ }
459
+ const renamedDevice = this.groupCache
460
+ .concat(this.deviceCache)
461
+ .find((x) => x.id === messageObj.payload.data.from);
462
+ if (renamedDevice) {
463
+ renamedDevice.id = messageObj.payload.data.to;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Entfernt ein Gerät anhand seiner IEEE-Adresse aus dem angegebenen Cache-Array.
396
469
  *
397
- * @param devices
398
- * @param ieee_address
470
+ * @param {Array} devices Das Cache-Array (deviceCache oder groupCache)
471
+ * @param {string} ieee_address Die IEEE-Adresse des zu entfernenden Geräts
399
472
  */
400
473
  removeDeviceByIeee(devices, ieee_address) {
401
- const idx = devices.findIndex((x) => x.ieee_address == ieee_address);
474
+ const idx = devices.findIndex((x) => x.ieee_address === ieee_address);
402
475
  if (idx > -1) {
403
476
  devices.splice(idx, 1);
404
477
  }
405
478
  }
406
479
 
407
480
  /**
481
+ * Erstellt eine bereinigte Kopie eines State-Objekts ohne interne Laufzeit-Felder
482
+ * (getter, setter, prop, etc.) für die ioBroker-Objektdefinition.
408
483
  *
409
- * @param state
484
+ * @param {object} state Das vollständige State-Objekt mit Laufzeit-Feldern
485
+ * @returns {object} Das bereinigte State-Objekt für extendObjectAsync
410
486
  */
411
- async copyAndCleanStateObj(state) {
487
+ copyAndCleanStateObj(state) {
412
488
  const iobState = { ...state };
413
489
  const blacklistedKeys = [
414
490
  'prop',
@@ -430,33 +506,44 @@ class DeviceController {
430
506
  }
431
507
 
432
508
  /**
509
+ * Gibt den Anzeigenamen eines Geräts zurück.
510
+ * Ist friendly_name gleich der IEEE-Adresse, wird ein leerer String zurückgegeben.
433
511
  *
434
- * @param device
512
+ * @param {object} device Das Geräteobjekt aus dem Cache
513
+ * @returns {string} Der Anzeigename oder ""
435
514
  */
436
515
  getDeviceName(device) {
437
- return device.id == device.ieee_address ? '' : device.id;
516
+ return device.id === device.ieee_address ? '' : device.id;
438
517
  }
439
518
 
440
519
  /**
520
+ * Gibt die Beschreibung eines Geräts zurück (oder "" wenn keine vorhanden).
441
521
  *
442
- * @param device
522
+ * @param {object} device Das Geräteobjekt aus dem Cache
523
+ * @returns {string} Die Beschreibung oder ""
443
524
  */
444
525
  getDeviceDescription(device) {
445
526
  return device.description ? device.description : '';
446
527
  }
447
528
 
448
529
  /**
530
+ * Verarbeitet die Antwort eines Coordinator-Check-Requests von Zigbee2MQTT
531
+ * und schreibt fehlende Router in die info-States.
449
532
  *
450
- * @param payload
533
+ * @param {{ data: { missing_routers: Array } }} payload Der Antwort-Payload
451
534
  */
452
- processCoordinatorCheck(payload) {
535
+ async processCoordinatorCheck(payload) {
453
536
  if (payload && payload.data && payload.data.missing_routers) {
454
537
  const missingRoutersCount = payload.data.missing_routers.length;
455
- this.adapter.setState('info.missing_routers', JSON.stringify(payload.data.missing_routers), true);
456
- this.adapter.setState('info.missing_routers_count', missingRoutersCount, true);
538
+ await this.adapter.setStateAsync('info.missing_routers', JSON.stringify(payload.data.missing_routers), true);
539
+ await this.adapter.setStateAsync('info.missing_routers_count', missingRoutersCount, true);
457
540
 
458
541
  if (missingRoutersCount > 0) {
459
- this.adapter.log[this.config.coordinatorCheckLogLvl](
542
+ const logLvl = this.config.coordinatorCheckLogLvl;
543
+ const logFn = (logLvl && typeof this.adapter.log[logLvl] === 'function')
544
+ ? this.adapter.log[logLvl].bind(this.adapter.log)
545
+ : this.adapter.log.warn.bind(this.adapter.log);
546
+ logFn(
460
547
  `Coordinator check: ${missingRoutersCount} missing routers were found, please check the data point 'zigbee2mqtt.x.info.missing_routers'!`
461
548
  );
462
549
  } else {