iobroker.zigbee 3.3.1 → 3.3.3
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 +14 -2
- package/io-package.json +27 -27
- package/lib/exposes.js +41 -106
- package/lib/models.js +2 -1
- package/lib/statescontroller.js +29 -3
- package/lib/zigbeecontroller.js +24 -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,22 @@ You can thank the authors by these links:
|
|
|
141
141
|
|
|
142
142
|
-----------------------------------------------------------------------------------------------------
|
|
143
143
|
## Changelog
|
|
144
|
+
### 3.3.3 (2026-01-11)
|
|
145
|
+
* Fix crash bug
|
|
146
|
+
* getter for composite states V1
|
|
147
|
+
* zhc
|
|
148
|
+
*
|
|
149
|
+
|
|
150
|
+
### 3.3.2 (2026-01-04)
|
|
151
|
+
* Fix sync brightness / state
|
|
152
|
+
* Fix bug in expose
|
|
153
|
+
* Fix rewrite state config
|
|
154
|
+
*
|
|
155
|
+
|
|
144
156
|
### 3.3.1 (2025-12-31)
|
|
145
157
|
* Update documentation
|
|
146
158
|
* Color Hue/Saturation in Groups
|
|
147
|
-
*
|
|
159
|
+
* Zigbee-Herdsman v8.x.x
|
|
148
160
|
* Sort by model in Admin
|
|
149
161
|
* Object for complex exposes
|
|
150
162
|
* POSSIBLY BREAKING: Complex exposes changed to 'channel / state' structure
|
|
@@ -411,7 +423,7 @@ You can thank the authors by these links:
|
|
|
411
423
|
## License
|
|
412
424
|
The MIT License (MIT)
|
|
413
425
|
|
|
414
|
-
Copyright (c) 2018-
|
|
426
|
+
Copyright (c) 2018-2026 Kirov Ilya <kirovilya@gmail.com>
|
|
415
427
|
|
|
416
428
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
417
429
|
of this software and associated documentation files (the "Software"), to deal
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zigbee",
|
|
4
|
-
"version": "3.3.
|
|
4
|
+
"version": "3.3.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"3.3.3": {
|
|
7
|
+
"en": "Fix crash bug\ngetter for composite states V1\nzhc\n",
|
|
8
|
+
"de": "Fehler behoben\ngetter für zusammengesetzte Zustände V1\nzh\n",
|
|
9
|
+
"ru": "Исправить ошибку crash bug\nгеттер для композитных состояний V1\nджк\n",
|
|
10
|
+
"pt": "Corrigir erro de falha\ngetter para estados compostos V1\nzhc\n",
|
|
11
|
+
"nl": "Fix crash bug\ngetter voor samengestelde toestanden V1\nzhc\n",
|
|
12
|
+
"fr": "Correction d'un bug de plantage\ngetter pour les états composites V1\nzhc\n",
|
|
13
|
+
"it": "Correre bug crash\ngetter per stati compositi V1\nzhc\n",
|
|
14
|
+
"es": "Corrección de fallo\ngetter para estados compuestos V1\nzhc\n",
|
|
15
|
+
"pl": "Napraw błąd awarii\ngetter for composite states V1\nzhc\n",
|
|
16
|
+
"uk": "Виправлення помилки збою\nоберт для композитних станів V1\nжк\n",
|
|
17
|
+
"zh-cn": "修复崩溃错误\nv1 复合状态获取器\n日语\n"
|
|
18
|
+
},
|
|
19
|
+
"3.3.2": {
|
|
20
|
+
"en": "Fix sync brightness / state\nFix bug in expose\nFix rewrite state config\n",
|
|
21
|
+
"de": "Synchronisierhelligkeit / Zustand\nFehler behoben in exponieren\nRewriting State config\n",
|
|
22
|
+
"ru": "Исправление яркости синхронизации / состояние\nУстранение ошибки в разоблачении\nRewrite state config (переписать конфигурацию состояния)\n",
|
|
23
|
+
"pt": "Corrigir o brilho / estado da sincronização\nCorrigir erro na exposição\nCorrigir a configuração do estado de reescrita\n",
|
|
24
|
+
"nl": "Synchronische helderheid / status herstellen\nFix bug in expos\nHerschrijf statusconfiguratie\n",
|
|
25
|
+
"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",
|
|
26
|
+
"it": "Fissare la luminosità di sincronizzazione / stato\nFissare bug in esposizione\nRiparare la configurazione dello stato di riscrittura\n",
|
|
27
|
+
"es": "Fijar el brillo de sincronización / estado\nFijar fallo en exponer\nReescribir el config del estado\n",
|
|
28
|
+
"pl": "Napraw jasność / stan synchronizacji\nPopraw błąd w odsłonięciu\nPopraw stan konfiguracji\n",
|
|
29
|
+
"uk": "Виправлення сипучих яскравості / стану\nВиправлення помилок у вигнанні\nВиправлення перезапису державного налаштування\n",
|
|
30
|
+
"zh-cn": "修正同步亮度/ 状态\n在曝光中修复错误\n修改状态配置\n"
|
|
31
|
+
},
|
|
6
32
|
"3.3.1": {
|
|
7
33
|
"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
34
|
"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",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Ulepszenia dotyczące debugowania interfejsu użytkownika\nOpcja \"resend _ states\" do publikowania wartości stanu dla urządzenia przy ponownym połączeniu\nUlepszona karta grupowa\nUlepszone informacje grupowe\nZmodyfikowana karta koordynatora (2 strony)\nponowna próba błędu 25\njasne komunikaty błędów ukryte\nZHC 25.50.0 lub nowsze",
|
|
68
94
|
"uk": "Удосконалення на дебурзі UI\nВаріант 'resend_states' для публікації державних значень для пристрою на відключенні\nПокращена групова карта\nПокращена інформація групи\nЗмінена карта координатора (2 сторони)\nптиця по помилки 25\nчіткі повідомлення про помилку\nZHC 25.50.0 або новачок",
|
|
69
95
|
"zh-cn": "调试 UI 的改进\n选项“ resend_ states ” 以在重新连接时发布状态值以设备\n改进组卡\n改进组信息\n修改的协调员卡(2面)\n重试错误 25\n清除隐藏的错误消息\nZHC 25.50.0或更新"
|
|
70
|
-
},
|
|
71
|
-
"3.2.2": {
|
|
72
|
-
"en": "Bugfix on delete object.\nimproved device query.\nfixed delete device with local overrides.\n",
|
|
73
|
-
"de": "Bugfix auf Löschobjekt.\nverbesserte geräteabfrage.\nfeste löschvorrichtung mit lokalen overrides.\n",
|
|
74
|
-
"ru": "Bugfix для удаления объекта.\nулучшенный запрос устройства.\nфиксированное устройство удаления с локальными переопределениями.\n",
|
|
75
|
-
"pt": "Correcção de erros ao apagar o objecto.\npesquisa de dispositivos melhorada.\ndispositivo de exclusão fixo com substituições locais.\n",
|
|
76
|
-
"nl": "Bugfix bij verwijderen van object.\nverbeterde apparaatquery.\nvast delete apparaat met lokale overrides.\n",
|
|
77
|
-
"fr": "Correction sur objet de suppression.\nrequête de périphérique améliorée.\ndispositif de suppression fixe avec redéfinitions locales.\n",
|
|
78
|
-
"it": "Bugfix su delete object.\nmigliore query del dispositivo.\ndispositivo di cancellazione fisso con override locali.\n",
|
|
79
|
-
"es": "Bugfix en el objeto borrado.\nmejorada consulta de dispositivos.\ndispositivo de eliminación fijo con anulas locales.\n",
|
|
80
|
-
"pl": "Bugfix na usunąć obiekt.\nulepszone zapytanie urządzenia.\nnaprawione urządzenie usuwające z lokalnymi przekroczeniami.\n",
|
|
81
|
-
"uk": "Виправлення помилок при видаленні об'єкта.\nпокращений пристрій запиту.\nфіксований пристрій видалення з локальними перенаряддями.\n",
|
|
82
|
-
"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
|
@@ -180,15 +180,32 @@ function genState(expose, overrides) {
|
|
|
180
180
|
|
|
181
181
|
function generateCompositeStates(expose, _channelID, options)
|
|
182
182
|
{
|
|
183
|
-
const states = [];
|
|
184
183
|
const stname = `${_channelID}${expose.property}`;
|
|
185
184
|
const stateId = stname.replace(/\*/g, '');
|
|
186
185
|
const stateName = (expose.description || expose.name);
|
|
187
186
|
const channelID = options.newCompositeMethod ? `c_${stateId}.` : '';
|
|
187
|
+
const stateData = {
|
|
188
|
+
states: [],
|
|
189
|
+
keys:[]
|
|
190
|
+
};
|
|
191
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
192
|
+
if (prop.type === 'composite' || prop.type === 'list') {
|
|
193
|
+
const subStateData = generateCompositeStates(prop, channelID, options);
|
|
194
|
+
if (options.newCompositeMethod)
|
|
195
|
+
stateData.keys.push(prop.property);
|
|
196
|
+
else
|
|
197
|
+
stateData.keys.push(...subStateData.keys);
|
|
198
|
+
stateData.states.push(...subStateData.states);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
stateData.states.push(genState(prop, options.newCompositeMethod ? { fromComposite: true, name: `${channelID}${prop.property}` , channelID: stateId }: { fromComposite: true }))
|
|
202
|
+
stateData.keys.push(prop.property)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
188
205
|
if (options.newCompositeMethod) {
|
|
189
|
-
|
|
190
206
|
if (typeof stname !== 'string') return undefined;;
|
|
191
|
-
|
|
207
|
+
const KeysToUpdate = stateData.keys;
|
|
208
|
+
stateData.states.push({
|
|
192
209
|
id: stateId,
|
|
193
210
|
name: stateName,
|
|
194
211
|
icon: undefined,
|
|
@@ -196,7 +213,17 @@ function generateCompositeStates(expose, _channelID, options)
|
|
|
196
213
|
write: Boolean (expose.access & ea.SET),
|
|
197
214
|
read: Boolean(expose.access & ea.GET),
|
|
198
215
|
type: 'string',
|
|
199
|
-
|
|
216
|
+
subkeys : stateData.keys,
|
|
217
|
+
getter: (value) => {
|
|
218
|
+
const myval = value[stateId];
|
|
219
|
+
if (myval != undefined && typeof myval === 'object') return JSON.stringify(myval);
|
|
220
|
+
return myval;
|
|
221
|
+
},
|
|
222
|
+
compositeGetter: (value) => {
|
|
223
|
+
const myval = value[stateId];
|
|
224
|
+
if (typeof myval === 'object') return myval;
|
|
225
|
+
return undefined;
|
|
226
|
+
},
|
|
200
227
|
setter: (value) => {
|
|
201
228
|
try {
|
|
202
229
|
return JSON.parse(value);
|
|
@@ -208,103 +235,8 @@ function generateCompositeStates(expose, _channelID, options)
|
|
|
208
235
|
})
|
|
209
236
|
|
|
210
237
|
}
|
|
211
|
-
for (const prop of expose.features) {
|
|
212
|
-
if (prop.type === 'composite' || prop.type === 'list') {
|
|
213
|
-
states.push(...generateCompositeStates(prop, channelID, options))
|
|
214
|
-
}
|
|
215
|
-
else
|
|
216
|
-
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
|
-
}
|
|
306
238
|
|
|
307
|
-
return
|
|
239
|
+
return stateData;
|
|
308
240
|
|
|
309
241
|
}
|
|
310
242
|
function createFromExposes(model, def, device, options, log) {
|
|
@@ -484,7 +416,7 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
484
416
|
let colorXYprop = undefined;
|
|
485
417
|
let colorHSprop = undefined;
|
|
486
418
|
|
|
487
|
-
for (const prop of expose.features) {
|
|
419
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
488
420
|
switch (prop.name) {
|
|
489
421
|
case 'state': {
|
|
490
422
|
const stateNameS = expose.endpoint ? `state_${expose.endpoint}` : 'state';
|
|
@@ -837,7 +769,7 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
837
769
|
break;
|
|
838
770
|
}
|
|
839
771
|
case 'switch':
|
|
840
|
-
for (const prop of expose.features) {
|
|
772
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
841
773
|
switch (prop.name) {
|
|
842
774
|
case 'state':
|
|
843
775
|
pushToStates(genState(prop, { role:'switch' }), prop.access);
|
|
@@ -1045,7 +977,7 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
1045
977
|
case 'lock':
|
|
1046
978
|
case 'fan':
|
|
1047
979
|
case 'cover':
|
|
1048
|
-
for (const prop of expose.features) {
|
|
980
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
1049
981
|
switch (prop.name) {
|
|
1050
982
|
case 'state':
|
|
1051
983
|
pushToStates(genState(prop, {role:'switch'}), prop.access);
|
|
@@ -1058,7 +990,7 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
1058
990
|
break;
|
|
1059
991
|
|
|
1060
992
|
case 'climate':
|
|
1061
|
-
for (const prop of expose.features) {
|
|
993
|
+
if (expose?.features?.[Symbol.iterator]) for (const prop of expose.features) {
|
|
1062
994
|
pushToStates(genState(prop), prop.access);
|
|
1063
995
|
}
|
|
1064
996
|
break;
|
|
@@ -1066,8 +998,11 @@ function createFromExposes(model, def, device, options, log) {
|
|
|
1066
998
|
case 'composite':
|
|
1067
999
|
{
|
|
1068
1000
|
const cStates = generateCompositeStates(expose, '', options);
|
|
1069
|
-
for (const state of cStates)
|
|
1070
|
-
|
|
1001
|
+
for (const state of cStates.states)
|
|
1002
|
+
if (options.newCompositeMethod)
|
|
1003
|
+
pushToStates(state, expose.access);
|
|
1004
|
+
else
|
|
1005
|
+
pushToStates(modifyState(state, { pairedkeys: cStates.keys } , false), expose.access);
|
|
1071
1006
|
break;
|
|
1072
1007
|
}
|
|
1073
1008
|
case 'list':
|
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: {}}, () =>
|
|
@@ -1153,6 +1173,11 @@ class StatesController extends EventEmitter {
|
|
|
1153
1173
|
|
|
1154
1174
|
let stateID = statedesc.id;
|
|
1155
1175
|
|
|
1176
|
+
if (statedesc.compositeGetter) {
|
|
1177
|
+
const compositePayload = statedesc.compositeGetter(payload);
|
|
1178
|
+
if (compositePayload) await this.publishToState(devId, model, compositePayload, debugId);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1156
1181
|
// checking value
|
|
1157
1182
|
if (value === undefined || value === null) {
|
|
1158
1183
|
const message = `value '${JSON.stringify(value)}' from device ${devId} via ${mode} for '${statedesc.name} (ID ${statedesc.id} ${statedesc.prop ? 'with property '+statedesc.prop : ''})`;
|
|
@@ -1218,6 +1243,7 @@ class StatesController extends EventEmitter {
|
|
|
1218
1243
|
const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
|
|
1219
1244
|
|
|
1220
1245
|
const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
|
|
1246
|
+
|
|
1221
1247
|
if (has_elevated_debug)
|
|
1222
1248
|
this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'03', IO:true }, message:message});
|
|
1223
1249
|
else
|
|
@@ -1232,7 +1258,7 @@ class StatesController extends EventEmitter {
|
|
|
1232
1258
|
let has_published = false;
|
|
1233
1259
|
if (devStates.states !== undefined) {
|
|
1234
1260
|
try {
|
|
1235
|
-
const states = devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop
|
|
1261
|
+
const states = devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop ? statedesc.prop: statedesc.id)
|
|
1236
1262
|
);
|
|
1237
1263
|
|
|
1238
1264
|
for (const stateInd in states) {
|
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) {
|
|
@@ -1455,6 +1465,17 @@ class ZigbeeController extends EventEmitter {
|
|
|
1455
1465
|
}
|
|
1456
1466
|
}
|
|
1457
1467
|
retry = 0;
|
|
1468
|
+
if (readKey.converter) {
|
|
1469
|
+
const tt = preparedOptions.transition || preparedOptions.transition_time;
|
|
1470
|
+
const did = deviceId;
|
|
1471
|
+
setTimeout(async () => {
|
|
1472
|
+
try {
|
|
1473
|
+
await readKey.converter.convertGet(target, readKey.key, {device:entity.device}) }
|
|
1474
|
+
catch (error) {
|
|
1475
|
+
this.warn(`error reading ${readKey.key} from ${deviceId} : ${error?.message}`);
|
|
1476
|
+
}
|
|
1477
|
+
}, tt ? tt * 1000 : 100);
|
|
1478
|
+
}
|
|
1458
1479
|
} catch (error) {
|
|
1459
1480
|
if (has_elevated_debug) {
|
|
1460
1481
|
const message = `caught error ${error?.message? error.message : 'no reason given'} when setting value for device ${deviceId}.`;
|
|
@@ -1471,7 +1492,9 @@ class ZigbeeController extends EventEmitter {
|
|
|
1471
1492
|
` Error: ${error.message}`, `Send command to ${deviceId} failed with`, error);
|
|
1472
1493
|
}
|
|
1473
1494
|
}
|
|
1474
|
-
} while (retry > 0)
|
|
1495
|
+
} while (retry > 0);
|
|
1496
|
+
|
|
1497
|
+
|
|
1475
1498
|
});
|
|
1476
1499
|
} catch (err) {
|
|
1477
1500
|
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.3",
|
|
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": "
|
|
32
|
-
"zigbee-herdsman-converters": "
|
|
31
|
+
"zigbee-herdsman": "8.0.2",
|
|
32
|
+
"zigbee-herdsman-converters": "25.104.0"
|
|
33
33
|
},
|
|
34
34
|
"description": "Zigbee devices",
|
|
35
35
|
"devDependencies": {
|