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.
@@ -1,18 +1,20 @@
1
+ 'use strict';
2
+
1
3
  const utils = require('./utils');
2
- const incStatsQueue = [];
3
- const timeOutCache = {};
4
4
 
5
5
  /**
6
- *
6
+ * Verwaltet das Schreiben von Zigbee2MQTT-Gerätedaten in ioBroker-States.
7
+ * Puffert eingehende Nachrichten für noch nicht erstellte Geräte in einer Queue.
7
8
  */
8
9
  class StatesController {
9
10
  /**
11
+ * Erstellt eine neue StatesController-Instanz.
10
12
  *
11
- * @param adapter
12
- * @param deviceCache
13
- * @param groupCache
14
- * @param logCustomizations
15
- * @param createCache
13
+ * @param {object} adapter Die ioBroker-Adapter-Instanz
14
+ * @param {Array} deviceCache Gemeinsamer Cache aller bekannten Geräte
15
+ * @param {Array} groupCache Gemeinsamer Cache aller bekannten Gruppen
16
+ * @param {object} logCustomizations Debug/Filter-Einstellungen (debugDevices, logfilter)
17
+ * @param {object} createCache Cache bereits erstellter ioBroker-Objekte
16
18
  */
17
19
  constructor(adapter, deviceCache, groupCache, logCustomizations, createCache) {
18
20
  this.adapter = adapter;
@@ -20,45 +22,99 @@ class StatesController {
20
22
  this.deviceCache = deviceCache;
21
23
  this.logCustomizations = logCustomizations;
22
24
  this.createCache = createCache;
25
+ this.incStatsQueue = [];
26
+ this.timeOutCache = {};
27
+ // Einmalig berechnen – wird nur bei Konfigurationsänderung ungültig
28
+ this._debugDeviceList = logCustomizations.debugDevices
29
+ ? String(logCustomizations.debugDevices).split(',').map((s) => s.trim()).filter(Boolean)
30
+ : [];
23
31
  }
24
32
 
25
33
  /**
34
+ * Verarbeitet eine eingehende Gerätenachricht von Zigbee2MQTT.
35
+ * Ist das Gerät noch nicht im Cache bekannt, wird die Nachricht in der incStatsQueue
36
+ * gepuffert und später über processQueue() erneut versucht.
26
37
  *
27
- * @param messageObj
38
+ * @param {{ topic: string, payload: any }} messageObj Die zu verarbeitende Nachricht
28
39
  */
29
- processDeviceMessage(messageObj) {
30
- // Is payload present?
31
- if (messageObj.payload == ''|| messageObj.payload == undefined || messageObj.payload == null) {
40
+ async processDeviceMessage(messageObj) {
41
+ if (!messageObj || typeof messageObj !== 'object') {
42
+ return;
43
+ }
44
+ if (messageObj.payload === '' || messageObj.payload === undefined || messageObj.payload === null) {
32
45
  return;
33
46
  }
34
47
 
35
- const device = this.groupCache.concat(this.deviceCache).find((x) => x.id == messageObj.topic);
48
+ const device = this.groupCache.concat(this.deviceCache).find((x) => x.id === messageObj.topic);
36
49
  if (device) {
37
50
  try {
38
- this.setDeviceStateSafely(messageObj, device);
51
+ await this.setDeviceStateSafely(messageObj, device);
39
52
  } catch (error) {
40
- this.adapter.log.error(error);
53
+ this.adapter.log.error(`setDeviceStateSafely error for ${messageObj.topic}: ${error}`);
41
54
  }
42
55
  } else {
43
- // incStatsQueue[incStatsQueue.length] = messageObj;
44
- if (!incStatsQueue.some((x) => x && x.topic === messageObj.topic)) {
45
- incStatsQueue.push(messageObj);
56
+ // Wenn das Gerät (noch) nicht bekannt ist: Message in Queue stellen.
57
+ // Existiert für dieses Topic bereits ein Eintrag, wird er mit den aktuellen
58
+ // Payload-Daten überschrieben, damit wir stets den neuesten Stand verarbeiten.
59
+ const existingIdx = this.incStatsQueue.findIndex((x) => x && x.topic === messageObj.topic);
60
+ if (existingIdx !== -1) {
61
+ const ttl = (this.incStatsQueue[existingIdx]._ttl || 0) + 1;
62
+ if (ttl > 10) {
63
+ this.adapter.log.warn(`incStatsQueue: dropping message for unknown device ${messageObj.topic} after ${ttl} retries`);
64
+ this.incStatsQueue.splice(existingIdx, 1);
65
+ return;
66
+ }
67
+ this.incStatsQueue[existingIdx] = { ...messageObj, _ttl: ttl };
68
+ } else {
69
+ if (this.incStatsQueue.length < 500) {
70
+ this.incStatsQueue.push({ ...messageObj, _ttl: 1 });
71
+ } else {
72
+ this.adapter.log.warn(`incStatsQueue is full (500), dropping message for ${messageObj.topic}`);
73
+ }
46
74
  }
47
75
  this.adapter.log.debug(`Device: ${messageObj.topic} not found, queue state in incStatsQueue!`);
48
76
  }
49
77
  }
50
78
 
51
79
  /**
80
+ * Schreibt alle State-Werte einer Nachricht in die zugehörigen ioBroker-States.
81
+ * Action-States werden gesammelt und am Ende gesondert behandelt.
52
82
  *
53
- * @param messageObj
54
- * @param device
83
+ * @param {{ topic: string, payload: object }} messageObj Die zu verarbeitende Nachricht
84
+ * @param {object} device Das zugehörige Geräteobjekt aus dem Cache
55
85
  */
56
86
  async setDeviceStateSafely(messageObj, device) {
57
- if (this.logCustomizations.debugDevices.includes(device.ieee_address)) {
87
+ if (this._debugDeviceList.includes(device.ieee_address)) {
58
88
  this.adapter.log.warn(`--->>> fromZ2M -> ${device.ieee_address} states: ${JSON.stringify(messageObj)}`);
59
89
  }
60
90
 
61
91
  const actionStates = [];
92
+ // Fix 1: Flag damit messageObj nur EINMAL in die Queue kommt, egal wie viele
93
+ // States noch nicht im createCache sind (verhindert N-faches Requeue)
94
+ let queuedThisRound = false;
95
+
96
+ const pushToQueue = (msg) => {
97
+ if (queuedThisRound) {return;}
98
+ const existingIdx = this.incStatsQueue.findIndex((x) => x && x.topic === msg.topic);
99
+ if (existingIdx !== -1) {
100
+ const ttl = (this.incStatsQueue[existingIdx]._ttl || 0) + 1;
101
+ if (ttl > 10) {
102
+ this.adapter.log.warn(`incStatsQueue: dropping message for ${msg.topic} after ${ttl} retries (state not yet created)`);
103
+ this.incStatsQueue.splice(existingIdx, 1);
104
+ return;
105
+ }
106
+ this.incStatsQueue[existingIdx] = { ...msg, _ttl: ttl };
107
+ } else if (this.incStatsQueue.length < 500) {
108
+ this.incStatsQueue.push({ ...msg, _ttl: 1 });
109
+ } else {
110
+ this.adapter.log.warn(`incStatsQueue is full, dropping message for ${msg.topic}`);
111
+ }
112
+ queuedThisRound = true;
113
+ };
114
+
115
+ if (!messageObj.payload || typeof messageObj.payload !== 'object' || Array.isArray(messageObj.payload)) {
116
+ return;
117
+ }
62
118
 
63
119
  for (let [key, value] of Object.entries(messageObj.payload)) {
64
120
  if (value === undefined || value === null) {
@@ -69,12 +125,12 @@ class StatesController {
69
125
  return state.prop && state.prop === key;
70
126
  });
71
127
 
72
- if (states.length == 0) {
73
- states = device.states.filter((x) => x.id == key);
128
+ if (states.length === 0) {
129
+ states = device.states.filter((x) => x.id === key);
74
130
  }
75
131
 
76
- if (states.length == 0) {
77
- if (key == 'device' || device.ieee_address.includes('group')) {
132
+ if (states.length === 0) {
133
+ if (key === 'device' || device.ieee_address.includes('group')) {
78
134
  // do nothing
79
135
  } else {
80
136
  // some devices has addition information in payload
@@ -92,16 +148,16 @@ class StatesController {
92
148
  common: {
93
149
  name: key,
94
150
  role: 'state',
95
- type: value !== null ? typeof value : 'mixed',
151
+ type: typeof value,
96
152
  write: false,
97
153
  read: true,
98
154
  },
99
155
  native: {},
100
156
  });
101
- if (typeof value == 'object') {
157
+ if (typeof value === 'object') {
102
158
  value = JSON.stringify(value);
103
159
  }
104
- this.adapter.setState(`${fullPath}.${key}`, value, true);
160
+ await this.adapter.setStateAsync(`${fullPath}.${key}`, value, true);
105
161
  }
106
162
  continue;
107
163
  }
@@ -114,26 +170,22 @@ class StatesController {
114
170
  await this.setStateSafelyAsync(`${device.ieee_address}.available`, true);
115
171
  }
116
172
 
117
- // It may be that the state has not yet been created!
173
+ // State noch nicht erstellt? einmal in Queue legen und weiter
118
174
  if (!this.createCache[device.ieee_address]
119
- || !this.createCache[device.ieee_address][state.id]
120
- || !this.createCache[device.ieee_address][state.id].created == true) {
121
- incStatsQueue[incStatsQueue.length] = messageObj;
175
+ || !this.createCache[device.ieee_address][state.id]
176
+ || this.createCache[device.ieee_address][state.id].created !== true) {
177
+ pushToQueue(messageObj);
122
178
  continue;
123
179
  }
124
180
 
125
181
  try {
126
182
  // Is an action
127
- if (state.prop && state.prop == 'action') {
183
+ if (state.prop && state.prop === 'action') {
128
184
  actionStates.push(state);
129
185
  }
130
- // Is not an action
131
- // check if its a motion sensor (occupancy state) and if configuration is set to update state every time
132
- // if yes, use setStateSafelyAsync instead of setStateChangedSafelyAsync
133
- else if (this.adapter.config.allwaysUpdateOccupancyState === true && state.id === 'occupancy' && value === true) {
186
+ else if (this.adapter.config.allwaysUpdateOccupancyState === true && state.id === 'occupancy' && value === true) {
134
187
  await this.setStateSafelyAsync(stateName, value);
135
188
  }
136
- // end section for motion sensor update
137
189
  else {
138
190
  if (state.getter) {
139
191
  await this.setStateChangedSafelyAsync(stateName, state.getter(messageObj.payload));
@@ -142,22 +194,21 @@ class StatesController {
142
194
  }
143
195
  }
144
196
  } catch (err) {
145
- incStatsQueue[incStatsQueue.length] = messageObj;
146
- this.adapter.log.debug(`Can not set ${stateName}, queue state in incStatsQueue!`);
197
+ this.adapter.log.warn(`Cannot set state ${stateName}: ${err}`);
147
198
  }
148
199
  }
149
200
  }
150
201
 
202
+
151
203
  for (const state of actionStates) {
152
204
  const stateName = `${device.ieee_address}.${state.id}`;
153
205
 
154
206
  try {
155
207
  const getterPayload = state.getter(messageObj.payload);
156
- if (getterPayload != undefined) {
157
- if (state.isEvent && state.isEvent == true) {
158
- if (state.type == 'boolean') {
208
+ if (getterPayload !== undefined) {
209
+ if (state.isEvent && state.isEvent === true) {
210
+ if (state.type === 'boolean') {
159
211
  await this.setStateWithTimeoutAsync(stateName, getterPayload, 250);
160
-
161
212
  } else {
162
213
  await this.setStateSafelyAsync(stateName, getterPayload);
163
214
  }
@@ -166,16 +217,17 @@ class StatesController {
166
217
  }
167
218
  }
168
219
  } catch (err) {
169
- incStatsQueue[incStatsQueue.length] = messageObj;
170
- this.adapter.log.debug(`Can not set ${stateName}, queue state in incStatsQueue!`);
220
+ this.adapter.log.warn(`Cannot set action state ${stateName}: ${err}`);
171
221
  }
172
222
  }
173
223
  }
174
224
 
175
225
  /**
226
+ * Setzt einen ioBroker-State (immer, ohne Changed-Prüfung).
227
+ * Ignoriert null/undefined-Werte sicher.
176
228
  *
177
- * @param stateName
178
- * @param value
229
+ * @param {string} stateName Vollständiger State-Pfad (z.B. "0xAABB.state")
230
+ * @param {*} value Der zu setzende Wert
179
231
  */
180
232
  async setStateSafelyAsync(stateName, value) {
181
233
  if (value === undefined || value === null) {
@@ -185,9 +237,11 @@ class StatesController {
185
237
  }
186
238
 
187
239
  /**
240
+ * Setzt einen ioBroker-State nur wenn sich der Wert geändert hat.
241
+ * Ignoriert null/undefined-Werte sicher.
188
242
  *
189
- * @param stateName
190
- * @param value
243
+ * @param {string} stateName Vollständiger State-Pfad (z.B. "0xAABB.brightness")
244
+ * @param {*} value Der zu setzende Wert
191
245
  */
192
246
  async setStateChangedSafelyAsync(stateName, value) {
193
247
  if (value === undefined || value === null) {
@@ -197,10 +251,13 @@ class StatesController {
197
251
  }
198
252
 
199
253
  /**
254
+ * Setzt einen State sofort auf den angegebenen Wert und – nur bei value=true –
255
+ * nach Ablauf des Timeouts automatisch zurück auf false (Button/Event-Reset).
256
+ * Bei value=false wird kein Auto-Reset ausgelöst (z.B. brightness_stop-Signal).
200
257
  *
201
- * @param stateName
202
- * @param value
203
- * @param timeout
258
+ * @param {string} stateName Vollständiger State-Pfad
259
+ * @param {boolean} value Der sofort zu setzende Wert
260
+ * @param {number} timeout Millisekunden bis zum Auto-Reset (nur bei value=true)
204
261
  */
205
262
  async setStateWithTimeoutAsync(stateName, value, timeout) {
206
263
  if (value === undefined || value === null) {
@@ -208,33 +265,50 @@ class StatesController {
208
265
  }
209
266
 
210
267
  await this.adapter.setStateAsync(stateName, value, true);
211
- if (timeOutCache[stateName]) {
212
- clearTimeout(timeOutCache[stateName]);
268
+
269
+ // Auto-Reset (false → nichts tun, true → nach timeout zurücksetzen)
270
+ // Wenn value=false (z.B. Stop-Aktion im simpleMoveStopState-Modus),
271
+ // soll der State dauerhaft false bleiben und NICHT nach timeout auf true springen.
272
+ if (this.timeOutCache[stateName]) {
273
+ this.adapter.clearTimeout(this.timeOutCache[stateName]);
274
+ delete this.timeOutCache[stateName];
275
+ }
276
+ if (value === true) {
277
+ this.timeOutCache[stateName] = this.adapter.setTimeout(() => {
278
+ delete this.timeOutCache[stateName];
279
+ this.adapter.setStateAsync(stateName, false, true).catch((err) => {
280
+ this.adapter.log.debug(`setStateWithTimeout reset error for ${stateName}: ${err}`);
281
+ });
282
+ }, timeout);
213
283
  }
214
- timeOutCache[stateName] = setTimeout(() => {
215
- this.adapter.setStateAsync(stateName, !value, true);
216
- }, timeout);
217
284
  }
218
285
 
219
286
  /**
220
- *
287
+ * Verarbeitet alle in der incStatsQueue gepufferten Nachrichten erneut.
288
+ * Wird nach dem Aufbau des Geräte-/Gruppen-Caches aufgerufen.
221
289
  */
222
- processQueue() {
290
+ async processQueue() {
223
291
  const oldIncStatsQueue = [];
224
- utils.moveArray(incStatsQueue, oldIncStatsQueue);
292
+ utils.moveArray(this.incStatsQueue, oldIncStatsQueue);
225
293
  while (oldIncStatsQueue.length > 0) {
226
- this.processDeviceMessage(oldIncStatsQueue.shift());
294
+ // seriell abarbeiten – nicht parallel feuern
295
+ await this.processDeviceMessage(oldIncStatsQueue.shift());
227
296
  }
228
297
  }
229
298
 
230
299
  /**
231
- *
300
+ * Meldet alle bisherigen State-Subscriptions ab und subscribt neu
301
+ * nur auf beschreibbare States aller bekannten Geräte und Gruppen.
232
302
  */
233
- async subscribeWritableStates() {
234
- await this.adapter.unsubscribeObjectsAsync('*');
303
+ subscribeWritableStates() {
304
+ // Alle bestehenden State-Subscriptions zuerst abmelden
305
+ this.adapter.unsubscribeStates('*');
235
306
  for (const device of this.groupCache.concat(this.deviceCache)) {
307
+ if (!device || !Array.isArray(device.states)) {
308
+ continue;
309
+ }
236
310
  for (const state of device.states) {
237
- if (state.write == true) {
311
+ if (state && state.write === true) {
238
312
  this.adapter.subscribeStates(`${device.ieee_address}.${state.id}`);
239
313
  }
240
314
  }
@@ -245,22 +319,28 @@ class StatesController {
245
319
  }
246
320
 
247
321
  /**
248
- *
322
+ * Setzt alle "*.available"-States im Adapter auf false.
323
+ * Wird beim Verbindungsverlust zu Zigbee2MQTT aufgerufen.
249
324
  */
250
325
  async setAllAvailableToFalse() {
251
326
  const availableStates = await this.adapter.getStatesAsync('*.available');
252
- for (const availableState in availableStates) {
327
+ if (!availableStates) {
328
+ return;
329
+ }
330
+ for (const availableState of Object.keys(availableStates)) {
253
331
  await this.adapter.setStateChangedAsync(availableState, false, true);
254
332
  }
255
333
  }
256
334
 
257
335
  /**
258
- *
336
+ * Bricht alle laufenden Auto-Reset-Timer ab und leert den Timer-Cache.
337
+ * Wird beim Adapter-Stop aufgerufen.
259
338
  */
260
- async allTimerClear() {
261
- for (const timer in timeOutCache) {
262
- clearTimeout(timeOutCache[timer]);
339
+ allTimerClear() {
340
+ for (const timer of Object.keys(this.timeOutCache)) {
341
+ this.adapter.clearTimeout(this.timeOutCache[timer]);
263
342
  }
343
+ this.timeOutCache = {};
264
344
  }
265
345
  }
266
346
 
package/lib/utils.js CHANGED
@@ -1,68 +1,43 @@
1
1
  /**
2
- * Converts a bulb level of range [0...254] to an adapter level of range [0...100]
2
+ * Konvertiert einen Lampen-Helligkeitswert [0..254] in einen Adapter-Prozentwert [0..100].
3
3
  *
4
- * @param bulbLevel
4
+ * @param {number} bulbLevel Helligkeitswert der Lampe (0–254)
5
+ * @returns {number} Prozentwert (0–100)
5
6
  */
6
7
  function bulbLevelToAdapterLevel(bulbLevel) {
7
- // Convert from bulb levels [0...254] to adapter levels [0...100]:
8
- // - Bulb level 0 is a forbidden value according to the ZigBee spec "ZigBee Cluster Library
9
- // (for ZigBee 3.0) User Guide", but some bulbs (HUE) accept this value and interpret this
10
- // value as "switch the bulb off".
11
- // - A bulb level of "1" is the "minimum possible level" which should mean "bulb off",
12
- // but there are bulbs that do not switch off (they need "0", some IKEA bulbs are affected).
13
- // - No visible difference was seen between bulb level 1 and 2 on HUE LCT012 bulbs.
14
- //
15
- // Conclusion:
16
- // - We map adapter level "0" to the (forbidden) bulb level "0" that seems to switch all
17
- // known bulbs.
18
- // - Bulb level "1" is not used, but if received nevertheless, it is converted to
19
- // adapter level "0" (off).
20
- // - Bulb level range [2...254] is linearly mapped to adapter level range [1...100].
21
8
  if (bulbLevel >= 2) {
22
9
  // Perform linear mapping of range [2...254] to [1...100]
23
10
  return Math.round(((bulbLevel - 2) * 99) / 252) + 1;
24
- }
25
- // The bulb is considered off. Even a bulb level of "1" is considered as off.
26
- return 0;
27
- // else
11
+ }
12
+ // The bulb is considered off. Even a bulb level of "1" is considered as off.
13
+ return 0;
28
14
  }
29
15
 
30
16
  /**
31
- * Converts an adapter level of range [0...100] to a bulb level of range [0...254]
17
+ * Konvertiert einen Adapter-Prozentwert [0..100] in einen Lampen-Helligkeitswert [0..254].
32
18
  *
33
- * @param adapterLevel
19
+ * @param {number} adapterLevel Prozentwert (0–100)
20
+ * @returns {number} Helligkeitswert der Lampe (0–254)
34
21
  */
35
22
  function adapterLevelToBulbLevel(adapterLevel) {
36
- // Convert from adapter levels [0...100] to bulb levels [0...254].
37
- // This is the inverse of function bulbLevelToAdapterLevel().
38
- // Please read the comments there regarding the rules applied here for mapping the values.
39
- if (adapterLevel) {
23
+ if (adapterLevel != null && adapterLevel > 0) {
40
24
  // Perform linear mapping of range [1...100] to [2...254]
41
25
  return Math.round(((adapterLevel - 1) * 252) / 99) + 2;
42
- }
43
- // Switch the bulb off. Some bulbs need "0" (IKEA), others "1" (HUE), and according to the
44
- // ZigBee docs "1" is the "minimum possible level"... we choose "0" here which seems to work.
45
- return 0;
46
- // else
47
- }
48
-
49
- /**
50
- *
51
- * @param ba
52
- */
53
- function bytesArrayToWordArray(ba) {
54
- const wa = [];
55
- for (let i = 0; i < ba.length; i++) {
56
- wa[(i / 2) | 0] |= ba[i] << (8 * (i % 2));
57
26
  }
58
- return wa;
27
+ // Switch the bulb off. Some bulbs need "0" (IKEA), others "1" (HUE), and according to the
28
+ // ZigBee docs "1" is the "minimum possible level"... we choose "0" here which seems to work.
29
+ return 0;
59
30
  }
60
31
 
32
+
61
33
  // If the value is greater than 1000, kelvin is assumed.
62
34
  // If smaller, it is assumed to be mired.
63
35
  /**
36
+ * Konvertiert einen Kelvin- oder Mired-Wert immer in Mired.
37
+ * Werte > 1000 werden als Kelvin interpretiert und umgerechnet.
64
38
  *
65
- * @param t
39
+ * @param {number} t Farbtemperatur in Kelvin (>1000) oder Mired (≤1000)
40
+ * @returns {number} Farbtemperatur in Mired
66
41
  */
67
42
  function toMired(t) {
68
43
  let miredValue = t;
@@ -73,22 +48,25 @@ function toMired(t) {
73
48
  }
74
49
 
75
50
  /**
51
+ * Konvertiert zwischen Mired und Kelvin (die Formel ist in beide Richtungen gleich: 1.000.000 / t).
76
52
  *
77
- * @param t
53
+ * @param {number} t Farbtemperatur in Mired oder Kelvin
54
+ * @returns {number} Umgerechneter Wert (gerundet)
78
55
  */
79
56
  function miredKelvinConversion(t) {
80
57
  return Math.round(1000000 / t);
81
58
  }
82
59
 
83
60
  /**
84
- * Converts a decimal number to a hex string with zero-padding
61
+ * Konvertiert eine Dezimalzahl in einen hex-String mit führenden Nullen.
85
62
  *
86
- * @param decimal
87
- * @param padding
63
+ * @param {number} decimal Die umzuwandelnde Zahl
64
+ * @param {number} [padding] Mindestlänge des hex-Strings (wird mit '0' aufgefüllt)
65
+ * @returns {string} Hex-String in Kleinbuchstaben
88
66
  */
89
67
  function decimalToHex(decimal, padding) {
90
68
  let hex = Number(decimal).toString(16);
91
- padding = typeof padding === 'undefined' || padding === null ? (padding = 2) : padding;
69
+ padding = typeof padding === 'undefined' || padding === null ? 2 : padding;
92
70
 
93
71
  while (hex.length < padding) {
94
72
  hex = `0${hex}`;
@@ -98,81 +76,43 @@ function decimalToHex(decimal, padding) {
98
76
  }
99
77
 
100
78
  /**
79
+ * Leert ein Array in-place (O(1)).
101
80
  *
102
- * @param adapterDevId
103
- */
104
- function getZbId(adapterDevId) {
105
- const idx = adapterDevId.indexOf('group');
106
- if (idx > 0) {
107
- return adapterDevId.substr(idx + 6);
108
- }
109
- return `0x${ adapterDevId.split('.')[2]}`;
110
- }
111
-
112
- /**
113
- *
114
- * @param adapter
115
- * @param id
116
- */
117
- function getAdId(adapter, id) {
118
- return `${adapter.namespace }.${ id.split('.')[2]}`; // iobroker device id
119
- }
120
-
121
- /**
122
- *
123
- * @param array
81
+ * @param {Array} array Das zu leerende Array
124
82
  */
125
83
  function clearArray(array) {
126
- while (array.length > 0) {
127
- array.pop();
128
- }
84
+ array.length = 0;
129
85
  }
130
86
 
131
87
  /**
88
+ * Verschiebt alle Einträge von source nach target und leert source dabei.
132
89
  *
133
- * @param source
134
- * @param target
90
+ * @param {Array} source Quell-Array (wird nach dem Aufruf leer sein)
91
+ * @param {Array} target Ziel-Array (bekommt alle Einträge aus source angehängt)
135
92
  */
136
93
  function moveArray(source, target) {
137
- while (source.length > 0) {
138
- target.push(source.shift());
139
- }
94
+ target.push(...source);
95
+ source.length = 0;
140
96
  }
141
97
 
142
98
  /**
99
+ * Prüft ob ein Wert ein einfaches Objekt ist (kein Array, kein null).
143
100
  *
144
- * @param item
101
+ * @param {*} item Der zu prüfende Wert
102
+ * @returns {boolean} true wenn item ein Objekt ist, sonst false
145
103
  */
146
104
  function isObject(item) {
147
105
  return typeof item === 'object' && !Array.isArray(item) && item !== null;
148
106
  }
149
107
 
150
- /**
151
- *
152
- * @param item
153
- */
154
- function isJson(item) {
155
- let value = typeof item !== 'string' ? JSON.stringify(item) : item;
156
- try {
157
- value = JSON.parse(value);
158
- } catch (e) {
159
- return false;
160
- }
161
-
162
- return typeof value === 'object' && value !== null;
163
- }
164
108
 
165
109
  module.exports = {
166
110
  bulbLevelToAdapterLevel,
167
111
  adapterLevelToBulbLevel,
168
- bytesArrayToWordArray,
169
112
  toMired,
170
113
  miredKelvinConversion,
171
114
  decimalToHex,
172
- getZbId,
173
- getAdId,
174
115
  clearArray,
175
116
  moveArray,
176
117
  isObject,
177
- isJson,
178
118
  };