iobroker.zigbee2mqtt 3.2.2 → 3.2.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.
- package/README.md +20 -9
- package/admin/i18n/de/translations.json +16 -2
- package/admin/i18n/en/translations.json +21 -6
- package/admin/i18n/es/translations.json +19 -1
- package/admin/i18n/fr/translations.json +18 -1
- package/admin/i18n/it/translations.json +18 -1
- package/admin/i18n/nl/translations.json +18 -1
- package/admin/i18n/pl/translations.json +18 -1
- package/admin/i18n/pt/translations.json +18 -1
- package/admin/i18n/ru/translations.json +18 -1
- package/admin/i18n/uk/translations.json +18 -1
- package/admin/i18n/zh-cn/translations.json +16 -1
- package/admin/jsonConfig.json +38 -48
- package/io-package.json +44 -4
- package/lib/deviceController.js +51 -21
- package/lib/exposes.js +13 -14
- package/lib/imageController.js +1 -1
- package/lib/mqttServerController.js +25 -5
- package/lib/rgb.js +5 -5
- package/lib/states.js +23 -1
- package/lib/statesController.js +196 -28
- package/lib/websocketController.js +5 -0
- package/lib/z2mController.js +9 -1
- package/main.js +38 -12
- package/package.json +10 -10
package/lib/deviceController.js
CHANGED
|
@@ -6,19 +6,6 @@ const rgb = require('./rgb.js');
|
|
|
6
6
|
const ImageController = require('./imageController').ImageController;
|
|
7
7
|
const dmUtils = require('@iobroker/dm-utils');
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* Prüft ob eine ieee_address in der kommaseparierten debugDevices-Liste enthalten ist.
|
|
11
|
-
* String.includes() würde Substring-Matches liefern (z.B. "0x12" trifft "0x1234").
|
|
12
|
-
*
|
|
13
|
-
* @param {string} debugDevices
|
|
14
|
-
* @param {string} address
|
|
15
|
-
* @returns {boolean}
|
|
16
|
-
*/
|
|
17
|
-
function isDebugDevice(debugDevices, address) {
|
|
18
|
-
if (!debugDevices || !address) { return false; }
|
|
19
|
-
return String(debugDevices).split(',').map((s) => s.trim()).includes(address);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
9
|
/**
|
|
23
10
|
* Verwaltet das Erstellen und Aktualisieren von Geräte- und Gruppenobjekten in ioBroker
|
|
24
11
|
* basierend auf den Zigbee2MQTT-Exposes.
|
|
@@ -43,6 +30,31 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
43
30
|
this.logCustomizations = logCustomizations;
|
|
44
31
|
this.createCache = createCache;
|
|
45
32
|
this.imageController = new ImageController(adapter);
|
|
33
|
+
|
|
34
|
+
// Debug-Device Cache - wird lazy befüllt (siehe isDebugDevice)
|
|
35
|
+
this._debugDeviceCache = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Checks if the given device is a debug device.
|
|
40
|
+
* Der CSV-String wird einmalig geparst und in einem Set gecached (O(1) Lookup).
|
|
41
|
+
* Case-insensitive Vergleich.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} ieeeAddr - IEEE address of the device
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
isDebugDevice(ieeeAddr) {
|
|
47
|
+
if (!ieeeAddr) { return false; }
|
|
48
|
+
if (this._debugDeviceCache === null) {
|
|
49
|
+
const debugDeviceList = this.logCustomizations.debugDevices || '';
|
|
50
|
+
this._debugDeviceCache = new Set(
|
|
51
|
+
debugDeviceList
|
|
52
|
+
.split(',')
|
|
53
|
+
.map(s => s.trim().toLowerCase())
|
|
54
|
+
.filter(s => s.length > 0)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return this._debugDeviceCache.has(ieeeAddr.toLowerCase());
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
/**
|
|
@@ -58,12 +70,15 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
58
70
|
return;
|
|
59
71
|
}
|
|
60
72
|
utils.clearArray(this.deviceCache);
|
|
73
|
+
// Topic->Device Map-Cache invalidieren, da deviceCache neu aufgebaut wird
|
|
74
|
+
// (verhindert veraltete Referenzen -> u.a. available-State bleibt sonst false)
|
|
75
|
+
this.invalidateStatesDeviceMap();
|
|
61
76
|
for (const devicesMessag of devicesMessage) {
|
|
62
77
|
if (!devicesMessag || !devicesMessag.ieee_address) {
|
|
63
78
|
continue;
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
if (isDebugDevice(
|
|
81
|
+
if (this.isDebugDevice(devicesMessag.ieee_address)) {
|
|
67
82
|
this.adapter.log.warn(
|
|
68
83
|
`--->>> fromZ2M -> ${devicesMessag.ieee_address} exposes: ${JSON.stringify(devicesMessag)}`
|
|
69
84
|
);
|
|
@@ -76,7 +91,11 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
76
91
|
if (Array.isArray(devicesMessag.definition.exposes)) {
|
|
77
92
|
try {
|
|
78
93
|
const newDevice = await createDeviceFromExposes(devicesMessag, this.adapter);
|
|
79
|
-
|
|
94
|
+
try {
|
|
95
|
+
newDevice.icon = await this.imageController.getDeviceIcon(devicesMessag);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
this.adapter.log.warn(`[deviceController] Could not load icon for device ${newDevice.common?.name || 'unknown'}: ${e.message}`);
|
|
98
|
+
}
|
|
80
99
|
newDevice.manufacturer = devicesMessag.definition.vendor || '';
|
|
81
100
|
newDevice.model = devicesMessag.definition.model || '';
|
|
82
101
|
newDevice.modelDescription = devicesMessag.definition.description || '';
|
|
@@ -292,12 +311,14 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
292
311
|
return;
|
|
293
312
|
}
|
|
294
313
|
utils.clearArray(this.groupCache);
|
|
314
|
+
// Topic->Device Map-Cache invalidieren, da groupCache neu aufgebaut wird
|
|
315
|
+
this.invalidateStatesDeviceMap();
|
|
295
316
|
for (const groupMessage of groupsMessage) {
|
|
296
317
|
// Fix 5: fehlende Pflichtfelder abfangen
|
|
297
318
|
if (!groupMessage || groupMessage.id == null || !groupMessage.friendly_name) {
|
|
298
319
|
continue;
|
|
299
320
|
}
|
|
300
|
-
if (isDebugDevice(
|
|
321
|
+
if (this.isDebugDevice(String(groupMessage.id))) {
|
|
301
322
|
this.adapter.log.warn(`--->>> fromZ2M -> ${groupMessage.id} exposes: ${JSON.stringify(groupMessage)}`);
|
|
302
323
|
}
|
|
303
324
|
this.defineGroupDevice(groupMessage.friendly_name, `group_${groupMessage.id}`, groupMessage.scenes || []);
|
|
@@ -469,6 +490,8 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
469
490
|
.find((x) => x.id === messageObj.payload.data.from);
|
|
470
491
|
if (renamedDevice) {
|
|
471
492
|
renamedDevice.id = messageObj.payload.data.to;
|
|
493
|
+
// Topic (device.id) hat sich geändert -> Map-Cache invalidieren
|
|
494
|
+
this.invalidateStatesDeviceMap();
|
|
472
495
|
}
|
|
473
496
|
}
|
|
474
497
|
|
|
@@ -485,6 +508,18 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
485
508
|
}
|
|
486
509
|
}
|
|
487
510
|
|
|
511
|
+
/**
|
|
512
|
+
* Invalidiert den Topic->Device Map-Cache des StatesController.
|
|
513
|
+
* Muss nach jedem Neuaufbau von deviceCache/groupCache aufgerufen werden,
|
|
514
|
+
* damit findDeviceByTopic() keine veralteten Geräteobjekte liefert
|
|
515
|
+
* (sonst bleibt z.B. das available-State neu angelernter Geräte auf false).
|
|
516
|
+
*/
|
|
517
|
+
invalidateStatesDeviceMap() {
|
|
518
|
+
if (this.adapter.statesController && typeof this.adapter.statesController.clearDeviceMap === 'function') {
|
|
519
|
+
this.adapter.statesController.clearDeviceMap();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
488
523
|
/**
|
|
489
524
|
* Erstellt eine bereinigte Kopie eines State-Objekts ohne interne Laufzeit-Felder
|
|
490
525
|
* (getter, setter, prop, etc.) für die ioBroker-Objektdefinition.
|
|
@@ -765,11 +800,9 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
765
800
|
const detailItems = device._detailItems || {};
|
|
766
801
|
const hasDetails = Object.keys(detailItems).length > 0;
|
|
767
802
|
|
|
768
|
-
/** @type {Record<string, any>} */
|
|
769
803
|
const data = {};
|
|
770
804
|
|
|
771
805
|
// ── Tab 1: Gerät ─────────────────────────────────────────────────────
|
|
772
|
-
/** @type {Record<string, any>} */
|
|
773
806
|
const infoItems = {};
|
|
774
807
|
infoItems['_h1'] = { type: 'header', text: 'Device Identity', size: 4, newLine: true };
|
|
775
808
|
infoItems['_d1'] = { type: 'divider', color: 'primary' };
|
|
@@ -781,7 +814,6 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
781
814
|
infoItems['pwr'] = { type: 'staticInfo', label: 'Power Source', data: device.powerSource || '—', size: 16, addColon: true, newLine: true };
|
|
782
815
|
|
|
783
816
|
// ── Tab 2: Verbindung ─────────────────────────────────────────────────
|
|
784
|
-
/** @type {Record<string, any>} */
|
|
785
817
|
const connItems = {};
|
|
786
818
|
connItems['_h1'] = { type: 'header', text: 'Zigbee Connection', size: 4, newLine: true };
|
|
787
819
|
connItems['_d1'] = { type: 'divider', color: 'primary' };
|
|
@@ -793,7 +825,6 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
793
825
|
}
|
|
794
826
|
|
|
795
827
|
// ── Tab 3: Technisch ──────────────────────────────────────────────────
|
|
796
|
-
/** @type {Record<string, any>} */
|
|
797
828
|
const techItems = {};
|
|
798
829
|
techItems['_h1'] = { type: 'header', text: 'Zigbee Status', size: 4, newLine: true };
|
|
799
830
|
techItems['_d1'] = { type: 'divider', color: 'primary' };
|
|
@@ -806,7 +837,6 @@ class DeviceController extends dmUtils.DeviceManagement {
|
|
|
806
837
|
data.disabled = !!device.disabled;
|
|
807
838
|
|
|
808
839
|
// ── Tabs zusammenbauen ────────────────────────────────────────────────
|
|
809
|
-
/** @type {Record<string, any>} */
|
|
810
840
|
const tabs = {
|
|
811
841
|
_tab_info: { type: 'panel', label: 'Device', items: infoItems },
|
|
812
842
|
_tab_conn: { type: 'panel', label: 'Connection', items: connItems },
|
package/lib/exposes.js
CHANGED
|
@@ -111,6 +111,7 @@ function genState(expose, role, name, desc) {
|
|
|
111
111
|
states: {},
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
+
state.type = 'string';
|
|
114
115
|
for (const val of expose.values) {
|
|
115
116
|
// if a definition of a button (eg. Aqara presence detector FP1)
|
|
116
117
|
if (val === '') {
|
|
@@ -118,7 +119,6 @@ function genState(expose, role, name, desc) {
|
|
|
118
119
|
} else {
|
|
119
120
|
state.states[val] = val;
|
|
120
121
|
}
|
|
121
|
-
state.type = typeof val;
|
|
122
122
|
}
|
|
123
123
|
// Fix 4: wenn values leer → type bleibt undefined → Fallback auf 'string'
|
|
124
124
|
if (!state.type) {
|
|
@@ -153,6 +153,7 @@ function genState(expose, role, name, desc) {
|
|
|
153
153
|
break;
|
|
154
154
|
|
|
155
155
|
default:
|
|
156
|
+
console.warn(`[genState] Unknown expose type: ${expose.type}`);
|
|
156
157
|
break;
|
|
157
158
|
}
|
|
158
159
|
|
|
@@ -203,13 +204,12 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
|
|
|
203
204
|
state = Object.assign({}, state);
|
|
204
205
|
|
|
205
206
|
if (access === undefined) {access = z2mAccess.ALL;}
|
|
206
|
-
state.
|
|
207
|
-
state.
|
|
207
|
+
state.read = (access & z2mAccess.STATE) > 0;
|
|
208
|
+
state.write = (access & z2mAccess.SET) > 0;
|
|
208
209
|
const stateExists = states.findIndex((x, _index, _array) => x.id === state.id);
|
|
209
210
|
|
|
210
211
|
if (stateExists < 0) {
|
|
211
|
-
state.write
|
|
212
|
-
if (!state.writable) {
|
|
212
|
+
if (!state.write) {
|
|
213
213
|
if (state.hasOwnProperty('setter')) {
|
|
214
214
|
delete state.setter;
|
|
215
215
|
}
|
|
@@ -219,7 +219,7 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
if (!state.
|
|
222
|
+
if (!state.read) {
|
|
223
223
|
if (state.hasOwnProperty('getter')) {
|
|
224
224
|
//to awid some worning on unprocessed data
|
|
225
225
|
state.getter = (_payload) => undefined;
|
|
@@ -228,7 +228,7 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
|
|
|
228
228
|
|
|
229
229
|
return states.push(state);
|
|
230
230
|
}
|
|
231
|
-
if (state.
|
|
231
|
+
if (state.read && !states[stateExists].read) {
|
|
232
232
|
states[stateExists].read = state.read;
|
|
233
233
|
// as state is readable, it can't be button or event
|
|
234
234
|
if (states[stateExists].role === 'button') {
|
|
@@ -259,11 +259,11 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
|
|
|
259
259
|
} else if (state.hasOwnProperty('prop')) {
|
|
260
260
|
states[stateExists].prop = state.prop;
|
|
261
261
|
}
|
|
262
|
-
states[stateExists].
|
|
262
|
+
states[stateExists].read = true;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
if (state.
|
|
266
|
-
states[stateExists].write = state.
|
|
265
|
+
if (state.write && !states[stateExists].write) {
|
|
266
|
+
states[stateExists].write = state.write;
|
|
267
267
|
// use new state `setter`
|
|
268
268
|
if (state.hasOwnProperty('setter')) {
|
|
269
269
|
states[stateExists].setter = state.setter;
|
|
@@ -303,7 +303,7 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
|
|
|
303
303
|
if (state.hasOwnProperty('epname')) {
|
|
304
304
|
states[stateExists].epname = state.epname;
|
|
305
305
|
}
|
|
306
|
-
states[stateExists].
|
|
306
|
+
states[stateExists].write = true;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
return states.length;
|
|
@@ -1289,9 +1289,8 @@ async function createDeviceFromExposes(devicesMessag, adapter) {
|
|
|
1289
1289
|
return !isNaN(payload[expose.property][prop.property])
|
|
1290
1290
|
? payload[expose.property][prop.property]
|
|
1291
1291
|
: undefined;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1292
|
+
}
|
|
1293
|
+
return undefined;
|
|
1295
1294
|
};
|
|
1296
1295
|
} else {
|
|
1297
1296
|
state.getter = (payload) => {
|
package/lib/imageController.js
CHANGED
|
@@ -106,7 +106,7 @@ class ImageController {
|
|
|
106
106
|
return '';
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const imageSize = this.adapter.config.deviceIconsSize;
|
|
109
|
+
const imageSize = this.adapter.config.deviceIconsSize || 120;
|
|
110
110
|
const z2mModel = device.definition.model || '';
|
|
111
111
|
const z2mIconFileNameJPG = `${this.sanitizeZ2MDeviceName(z2mModel)}.jpg`;
|
|
112
112
|
const z2mIconFileNamePNG = `${this.sanitizeZ2MDeviceName(z2mModel)}.png`;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Aedes = require('aedes');
|
|
4
4
|
const net = require('node:net');
|
|
5
|
+
const MemoryPersistence = require('aedes-persistence');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Verwaltet den internen oder Dummy-MQTT-Server (Aedes) für den Zigbee2MQTT-Adapter.
|
|
@@ -58,10 +59,9 @@ class MqttServerController {
|
|
|
58
59
|
*/
|
|
59
60
|
async createMQTTServer() {
|
|
60
61
|
if (!this._checkConfig()) {
|
|
61
|
-
return;
|
|
62
|
+
return false;
|
|
62
63
|
}
|
|
63
64
|
try {
|
|
64
|
-
const MemoryPersistence = require('aedes-persistence');
|
|
65
65
|
const db = MemoryPersistence();
|
|
66
66
|
const aedes = Aedes({ persistence: db });
|
|
67
67
|
this.aedesBroker = aedes;
|
|
@@ -82,10 +82,20 @@ class MqttServerController {
|
|
|
82
82
|
}
|
|
83
83
|
);
|
|
84
84
|
});
|
|
85
|
+
return true;
|
|
85
86
|
} catch (err) {
|
|
86
|
-
|
|
87
|
+
const code = err && err.code ? err.code : '';
|
|
88
|
+
if (code === 'EACCES' || code === 'EADDRINUSE') {
|
|
89
|
+
this.adapter.log.error(
|
|
90
|
+
`Port ${this.adapter.config.mqttServerPort} is already in use or requires admin privileges. ` +
|
|
91
|
+
`Please change the port in the adapter settings or stop the other service.`
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
this.adapter.log.error(`createMQTTServer error: ${err && err.message ? err.message : String(err)}`);
|
|
95
|
+
}
|
|
87
96
|
// Sicherstellen dass kein halboffener Server übrig bleibt
|
|
88
97
|
this.closeServer();
|
|
98
|
+
return false;
|
|
89
99
|
}
|
|
90
100
|
}
|
|
91
101
|
|
|
@@ -95,7 +105,7 @@ class MqttServerController {
|
|
|
95
105
|
*/
|
|
96
106
|
async createDummyMQTTServer() {
|
|
97
107
|
if (!this._checkConfig()) {
|
|
98
|
-
return;
|
|
108
|
+
return false;
|
|
99
109
|
}
|
|
100
110
|
try {
|
|
101
111
|
const aedes = Aedes();
|
|
@@ -117,9 +127,19 @@ class MqttServerController {
|
|
|
117
127
|
}
|
|
118
128
|
);
|
|
119
129
|
});
|
|
130
|
+
return true;
|
|
120
131
|
} catch (err) {
|
|
121
|
-
|
|
132
|
+
const code = err && err.code ? err.code : '';
|
|
133
|
+
if (code === 'EACCES' || code === 'EADDRINUSE') {
|
|
134
|
+
this.adapter.log.error(
|
|
135
|
+
`Port ${this.adapter.config.mqttServerPort} is already in use or requires admin privileges. ` +
|
|
136
|
+
`Please change the port in the adapter settings or stop the other service.`
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
this.adapter.log.error(`createDummyMQTTServer error: ${err && err.message ? err.message : String(err)}`);
|
|
140
|
+
}
|
|
122
141
|
this.closeServer();
|
|
142
|
+
return false;
|
|
123
143
|
}
|
|
124
144
|
}
|
|
125
145
|
|
package/lib/rgb.js
CHANGED
|
@@ -152,6 +152,11 @@ function rgb_to_cie(red, green, blue) {
|
|
|
152
152
|
* @param v
|
|
153
153
|
*/
|
|
154
154
|
function hsvToRGB(h, s, v) {
|
|
155
|
+
if (arguments.length === 1) {
|
|
156
|
+
s = h.s;
|
|
157
|
+
v = h.v;
|
|
158
|
+
h = h.h;
|
|
159
|
+
}
|
|
155
160
|
h = (h % 360) / 360;
|
|
156
161
|
s = s / 100;
|
|
157
162
|
v = v / 100;
|
|
@@ -159,11 +164,6 @@ function hsvToRGB(h, s, v) {
|
|
|
159
164
|
let r;
|
|
160
165
|
let g;
|
|
161
166
|
let b;
|
|
162
|
-
if (arguments.length === 1) {
|
|
163
|
-
s = h.s;
|
|
164
|
-
v = h.v;
|
|
165
|
-
h = h.h;
|
|
166
|
-
}
|
|
167
167
|
const i = Math.floor(h * 6);
|
|
168
168
|
const f = h * 6 - i;
|
|
169
169
|
const p = v * (1 - s);
|
package/lib/states.js
CHANGED
|
@@ -37,6 +37,17 @@ const nameLookup = {
|
|
|
37
37
|
Hz: 'frequency',
|
|
38
38
|
pf: 'power_factor',
|
|
39
39
|
lx: 'illuminance_lux',
|
|
40
|
+
'°C': 'temperature_celsius',
|
|
41
|
+
'°F': 'temperature_fahrenheit',
|
|
42
|
+
mA: 'milliampere',
|
|
43
|
+
mV: 'millivolt',
|
|
44
|
+
'μg/m³': 'microgram_per_cubic_meter',
|
|
45
|
+
dB: 'decibel',
|
|
46
|
+
'%rH': 'percent_rh',
|
|
47
|
+
'km/h': 'kilometer_per_hour',
|
|
48
|
+
'm/s': 'meter_per_second',
|
|
49
|
+
'mg/m³': 'milligram_per_cubic_meter',
|
|
50
|
+
K: 'kelvin',
|
|
40
51
|
};
|
|
41
52
|
|
|
42
53
|
const unitLookup = {
|
|
@@ -53,6 +64,17 @@ const unitLookup = {
|
|
|
53
64
|
frequency: 'Hz',
|
|
54
65
|
power_factor: 'pf',
|
|
55
66
|
illuminance_lux: 'lx',
|
|
67
|
+
temperature_celsius: '°C',
|
|
68
|
+
temperature_fahrenheit: '°F',
|
|
69
|
+
milliampere: 'mA',
|
|
70
|
+
millivolt: 'mV',
|
|
71
|
+
microgram_per_cubic_meter: 'μg/m³',
|
|
72
|
+
decibel: 'dB',
|
|
73
|
+
percent_rh: '%rH',
|
|
74
|
+
kilometer_per_hour: 'km/h',
|
|
75
|
+
meter_per_second: 'm/s',
|
|
76
|
+
milligram_per_cubic_meter: 'mg/m³',
|
|
77
|
+
kelvin: 'K',
|
|
56
78
|
};
|
|
57
79
|
|
|
58
80
|
const states = {
|
|
@@ -396,7 +418,7 @@ const states = {
|
|
|
396
418
|
unit: '',
|
|
397
419
|
getter: (payload) => {
|
|
398
420
|
// Wenn illuminance_raw bereits im Payload → illuminance_raw_direct setzt ihn direkt
|
|
399
|
-
if (payload.illuminance_raw != null) { return
|
|
421
|
+
if (payload.illuminance_raw != null) { return Number(payload.illuminance_raw); }
|
|
400
422
|
const lux = payload.illuminance;
|
|
401
423
|
if (lux == null) { return undefined; }
|
|
402
424
|
if (lux <= 0) { return 0; }
|