iobroker.zigbee 3.3.1 → 3.3.2
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/LICENSE +1 -1
- package/README.md +8 -2
- package/io-package.json +14 -14
- package/lib/exposes.js +10 -94
- package/lib/models.js +2 -1
- package/lib/statescontroller.js +23 -2
- package/lib/zigbeecontroller.js +17 -1
- package/main.js +1 -1
- package/package.json +3 -3
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2018-
|
|
3
|
+
Copyright (c) 2018-2026 Kirov Ilya <kirovilya@gmail.com>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -141,10 +141,16 @@ You can thank the authors by these links:
|
|
|
141
141
|
|
|
142
142
|
-----------------------------------------------------------------------------------------------------
|
|
143
143
|
## Changelog
|
|
144
|
+
### 3.3.2 (2026-01-04)
|
|
145
|
+
* Fix sync brightness / state
|
|
146
|
+
* Fix bug in expose
|
|
147
|
+
* Fix rewrite state config
|
|
148
|
+
*
|
|
149
|
+
|
|
144
150
|
### 3.3.1 (2025-12-31)
|
|
145
151
|
* Update documentation
|
|
146
152
|
* Color Hue/Saturation in Groups
|
|
147
|
-
*
|
|
153
|
+
* Zigbee-Herdsman v8.x.x
|
|
148
154
|
* Sort by model in Admin
|
|
149
155
|
* Object for complex exposes
|
|
150
156
|
* POSSIBLY BREAKING: Complex exposes changed to 'channel / state' structure
|
|
@@ -411,7 +417,7 @@ You can thank the authors by these links:
|
|
|
411
417
|
## License
|
|
412
418
|
The MIT License (MIT)
|
|
413
419
|
|
|
414
|
-
Copyright (c) 2018-
|
|
420
|
+
Copyright (c) 2018-2026 Kirov Ilya <kirovilya@gmail.com>
|
|
415
421
|
|
|
416
422
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
417
423
|
of this software and associated documentation files (the "Software"), to deal
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zigbee",
|
|
4
|
-
"version": "3.3.
|
|
4
|
+
"version": "3.3.2",
|
|
5
5
|
"news": {
|
|
6
|
+
"3.3.2": {
|
|
7
|
+
"en": "Fix sync brightness / state\nFix bug in expose\nFix rewrite state config\n",
|
|
8
|
+
"de": "Synchronisierhelligkeit / Zustand\nFehler behoben in exponieren\nRewriting State config\n",
|
|
9
|
+
"ru": "Исправление яркости синхронизации / состояние\nУстранение ошибки в разоблачении\nRewrite state config (переписать конфигурацию состояния)\n",
|
|
10
|
+
"pt": "Corrigir o brilho / estado da sincronização\nCorrigir erro na exposição\nCorrigir a configuração do estado de reescrita\n",
|
|
11
|
+
"nl": "Synchronische helderheid / status herstellen\nFix bug in expos\nHerschrijf statusconfiguratie\n",
|
|
12
|
+
"fr": "Correction de la luminosité de synchronisation / état\nCorrection du bogue dans l'exposition\nCorrection de la configuration de l'état de réécriture\n",
|
|
13
|
+
"it": "Fissare la luminosità di sincronizzazione / stato\nFissare bug in esposizione\nRiparare la configurazione dello stato di riscrittura\n",
|
|
14
|
+
"es": "Fijar el brillo de sincronización / estado\nFijar fallo en exponer\nReescribir el config del estado\n",
|
|
15
|
+
"pl": "Napraw jasność / stan synchronizacji\nPopraw błąd w odsłonięciu\nPopraw stan konfiguracji\n",
|
|
16
|
+
"uk": "Виправлення сипучих яскравості / стану\nВиправлення помилок у вигнанні\nВиправлення перезапису державного налаштування\n",
|
|
17
|
+
"zh-cn": "修正同步亮度/ 状态\n在曝光中修复错误\n修改状态配置\n"
|
|
18
|
+
},
|
|
6
19
|
"3.3.1": {
|
|
7
20
|
"en": "Update documentation\nColor Hue/Saturation in Groups\nZH8\nSort by model in Admin\nObject for complex exposes\nPOSSIBLY BREAKING: Complex exposes changed to 'channel / state' structure\nBugfixes\n",
|
|
8
21
|
"de": "Dokumentation zur Aktualisierung\nFarbe Hue/Sättigung in Gruppen\nZH8\nSortieren nach Modell in Admin\nObjekt für komplexe Exponate\nPOSSIBLY BREAKING: Komplex enthüllt geändert in 'Kanal / Zustand' Struktur\nBugfixes\n",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Bugfix na usunąć obiekt.\nulepszone zapytanie urządzenia.\nnaprawione urządzenie usuwające z lokalnymi przekroczeniami.\n",
|
|
81
94
|
"uk": "Виправлення помилок при видаленні об'єкта.\nпокращений пристрій запиту.\nфіксований пристрій видалення з локальними перенаряддями.\n",
|
|
82
95
|
"zh-cn": "删除对象上的错误修正 .\n改进设备查询 .\n有本地覆盖的固定删除设备 .\n"
|
|
83
|
-
},
|
|
84
|
-
"3.2.1": {
|
|
85
|
-
"en": "fix bug #2640\n",
|
|
86
|
-
"de": "fehler beheben #2640\n",
|
|
87
|
-
"ru": "исправить ошибку #2640\n",
|
|
88
|
-
"pt": "corrigir o erro # 2640\n",
|
|
89
|
-
"nl": "fix bug #2640\n",
|
|
90
|
-
"fr": "correction du bug #2640\n",
|
|
91
|
-
"it": "fix bug #2640\n",
|
|
92
|
-
"es": "corrección de error #2640\n",
|
|
93
|
-
"pl": "fix bug # 2640\n",
|
|
94
|
-
"uk": "виправлення помилки #2640\n",
|
|
95
|
-
"zh-cn": "修复错误 # 2640\n"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
package/lib/exposes.js
CHANGED
|
@@ -196,7 +196,11 @@ function generateCompositeStates(expose, _channelID, options)
|
|
|
196
196
|
write: Boolean (expose.access & ea.SET),
|
|
197
197
|
read: Boolean(expose.access & ea.GET),
|
|
198
198
|
type: 'string',
|
|
199
|
-
getter: (value) => {
|
|
199
|
+
getter: (value) => {
|
|
200
|
+
const myval = value[stateId];
|
|
201
|
+
if (myval != undefined && typeof myval === 'object') return JSON.stringify(myval);
|
|
202
|
+
return myval;
|
|
203
|
+
},
|
|
200
204
|
setter: (value) => {
|
|
201
205
|
try {
|
|
202
206
|
return JSON.parse(value);
|
|
@@ -208,100 +212,12 @@ function generateCompositeStates(expose, _channelID, options)
|
|
|
208
212
|
})
|
|
209
213
|
|
|
210
214
|
}
|
|
211
|
-
for (const prop of expose.features) {
|
|
215
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
212
216
|
if (prop.type === 'composite' || prop.type === 'list') {
|
|
213
217
|
states.push(...generateCompositeStates(prop, channelID, options))
|
|
214
218
|
}
|
|
215
219
|
else
|
|
216
220
|
states.push(genState(prop, options.newCompositeMethod ? { fromComposite: true, name: `${channelID}${prop.name}` , channelID: stateId }: { fromComposite: true }))
|
|
217
|
-
/*
|
|
218
|
-
switch (prop.type) {
|
|
219
|
-
case 'numeric':
|
|
220
|
-
case 'text':
|
|
221
|
-
case 'binary':
|
|
222
|
-
{
|
|
223
|
-
const st = genState(prop, 'state', `${channelID}${prop.name}`);
|
|
224
|
-
st.prop = expose.property;
|
|
225
|
-
st.inOptions = !LocalData.options.newCompositeMethod;
|
|
226
|
-
if (LocalData.options.newCompositeMethod) {
|
|
227
|
-
st.compositeKey = stateId;
|
|
228
|
-
st.compositeTimeout = 250;
|
|
229
|
-
st.compositeState = stateId;
|
|
230
|
-
} else
|
|
231
|
-
{
|
|
232
|
-
// if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
|
|
233
|
-
// I'm not fully sure, as it really needed, but
|
|
234
|
-
st.setterOpt = (value, options) => {
|
|
235
|
-
const result = {};
|
|
236
|
-
options[prop.property] = value;
|
|
237
|
-
result[expose.property] = options;
|
|
238
|
-
return result;
|
|
239
|
-
};
|
|
240
|
-
if (prop.access & ea.SET) {
|
|
241
|
-
st.setter = (value, options) => {
|
|
242
|
-
const result = {};
|
|
243
|
-
options[prop.property] = value;
|
|
244
|
-
result[expose.property] = options;
|
|
245
|
-
return result;
|
|
246
|
-
};
|
|
247
|
-
st.setattr = expose.property;
|
|
248
|
-
}
|
|
249
|
-
// if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
|
|
250
|
-
if (prop.access & ea.STATE) {
|
|
251
|
-
st.getter = payload => {
|
|
252
|
-
if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
|
|
253
|
-
return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined;
|
|
254
|
-
} else {
|
|
255
|
-
return undefined;
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
} else {
|
|
259
|
-
st.getter = payload => undefined;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
pushToStates(st, prop.access);
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
case 'list': {
|
|
266
|
-
for (const propList of prop.item_type.features) {
|
|
267
|
-
const st = genState(propList);
|
|
268
|
-
st.prop = expose.property;
|
|
269
|
-
st.inOptions = true;
|
|
270
|
-
st.setterOpt = (value, options) => {
|
|
271
|
-
const result = {};
|
|
272
|
-
options[propList.property] = value;
|
|
273
|
-
result[expose.property] = options;
|
|
274
|
-
return result;
|
|
275
|
-
};
|
|
276
|
-
if (propList.access & ea.SET) {
|
|
277
|
-
st.setter = (value, options) => {
|
|
278
|
-
const result = {};
|
|
279
|
-
options[propList.property] = value;
|
|
280
|
-
result[expose.property] = options;
|
|
281
|
-
return result;
|
|
282
|
-
};
|
|
283
|
-
st.setattr = expose.property;
|
|
284
|
-
}
|
|
285
|
-
if (propList.access & ea.STATE) {
|
|
286
|
-
st.getter = payload => {
|
|
287
|
-
if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(propList.property)) {
|
|
288
|
-
return !isNaN(payload[expose.property][propList.property]) ? payload[expose.property][propList.property] : undefined;
|
|
289
|
-
} else {
|
|
290
|
-
return undefined;
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
} else {
|
|
294
|
-
st.getter = payload => undefined;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
st.id = st.prop + '_' + st.id;
|
|
298
|
-
pushToStates(st, propList.access);
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
*/
|
|
305
221
|
}
|
|
306
222
|
|
|
307
223
|
return states;
|
|
@@ -484,7 +400,7 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
484
400
|
let colorXYprop = undefined;
|
|
485
401
|
let colorHSprop = undefined;
|
|
486
402
|
|
|
487
|
-
for (const prop of expose.features) {
|
|
403
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
488
404
|
switch (prop.name) {
|
|
489
405
|
case 'state': {
|
|
490
406
|
const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
|
|
@@ -837,7 +753,7 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
837
753
|
break;
|
|
838
754
|
}
|
|
839
755
|
case 'switch':
|
|
840
|
-
for (const prop of expose.features) {
|
|
756
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
841
757
|
switch (prop.name) {
|
|
842
758
|
case 'state':
|
|
843
759
|
pushToStates(genState(prop, { role:'switch' }), prop.access);
|
|
@@ -1045,7 +961,7 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
1045
961
|
case 'lock':
|
|
1046
962
|
case 'fan':
|
|
1047
963
|
case 'cover':
|
|
1048
|
-
for (const prop of expose.features) {
|
|
964
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
1049
965
|
switch (prop.name) {
|
|
1050
966
|
case 'state':
|
|
1051
967
|
pushToStates(genState(prop, {role:'switch'}), prop.access);
|
|
@@ -1058,7 +974,7 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
1058
974
|
break;
|
|
1059
975
|
|
|
1060
976
|
case 'climate':
|
|
1061
|
-
for (const prop of expose.features) {
|
|
977
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
1062
978
|
pushToStates(genState(prop), prop.access);
|
|
1063
979
|
}
|
|
1064
980
|
break;
|
package/lib/models.js
CHANGED
|
@@ -668,8 +668,9 @@ async function clearModelDefinitions() {
|
|
|
668
668
|
|
|
669
669
|
function getStateDefinition(name, type, prop) {
|
|
670
670
|
if (typeof name != 'string') return getStateDefinition('ilstate', type, prop);
|
|
671
|
+
if (states.hasOwnProperty(name)) return states[name];
|
|
671
672
|
if (legacyStates.hasOwnProperty(name)) return legacyStates[name];
|
|
672
|
-
return
|
|
673
|
+
return {
|
|
673
674
|
id: name,
|
|
674
675
|
prop: prop || name,
|
|
675
676
|
name: name.replace('_',' '),
|
package/lib/statescontroller.js
CHANGED
|
@@ -751,6 +751,26 @@ class StatesController extends EventEmitter {
|
|
|
751
751
|
setTimeout(() => this.updateState(dev_id, name, outValue, common), timeout);
|
|
752
752
|
}
|
|
753
753
|
|
|
754
|
+
deepCompare(item1, item2) {
|
|
755
|
+
if (typeof item1 != typeof item2) return false; // different object, definitely different.
|
|
756
|
+
switch (typeof item1) {
|
|
757
|
+
case 'boolean':
|
|
758
|
+
case 'number':
|
|
759
|
+
case 'string': return item1 === item2;
|
|
760
|
+
case 'object':
|
|
761
|
+
if (!item1 || !item2) {
|
|
762
|
+
if (!item1 && !item2) return true;
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
for (const key of Object.keys(item1)) {
|
|
766
|
+
if (!this.deepCompare(item1[key],item2[key])) return false;
|
|
767
|
+
}
|
|
768
|
+
return true;
|
|
769
|
+
case 'function':
|
|
770
|
+
return true; // functions are always considere equal
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
754
774
|
async updateState(devId, name, value, common) {
|
|
755
775
|
const new_common = {name: name, color:null};
|
|
756
776
|
const stateId = devId + '.' + name;
|
|
@@ -790,7 +810,7 @@ class StatesController extends EventEmitter {
|
|
|
790
810
|
if (stobj.common) {
|
|
791
811
|
for (const property in new_common) {
|
|
792
812
|
if (stobj.common[property] != undefined) {
|
|
793
|
-
if (stobj.common[property]
|
|
813
|
+
if (this.deepCompare(stobj.common[property],new_common[property])) {
|
|
794
814
|
delete new_common[property];
|
|
795
815
|
}
|
|
796
816
|
}
|
|
@@ -810,7 +830,7 @@ class StatesController extends EventEmitter {
|
|
|
810
830
|
}
|
|
811
831
|
}
|
|
812
832
|
|
|
813
|
-
|
|
833
|
+
if (new_common.color=== null && (stobj?.common?.color === undefined || stobj?.common.color === null)) delete new_common.color;
|
|
814
834
|
// only change object when any common property has changed
|
|
815
835
|
if (Object.keys(new_common).length) {
|
|
816
836
|
this.adapter.extendObject(stateId, {type: 'state', common: new_common, native: {}}, () =>
|
|
@@ -1218,6 +1238,7 @@ class StatesController extends EventEmitter {
|
|
|
1218
1238
|
const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
|
|
1219
1239
|
|
|
1220
1240
|
const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
|
|
1241
|
+
|
|
1221
1242
|
if (has_elevated_debug)
|
|
1222
1243
|
this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'03', IO:true }, message:message});
|
|
1223
1244
|
else
|
package/lib/zigbeecontroller.js
CHANGED
|
@@ -1298,6 +1298,12 @@ class ZigbeeController extends EventEmitter {
|
|
|
1298
1298
|
|
|
1299
1299
|
return;
|
|
1300
1300
|
}
|
|
1301
|
+
// force read for brightness
|
|
1302
|
+
const skey = stateDesc.setattr || stateDesc.prop || stateDesc.id;
|
|
1303
|
+
const readKey = { key: undefined, converter: undefined };
|
|
1304
|
+
if (skey === 'brightness') readKey.key = 'state';
|
|
1305
|
+
if (skey === 'state') readKey.key = 'brightness';
|
|
1306
|
+
|
|
1301
1307
|
for (const c of mappedModel.toZigbee) {
|
|
1302
1308
|
|
|
1303
1309
|
if (!c.hasOwnProperty('convertSet')) continue;
|
|
@@ -1306,6 +1312,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
1306
1312
|
{
|
|
1307
1313
|
if (converter === undefined)
|
|
1308
1314
|
{
|
|
1315
|
+
if (readKey.key && readKey.converter === undefined && c.hasOwnProperty('convertGet'))
|
|
1316
|
+
readKey.converter = c;
|
|
1309
1317
|
converter = c;
|
|
1310
1318
|
if (has_elevated_debug) {
|
|
1311
1319
|
const message = `Setting converter to keyless converter for ${deviceId} of type ${model}`;
|
|
@@ -1340,6 +1348,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
1340
1348
|
converter = c;
|
|
1341
1349
|
msg_counter++;
|
|
1342
1350
|
}
|
|
1351
|
+
if (readKey.key && c.key.includes(readKey.key) && c.hasOwnProperty('convertGet'))
|
|
1352
|
+
readKey.converter = c;
|
|
1343
1353
|
}
|
|
1344
1354
|
if (converter === undefined) {
|
|
1345
1355
|
if (stateDesc.isInternalState) {
|
|
@@ -1471,7 +1481,13 @@ class ZigbeeController extends EventEmitter {
|
|
|
1471
1481
|
` Error: ${error.message}`, `Send command to ${deviceId} failed with`, error);
|
|
1472
1482
|
}
|
|
1473
1483
|
}
|
|
1474
|
-
} while (retry > 0)
|
|
1484
|
+
} while (retry > 0);
|
|
1485
|
+
|
|
1486
|
+
if (readKey.converter) {
|
|
1487
|
+
const tt = preparedOptions.transition || preparedOptions.transition_time;
|
|
1488
|
+
setTimeout(async () => { await readKey.converter.convertGet(target, readKey.key, {device:entity.device})}, tt ? tt * 1000 : 100)
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1475
1491
|
});
|
|
1476
1492
|
} catch (err) {
|
|
1477
1493
|
const message = `No entity for ${deviceId} : ${err && err.message ? err.message : 'no error message'}`;
|
package/main.js
CHANGED
|
@@ -706,7 +706,7 @@ class Zigbee extends utils.Adapter {
|
|
|
706
706
|
const model = (entity.mapped) ? entity.mapped.model : entity.device.modelID;
|
|
707
707
|
if (this.debugActive) this.log.debug(`new device ${device.ieeeAddr} ${device.networkAddress} ${model} `);
|
|
708
708
|
|
|
709
|
-
this.logToPairing(`New device joined '${device.ieeeAddr}' model ${model}`, true);
|
|
709
|
+
if (!obj) this.logToPairing(`New device joined '${device.ieeeAddr}' model ${model}`, true);
|
|
710
710
|
this.stController.updateDev(zbIdorIeeetoAdId(this, device.ieeeAddr, false), model, model, () =>
|
|
711
711
|
this.stController.syncDevStates(device, model));
|
|
712
712
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zigbee",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.2",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Kirov Ilya",
|
|
6
6
|
"email": "kirovilya@gmail.com"
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"ajv": "^8.17.1",
|
|
29
29
|
"uri-js": "^4.4.1",
|
|
30
30
|
"typescript": "^5.9.3",
|
|
31
|
-
"zigbee-herdsman": "^8.0.
|
|
32
|
-
"zigbee-herdsman-converters": "^25.
|
|
31
|
+
"zigbee-herdsman": "^8.0.1",
|
|
32
|
+
"zigbee-herdsman-converters": "^25.99.0"
|
|
33
33
|
},
|
|
34
34
|
"description": "Zigbee devices",
|
|
35
35
|
"devDependencies": {
|