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 CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018-2025 Kirov Ilya <kirovilya@gmail.com>
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
- * ZH8
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-2025 Kirov Ilya <kirovilya@gmail.com>
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.1",
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) => { return (typeof value === 'object' ? JSON.stringify(value) : 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 states[name] || {
673
+ return {
673
674
  id: name,
674
675
  prop: prop || name,
675
676
  name: name.replace('_',' '),
@@ -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] === new_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
@@ -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.1",
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.0",
32
- "zigbee-herdsman-converters": "^25.85.0"
31
+ "zigbee-herdsman": "^8.0.1",
32
+ "zigbee-herdsman-converters": "^25.99.0"
33
33
  },
34
34
  "description": "Zigbee devices",
35
35
  "devDependencies": {