iobroker.zigbee2mqtt 3.1.1 → 3.2.0

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,42 +2,49 @@ 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
- if (!modelName) {return 'NA';}
22
- const modelNameString = modelName.replace('/', '_');
25
+ if (!modelName) { return 'NA'; }
23
26
  // eslint-disable-next-line no-control-regex
24
- return modelNameString.replace(/\u0000/g, '');
27
+ return modelName.replace(/\//g, '_').replace(/\u0000/g, '');
25
28
  }
26
29
 
27
30
  /**
31
+ * Bereinigt einen Z2M-Gerätenamen für die Verwendung in einer URL.
28
32
  *
29
- * @param deviceName
33
+ * @param {string} deviceName Der zu bereinigende Gerätename
34
+ * @returns {string} Bereinigter Name oder 'NA' wenn leer
30
35
  */
31
36
  sanitizeZ2MDeviceName(deviceName) {
32
- if (!deviceName) {return 'NA';}
33
- const deviceNameString = deviceName.replace(/:|\s|\//g, '-');
37
+ if (!deviceName) { return 'NA'; }
34
38
  // eslint-disable-next-line no-control-regex
35
- return deviceNameString.replace(/\u0000/g, '');
39
+ return deviceName.replace(/:|\s|\//g, '-').replace(/\u0000/g, '');
36
40
  }
37
41
 
38
42
  /**
39
- * @param device
40
- * @param ext
43
+ * Erstellt die zigbee2mqtt.io-Bild-URL für ein Gerät.
44
+ *
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
41
48
  */
42
49
  _buildZ2mImageUrl(device, ext) {
43
50
  if (device && device.definition && device.definition.model) {
@@ -49,14 +56,20 @@ class ImageController {
49
56
  }
50
57
 
51
58
  /**
52
- * @param device
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
53
63
  */
54
64
  getZ2mDeviceImageModelJPG(device) {
55
65
  return this._buildZ2mImageUrl(device, 'jpg');
56
66
  }
57
67
 
58
68
  /**
59
- * @param device
69
+ * Gibt die PNG-Bild-URL des Geräts auf zigbee2mqtt.io zurück.
70
+ *
71
+ * @param {object} device Das Geräteobjekt
72
+ * @returns {string|undefined} Die PNG-URL oder undefined
60
73
  */
61
74
  getZ2mDeviceImageModelPNG(device) {
62
75
  return this._buildZ2mImageUrl(device, 'png');
@@ -64,8 +77,10 @@ class ImageController {
64
77
 
65
78
 
66
79
  /**
80
+ * Gibt die SLS-Bild-URL des Geräts (alternative Quelle) zurück.
67
81
  *
68
- * @param device
82
+ * @param {object} device Das Geräteobjekt mit model_id-Feld
83
+ * @returns {string|undefined} Die Bild-URL oder undefined
69
84
  */
70
85
  getSlsDeviceImage(device) {
71
86
  if (device && device.model_id) {
@@ -73,11 +88,15 @@ class ImageController {
73
88
  // eslint-disable-next-line no-control-regex
74
89
  return icoString.replace(/\u0000/g, '');
75
90
  }
91
+ return undefined;
76
92
  }
77
93
 
78
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.
79
97
  *
80
- * @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 ''
81
100
  */
82
101
  async getDeviceIcon(device) {
83
102
  if (!this.adapter.config.useDeviceIcons) {
@@ -111,7 +130,7 @@ class ImageController {
111
130
  break;
112
131
  }
113
132
  } catch (ex) {
114
- // check next pic
133
+ this.adapter.log.debug(`Icon download failed for ${iconUrl}: ${ex && ex.message ? ex.message : ex}`);
115
134
  }
116
135
  }
117
136
  }
@@ -122,6 +141,10 @@ class ImageController {
122
141
  }
123
142
 
124
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
+ }
125
148
  const origIconMeta = await sharp(icon.file).metadata();
126
149
  if (
127
150
  (origIconMeta.height && origIconMeta.height > imageSize) ||
@@ -143,9 +166,12 @@ class ImageController {
143
166
 
144
167
  return `data:image/png;base64,${icon.file.toString('base64')}`;
145
168
  }
169
+
146
170
  /**
171
+ * Extrahiert den Dateinamen (mit Erweiterung) aus einer URL.
147
172
  *
148
- * @param url
173
+ * @param {string} url Die vollständige URL
174
+ * @returns {string} Dateiname (z.B. "TRADFRI_bulb.jpg")
149
175
  */
150
176
  getFileNameWithExtension(url) {
151
177
  const path = new URL(url).pathname;
@@ -155,26 +181,30 @@ class ImageController {
155
181
  }
156
182
 
157
183
  /**
184
+ * Lädt ein Icon von der angegebenen URL herunter und speichert es im ioBroker-Dateisystem.
158
185
  *
159
- * @param adapter
160
- * @param url
161
- * @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
162
190
  */
163
191
  async downloadIcon(adapter, url, namespace) {
164
192
  try {
165
- const res = await axios.get(url, { responseType: 'arraybuffer' });
193
+ const res = await axios.get(url, { responseType: 'arraybuffer', timeout: 10000 });
166
194
  await adapter.writeFileAsync(namespace, this.getFileNameWithExtension(url), res.data);
167
195
  return true;
168
196
  } catch (ex) {
169
- //adapter.log.warn(ex);
170
197
  return false;
171
198
  }
172
199
  }
200
+
173
201
  /**
202
+ * Prüft welcher lokale Icon-Dateiname bereits im ioBroker-Dateisystem existiert.
174
203
  *
175
- * @param z2mIconFileNameJPG
176
- * @param z2mIconFileNamePNG
177
- * @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
178
208
  */
179
209
  async getExistingIconFileName(z2mIconFileNameJPG, z2mIconFileNamePNG, slsIconFileName) {
180
210
  if (await this.adapter.fileExistsAsync(this.adapter.namespace, z2mIconFileNameJPG)) {
package/lib/messages.js CHANGED
@@ -1,9 +1,10 @@
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) {
7
8
  if (!config || !log) {
8
9
  return;
9
10
  }
@@ -12,23 +13,23 @@ async function adapterInfo(config, log) {
12
13
  log.info(`|| Zigbee2MQTT Frontend Server: ${config.webUIServer}`);
13
14
  log.info(`|| Zigbee2MQTT Frontend Port: ${config.webUIPort}`);
14
15
  log.info(`|| Zigbee2MQTT Connection Type: ${config.connectionType}`);
15
- if (config.connectionType == 'ws') {
16
+ if (config.connectionType === 'ws') {
16
17
  log.info(`|| Zigbee2MQTT Websocket Scheme: ${config.wsScheme}`);
17
18
  log.info(`|| Zigbee2MQTT Websocket Server: ${config.wsServerIP}`);
18
19
  log.info(`|| Zigbee2MQTT Websocket Port: ${config.wsServerPort}`);
19
20
  log.info(`|| Zigbee2MQTT Websocket Auth-Token: ${config.wsTokenEnabled ? 'use' : 'unused'}`);
20
21
  log.info(`|| Zigbee2MQTT Websocket Dummy MQTT-Server: ${config.dummyMqtt ? 'activated' : 'deactivated'}`);
21
- if (config.dummyMqtt == true) {
22
+ if (config.dummyMqtt === true) {
22
23
  log.info(`|| Zigbee2MQTT Dummy MQTT IP-Bind: ${config.mqttServerIPBind}`);
23
24
  log.info(`|| Zigbee2MQTT Dummy MQTT Port: ${config.mqttServerPort}`);
24
25
  }
25
- } else if (config.connectionType == 'exmqtt') {
26
- log.info(`|| Zigbee2MQTT Externanl MQTT Server: ${config.externalMqttServerIP}`);
27
- 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}`);
28
29
  log.info(
29
- `|| Zigbee2MQTT Externanl MQTT Credentials: ${config.externalMqttServerCredentials ? 'use' : 'unused'}`
30
+ `|| Zigbee2MQTT External MQTT Credentials: ${config.externalMqttServerCredentials ? 'use' : 'unused'}`
30
31
  );
31
- } else if (config.connectionType == 'intmqtt') {
32
+ } else if (config.connectionType === 'intmqtt') {
32
33
  log.info(`|| Zigbee2MQTT Internal MQTT IP-Bind: ${config.mqttServerIPBind}`);
33
34
  log.info(`|| Zigbee2MQTT Internal MQTT Port: ${config.mqttServerPort}`);
34
35
  }
@@ -50,11 +51,12 @@ async function adapterInfo(config, log) {
50
51
  }
51
52
 
52
53
  /**
54
+ * Gibt die Zigbee2MQTT-Bridge-Informationen formatiert als Log-Block aus.
53
55
  *
54
- * @param payload
55
- * @param log
56
+ * @param {object} payload Der Payload des bridge/info-Topics
57
+ * @param {object} log Das ioBroker-Log-Objekt (this.log)
56
58
  */
57
- async function zigbee2mqttInfo(payload, log) {
59
+ function zigbee2mqttInfo(payload, log) {
58
60
  if (!payload || !log) {
59
61
  return;
60
62
  }
@@ -1,15 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('@iobroker/adapter-core');
4
- const { Aedes } = require('aedes');
4
+ const Aedes = require('aedes');
5
5
  const net = require('node:net');
6
6
 
7
7
  /**
8
- *
8
+ * Verwaltet den internen oder Dummy-MQTT-Server (Aedes) für den Zigbee2MQTT-Adapter.
9
+ * Wird im intmqtt- und ws-Modus (dummyMqtt) verwendet.
9
10
  */
10
11
  class MqttServerController {
11
12
  /**
12
- * @param adapter
13
+ * Erstellt eine neue MqttServerController-Instanz.
14
+ *
15
+ * @param {object} adapter Die ioBroker-Adapter-Instanz
13
16
  */
14
17
  constructor(adapter) {
15
18
  this.adapter = adapter;
@@ -20,7 +23,7 @@ class MqttServerController {
20
23
  /**
21
24
  * Prüft ob Port und Bind-Adresse konfiguriert sind.
22
25
  *
23
- * @returns {boolean}
26
+ * @returns {boolean} true wenn die Konfiguration vollständig ist, sonst false
24
27
  */
25
28
  _checkConfig() {
26
29
  if (!this.adapter.config.mqttServerPort || !this.adapter.config.mqttServerIPBind) {
@@ -31,9 +34,9 @@ class MqttServerController {
31
34
  }
32
35
 
33
36
  /**
34
- * Hängt error- und close-Events an einen net.Server.
37
+ * Hängt error- und close-Event-Handler an den internen net.Server.
35
38
  *
36
- * @param {string} label
39
+ * @param {string} label Bezeichnung des Servers für Log-Ausgaben (z.B. "MQTT-Server")
37
40
  */
38
41
  _attachServerEvents(label) {
39
42
  if (!this.mqttServer) {
@@ -48,7 +51,8 @@ class MqttServerController {
48
51
  }
49
52
 
50
53
  /**
51
- * Startet den internen MQTT-Server mit Persistenz (aedes + nedb).
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.
52
56
  */
53
57
  async createMQTTServer() {
54
58
  if (!this._checkConfig()) {
@@ -65,22 +69,24 @@ class MqttServerController {
65
69
  if (!db.setup) {
66
70
  db.setup = async () => {};
67
71
  }
68
- this.aedesBroker = await Aedes.createBroker({ persistence: db });
69
- this.mqttServer = net.createServer(this.aedesBroker.handle);
72
+ const aedes = Aedes({ persistence: db });
73
+ this.aedesBroker = aedes;
74
+ this.mqttServer = net.createServer(aedes.handle);
70
75
  this._attachServerEvents('MQTT-Server');
71
76
 
72
77
  await new Promise((resolve, reject) => {
78
+ this.mqttServer.once('error', reject);
73
79
  this.mqttServer.listen(
74
80
  this.adapter.config.mqttServerPort,
75
81
  this.adapter.config.mqttServerIPBind,
76
82
  () => {
83
+ this.mqttServer.removeListener('error', reject);
77
84
  this.adapter.log.info(
78
85
  `Starting MQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`
79
86
  );
80
87
  resolve();
81
88
  }
82
89
  );
83
- this.mqttServer.once('error', reject);
84
90
  });
85
91
  } catch (err) {
86
92
  this.adapter.log.error(`createMQTTServer error: ${err && err.message ? err.message : String(err)}`);
@@ -90,29 +96,32 @@ class MqttServerController {
90
96
  }
91
97
 
92
98
  /**
93
- * Startet einen Dummy-MQTT-Server ohne Persistenz (für WS-Modus).
99
+ * Startet einen Dummy-MQTT-Server ohne Persistenz.
100
+ * Wird im WebSocket-Modus (dummyMqtt=true) benötigt damit Z2M einen MQTT-Broker findet.
94
101
  */
95
102
  async createDummyMQTTServer() {
96
103
  if (!this._checkConfig()) {
97
104
  return;
98
105
  }
99
106
  try {
100
- this.aedesBroker = await Aedes.createBroker();
101
- this.mqttServer = net.createServer(this.aedesBroker.handle);
107
+ const aedes = Aedes();
108
+ this.aedesBroker = aedes;
109
+ this.mqttServer = net.createServer(aedes.handle);
102
110
  this._attachServerEvents('DummyMQTT-Server');
103
111
 
104
112
  await new Promise((resolve, reject) => {
113
+ this.mqttServer.once('error', reject);
105
114
  this.mqttServer.listen(
106
115
  this.adapter.config.mqttServerPort,
107
116
  this.adapter.config.mqttServerIPBind,
108
117
  () => {
118
+ this.mqttServer.removeListener('error', reject);
109
119
  this.adapter.log.info(
110
120
  `Starting DummyMQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`
111
121
  );
112
122
  resolve();
113
123
  }
114
124
  );
115
- this.mqttServer.once('error', reject);
116
125
  });
117
126
  } catch (err) {
118
127
  this.adapter.log.error(`createDummyMQTTServer error: ${err && err.message ? err.message : String(err)}`);
@@ -121,9 +130,21 @@ class MqttServerController {
121
130
  }
122
131
 
123
132
  /**
124
- * Schließt den MQTT-Server und den Aedes-Broker.
133
+ * Schließt den MQTT-Server und den Aedes-Broker sicher.
134
+ * Wird beim Adapter-Stop oder nach einem Fehler beim Start aufgerufen.
125
135
  */
126
136
  closeServer() {
137
+ if (this.aedesBroker) {
138
+ const broker = this.aedesBroker;
139
+ this.aedesBroker = null;
140
+ try {
141
+ broker.close(() => {
142
+ this.adapter.log.debug('Aedes broker closed.');
143
+ });
144
+ } catch (err) {
145
+ this.adapter.log.error(`closeServer aedesBroker.close() error: ${err}`);
146
+ }
147
+ }
127
148
  if (this.mqttServer) {
128
149
  const server = this.mqttServer;
129
150
  this.mqttServer = null;
@@ -137,21 +158,6 @@ class MqttServerController {
137
158
  this.adapter.log.error(`closeServer mqttServer.close() error: ${err}`);
138
159
  }
139
160
  }
140
- if (this.aedesBroker) {
141
- const broker = this.aedesBroker;
142
- this.aedesBroker = null;
143
- try {
144
- broker.close((err) => {
145
- if (err) {
146
- this.adapter.log.debug(`Aedes broker close callback error: ${err}`);
147
- } else {
148
- this.adapter.log.debug('Aedes broker closed.');
149
- }
150
- });
151
- } catch (err) {
152
- this.adapter.log.error(`closeServer aedesBroker.close() error: ${err}`);
153
- }
154
- }
155
161
  }
156
162
  }
157
163
 
@@ -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 = {
package/lib/rgb.js CHANGED
@@ -115,10 +115,15 @@ function cie_to_rgb(x, y, brightness) {
115
115
  * @returns {Array} Array that contains the CIE color values for x and y
116
116
  */
117
117
  function rgb_to_cie(red, green, blue) {
118
+ // Normalize RGB values from 0-255 to 0-1 before gamma correction
119
+ red = red / 255;
120
+ green = green / 255;
121
+ blue = blue / 255;
122
+
118
123
  // Apply a gamma correction to the RGB values, which makes the color more vivid and more the like the color displayed on the screen of your device
119
- red = red > 0.04045 ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : red / 12.92;
124
+ red = red > 0.04045 ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : red / 12.92;
120
125
  green = green > 0.04045 ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : green / 12.92;
121
- blue = blue > 0.04045 ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : blue / 12.92;
126
+ blue = blue > 0.04045 ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : blue / 12.92;
122
127
 
123
128
  // RGB values to XYZ using the Wide RGB D65 conversion formula
124
129
  const X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
@@ -155,7 +160,9 @@ function hsvToRGB(h, s, v) {
155
160
  let g;
156
161
  let b;
157
162
  if (arguments.length === 1) {
158
- (s = h.s), (v = h.v), (h = h.h);
163
+ s = h.s;
164
+ v = h.v;
165
+ h = h.h;
159
166
  }
160
167
  const i = Math.floor(h * 6);
161
168
  const f = h * 6 - i;
@@ -163,24 +170,13 @@ function hsvToRGB(h, s, v) {
163
170
  const q = v * (1 - f * s);
164
171
  const t = v * (1 - (1 - f) * s);
165
172
  switch (i % 6) {
166
- case 0:
167
- (r = v), (g = t), (b = p);
168
- break;
169
- case 1:
170
- (r = q), (g = v), (b = p);
171
- break;
172
- case 2:
173
- (r = p), (g = v), (b = t);
174
- break;
175
- case 3:
176
- (r = p), (g = q), (b = v);
177
- break;
178
- case 4:
179
- (r = t), (g = p), (b = v);
180
- break;
181
- case 5:
182
- (r = v), (g = p), (b = q);
183
- break;
173
+ case 0: r = v; g = t; b = p; break;
174
+ case 1: r = q; g = v; b = p; break;
175
+ case 2: r = p; g = v; b = t; break;
176
+ case 3: r = p; g = q; b = v; break;
177
+ case 4: r = t; g = p; b = v; break;
178
+ case 5: r = v; g = p; b = q; break;
179
+ default: r = 0; g = 0; b = 0; break;
184
180
  }
185
181
  return {
186
182
  r: Math.round(r * 255),
@@ -198,7 +194,9 @@ function hsvToRGB(h, s, v) {
198
194
  */
199
195
  function rgbToHSV(r, g, b, numeric) {
200
196
  if (arguments.length === 1) {
201
- (g = r.g), (b = r.b), (r = r.r);
197
+ g = r.g;
198
+ b = r.b;
199
+ r = r.r;
202
200
  }
203
201
  const max = Math.max(r, g, b);
204
202
  const min = Math.min(r, g, b);
@@ -225,20 +223,18 @@ function rgbToHSV(r, g, b, numeric) {
225
223
  break;
226
224
  }
227
225
  if (numeric) {
228
- return {
229
-
226
+ return {
230
227
  h: Math.round(h * 360),
231
228
  s: Math.round(s * 100),
232
229
  v: Math.round(v * 100),
233
230
  };
234
231
  }
235
- return {
236
-
232
+ return {
237
233
  h: (h * 360).toFixed(3),
238
234
  s: (s * 100).toFixed(3),
239
235
  v: (v * 100).toFixed(3),
240
- };
241
- }
236
+ };
237
+ }
242
238
 
243
239
  /**
244
240
  *
@@ -281,20 +277,20 @@ function hsv_to_cie(h, s, v) {
281
277
  function rgb_to_rgbstring(element) {
282
278
  let col = '#';
283
279
  if (element && element.hasOwnProperty('r')) {
284
- col = col + element.r.toString(16).padStart(2, '0');
285
- } else {
286
- col = `${col }00`;
287
- }
280
+ col += element.r.toString(16).padStart(2, '0');
281
+ } else {
282
+ col += '00';
283
+ }
288
284
  if (element && element.hasOwnProperty('g')) {
289
- col = col + element.g.toString(16).padStart(2, '0');
290
- } else {
291
- col = `${col }00`;
292
- }
285
+ col += element.g.toString(16).padStart(2, '0');
286
+ } else {
287
+ col += '00';
288
+ }
293
289
  if (element && element.hasOwnProperty('b')) {
294
- col = col + element.b.toString(16).padStart(2, '0');
295
- } else {
296
- col = `${col }00`;
297
- }
290
+ col += element.b.toString(16).padStart(2, '0');
291
+ } else {
292
+ col += '00';
293
+ }
298
294
  return col;
299
295
  }
300
296
 
@@ -306,30 +302,23 @@ col = `${col }00`;
306
302
  */
307
303
  function hsbToRGB(h, s, b) {
308
304
  const br = Math.round(b * 2.55);
309
- if (s == 0) {
305
+ if (s === 0) {
310
306
  return [br, br, br];
311
- }
312
- const hue = h % 360;
313
- const f = hue % 60;
314
- const p = Math.round(b * (100 - s) * 0.0255);
315
- const q = Math.round(b * (6000 - s * f) * 0.000425);
316
- const t = Math.round(b * (6000 - s * (60 - f)) * 0.000425);
317
- switch (Math.floor(hue / 60)) {
318
- case 0:
319
- return [br, t, p];
320
- case 1:
321
- return [q, br, p];
322
- case 2:
323
- return [p, br, t];
324
- case 3:
325
- return [p, q, br];
326
- case 4:
327
- return [t, p, br];
328
- case 5:
329
- return [br, p, q];
330
- }
331
-
332
- return false;
307
+ }
308
+ const hue = h % 360;
309
+ const f = hue % 60;
310
+ const p = Math.round(b * (100 - s) * 0.0255);
311
+ const q = Math.round(b * (6000 - s * f) * 0.000425);
312
+ const t = Math.round(b * (6000 - s * (60 - f)) * 0.000425);
313
+ switch (Math.floor(hue / 60)) {
314
+ case 0: return [br, t, p];
315
+ case 1: return [q, br, p];
316
+ case 2: return [p, br, t];
317
+ case 3: return [p, q, br];
318
+ case 4: return [t, p, br];
319
+ case 5: return [br, p, q];
320
+ }
321
+ return [br, br, br]; // fallback – sollte nie erreicht werden
333
322
  }
334
323
 
335
324
  /**