iobroker.zigbee 3.3.0 → 3.3.1
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 +45 -48
- package/admin/admin.js +39 -7
- package/admin/tab_m.html +19 -0
- package/io-package.json +14 -14
- package/lib/colors.js +182 -421
- package/lib/exposes.js +206 -198
- package/lib/groups.js +2 -5
- package/lib/legacy/devices.js +8 -5
- package/lib/models.js +99 -11
- package/lib/statescontroller.js +125 -133
- package/lib/zigbeecontroller.js +42 -9
- package/main.js +15 -20
- package/package.json +5 -5
package/lib/models.js
CHANGED
|
@@ -7,7 +7,7 @@ const { findByDevice } = require('zigbee-herdsman-converters');
|
|
|
7
7
|
const herdsmanModelInfo = new Map();
|
|
8
8
|
const UUIDbyDevice = new Map();
|
|
9
9
|
|
|
10
|
-
const { ParseColor } = require('./colors');
|
|
10
|
+
const { ParseColor, complexColor, CC_DEFS } = require('./colors');
|
|
11
11
|
const { rgb_to_cie, cie_to_rgb } = require('./rgb');
|
|
12
12
|
const { decimalToHex, adapterLevelToBulbLevel, bulbLevelToAdapterLevel , toMired } = require('./utils');
|
|
13
13
|
|
|
@@ -245,6 +245,8 @@ const states = {
|
|
|
245
245
|
setter: (value) => {
|
|
246
246
|
|
|
247
247
|
// convert RGB to XY for set
|
|
248
|
+
const objColor = complexColor(value, true);
|
|
249
|
+
if (objColor) return objColor;
|
|
248
250
|
let xy = [0, 0];
|
|
249
251
|
const rgbcolor = ParseColor(value);
|
|
250
252
|
xy = rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
|
|
@@ -259,14 +261,100 @@ const states = {
|
|
|
259
261
|
return {...options, transition: transitionTime};
|
|
260
262
|
},
|
|
261
263
|
getter: payload => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return
|
|
265
|
-
} else {
|
|
266
|
-
return undefined;
|
|
264
|
+
const value = payload.color;
|
|
265
|
+
if (value && typeof value === 'object') {
|
|
266
|
+
return complexColor(value, false);
|
|
267
267
|
}
|
|
268
|
+
if (typeof value === 'string') return value;
|
|
269
|
+
return undefined;
|
|
268
270
|
},
|
|
269
271
|
},
|
|
272
|
+
color_hue: {
|
|
273
|
+
id: `color_hs.hue`,
|
|
274
|
+
prop:'color',
|
|
275
|
+
name: `Hue`,
|
|
276
|
+
icon: undefined,
|
|
277
|
+
role: 'level.color.hue',
|
|
278
|
+
write: true,
|
|
279
|
+
read: true,
|
|
280
|
+
type: 'number',
|
|
281
|
+
min: 0,
|
|
282
|
+
max: 360,
|
|
283
|
+
compositeKey: 'color_hs',
|
|
284
|
+
compositeTimeout: 500,
|
|
285
|
+
compositeState: 'color',
|
|
286
|
+
getter: (payload) => {
|
|
287
|
+
if (typeof payload.color == 'object') {
|
|
288
|
+
if (payload.color?.hue) {
|
|
289
|
+
return payload.color.hue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
color_saturation: {
|
|
295
|
+
id: `color_hs.saturation`,
|
|
296
|
+
prop:'color',
|
|
297
|
+
name: `Saturation`,
|
|
298
|
+
icon: undefined,
|
|
299
|
+
role: 'level.color.saturation',
|
|
300
|
+
write: true,
|
|
301
|
+
read: true,
|
|
302
|
+
type: 'number',
|
|
303
|
+
min: 0,
|
|
304
|
+
max: 100,
|
|
305
|
+
compositeKey: 'color_hs',
|
|
306
|
+
compositeTimeout: 500,
|
|
307
|
+
compositeState: 'color',
|
|
308
|
+
getter: (payload) => {
|
|
309
|
+
if (typeof payload.color == 'object') {
|
|
310
|
+
if (payload.color?.saturation) {
|
|
311
|
+
return payload.color.saturation;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
color_red: {
|
|
317
|
+
id: `color_rgb.r`,
|
|
318
|
+
name: `Red`,
|
|
319
|
+
icon: undefined,
|
|
320
|
+
role: 'level.color.red',
|
|
321
|
+
write: true,
|
|
322
|
+
read: true,
|
|
323
|
+
type: 'number',
|
|
324
|
+
min: 0,
|
|
325
|
+
max: 255,
|
|
326
|
+
compositeKey: 'color_rgb',
|
|
327
|
+
compositeTimeout: 500,
|
|
328
|
+
compositeState: 'color',
|
|
329
|
+
},
|
|
330
|
+
color_green: {
|
|
331
|
+
id: `color_rgb.g`,
|
|
332
|
+
name: `Green`,
|
|
333
|
+
icon: undefined,
|
|
334
|
+
role: 'level.color.green',
|
|
335
|
+
write: true,
|
|
336
|
+
read: true,
|
|
337
|
+
type: 'number',
|
|
338
|
+
min: 0,
|
|
339
|
+
max: 255,
|
|
340
|
+
compositeKey: 'color_rgb',
|
|
341
|
+
compositeTimeout: 500,
|
|
342
|
+
compositeState: 'color',
|
|
343
|
+
},
|
|
344
|
+
color_blue: {
|
|
345
|
+
id: `color_rgb.b`,
|
|
346
|
+
name: `Blue`,
|
|
347
|
+
icon: undefined,
|
|
348
|
+
role: 'level.color.blue',
|
|
349
|
+
write: true,
|
|
350
|
+
read: true,
|
|
351
|
+
type: 'number',
|
|
352
|
+
min: 0,
|
|
353
|
+
max: 255,
|
|
354
|
+
compositeKey: 'color_rgb',
|
|
355
|
+
compositeTimeout: 500,
|
|
356
|
+
compositeState: 'color'
|
|
357
|
+
},
|
|
270
358
|
hex_color: {
|
|
271
359
|
id:`hex_color`,
|
|
272
360
|
name: `Hex Color`,
|
|
@@ -489,14 +577,14 @@ const states = {
|
|
|
489
577
|
|
|
490
578
|
|
|
491
579
|
const lightStates = [states.state, states.brightness, states.brightness_move, states.transition_time, states.brightness_step],
|
|
492
|
-
lightStatesWithColor=[...lightStates, states.color, states.hex_color, states.colortemp, states.colortemp_move],
|
|
580
|
+
lightStatesWithColor=[...lightStates, states.color, states.hex_color, states.colortemp, states.colortemp_move,states.color_red, states.color_green, states.color_blue],
|
|
493
581
|
onOffStates=[states.state],
|
|
494
582
|
lightStatesWithColortemp = [...lightStates, states.colortemp, states.colortemp_move],
|
|
495
|
-
lightStatesWithColor_hue= [...lightStatesWithColor, states.hue_move, states.transition_time, states.effect_type_hue],
|
|
496
|
-
lightStatesWithColorNoTemp= [...lightStates, states.color],
|
|
583
|
+
lightStatesWithColor_hue= [...lightStatesWithColor, states.hue_move, states.transition_time, /*states.effect_type_hue,*/ states.color_hue, states.color_saturation],
|
|
584
|
+
lightStatesWithColorNoTemp= [...lightStates, states.color, states.color_hue, states.color_saturation],
|
|
497
585
|
commonStates=Object.values(states).filter((candidate) => candidate.isCommonState),
|
|
498
586
|
commonGroupStates=[states.groupstateupdate, states.groupmemberupdate],
|
|
499
|
-
groupStates=[states.groupstateupdate, states.groupmemberupdate, ...
|
|
587
|
+
groupStates=[states.groupstateupdate, states.groupmemberupdate, ...lightStatesWithColor_hue, ...onOffStates];
|
|
500
588
|
|
|
501
589
|
/**
|
|
502
590
|
* Returns a model description, if one exists
|
|
@@ -557,7 +645,7 @@ async function addExposeToDevices(device, model, logger) {
|
|
|
557
645
|
adapterModelDefinition.states = commonStates;
|
|
558
646
|
const adapterModel = await findModel(model, device.ieeeAddr, false);
|
|
559
647
|
if (adapterModel) return adapterModel;
|
|
560
|
-
const { newModel, message, error } = await applyHerdsmanModel(adapterModelDefinition);
|
|
648
|
+
const { newModel, message, error } = await applyHerdsmanModel(adapterModelDefinition, { newCompositeMethod: true });
|
|
561
649
|
if (error) {
|
|
562
650
|
if (logger) logger.warn(`${message} : ${error?.message}`);
|
|
563
651
|
}
|
package/lib/statescontroller.js
CHANGED
|
@@ -99,87 +99,92 @@ class StatesController extends EventEmitter {
|
|
|
99
99
|
const srcIcon = (modelDesc && modelDesc.icon ? modelDesc.icon : '');
|
|
100
100
|
const model_modif = model.replace(/\//g, '-');
|
|
101
101
|
const pathToAdminIcon = `img/${model_modif}.png`;
|
|
102
|
-
|
|
103
|
-
if (srcIcon
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.downloadIconToAdmin(srcIcon, pathToAdminIcon)
|
|
107
|
-
} catch (err) {
|
|
108
|
-
this.warn(`ERROR : unable to download ${srcIcon}: ${err && err.message ? err.message : 'no reason given'}`);
|
|
109
|
-
}
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
// source is inline basee64F
|
|
113
|
-
const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
|
|
114
|
-
if (base64Match) {
|
|
115
|
-
this.warn(`base 64 Icon matched, trying to save it to disk as ${pathToAdminIcon}`);
|
|
116
|
-
if (modelDesc) modelDesc.icon = pathToAdminIcon;
|
|
117
|
-
this.adapter.fileExists(namespace, pathToAdminIcon, async (err,result) => {
|
|
118
|
-
if (result) {
|
|
119
|
-
this.info(`no need to save icon to ${pathToAdminIcon}`);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const msg = `Saving base64 Data to ${pathToAdminIcon}`
|
|
102
|
+
|
|
103
|
+
if (srcIcon != '') {
|
|
104
|
+
// source is a web address
|
|
105
|
+
if (srcIcon.startsWith('http')) {
|
|
123
106
|
try {
|
|
124
|
-
|
|
125
|
-
this.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
this.debug(`${msg} -- success`);
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
catch (err) {
|
|
134
|
-
this.warn(`${msg} -- failed: ${err?.message || 'no reason given'}`)
|
|
107
|
+
//if (modelDesc) modelDesc.icon = pathToAdminIcon;
|
|
108
|
+
this.downloadIconToAdmin(srcIcon, pathToAdminIcon);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
this.warn(`ERROR : unable to download ${srcIcon}: ${err && err.message ? err.message : 'no reason given'}`);
|
|
135
111
|
}
|
|
136
|
-
});
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
// path is absolute
|
|
140
|
-
if (modelDesc) modelDesc.icon = pathToAdminIcon;
|
|
141
|
-
this.adapter.fileExists(namespace, pathToAdminIcon, async(err, result) => {
|
|
142
|
-
if (result) {
|
|
143
|
-
this.debug(`icon ${modelDesc?.icon || 'unknown icon'} found - no copy needed`);
|
|
144
112
|
return;
|
|
145
113
|
}
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
114
|
+
// source is inline basee64F
|
|
115
|
+
const base64Match = srcIcon.match(/data:image\/(.+);base64,/);
|
|
116
|
+
if (base64Match) {
|
|
117
|
+
this.warn(`base 64 Icon matched, trying to save it to disk as ${pathToAdminIcon}`);
|
|
118
|
+
if (modelDesc) modelDesc.icon = pathToAdminIcon;
|
|
119
|
+
this.adapter.fileExists(namespace, pathToAdminIcon, async (err,result) => {
|
|
120
|
+
if (result) {
|
|
121
|
+
this.info(`no need to save icon to ${pathToAdminIcon}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const msg = `Saving base64 Data to ${pathToAdminIcon}`
|
|
125
|
+
try {
|
|
126
|
+
const buffer = Buffer.from(srcIcon.replace(base64Match[0],''), 'base64');
|
|
127
|
+
this.adapter.writeFile(namespace, pathToAdminIcon, buffer, (err) => {
|
|
128
|
+
if (err) {
|
|
129
|
+
this.warn(`${msg} -- failed: ${err?.message || 'no reason given'}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.debug(`${msg} -- success`);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
this.warn(`${msg} -- failed: ${err?.message || 'no reason given'}`)
|
|
137
|
+
}
|
|
138
|
+
});
|
|
163
139
|
return;
|
|
164
140
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
141
|
+
// path is absolute
|
|
142
|
+
if (modelDesc) modelDesc.icon = pathToAdminIcon;
|
|
143
|
+
this.adapter.fileExists(namespace, pathToAdminIcon, async(err, result) => {
|
|
144
|
+
if (result) {
|
|
145
|
+
this.debug(`icon ${modelDesc?.icon || 'unknown icon'} found - no copy needed`);
|
|
168
146
|
return;
|
|
169
147
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
148
|
+
// try 3 options for source file
|
|
149
|
+
let src = srcIcon; // as given
|
|
150
|
+
const locations=[];
|
|
151
|
+
if (!fs.existsSync(src)) {
|
|
152
|
+
src = path.normalize(this.adapter.expandFileName(src));
|
|
153
|
+
locations.push(src);
|
|
154
|
+
} // assumed relative to data folder
|
|
155
|
+
if (!fs.existsSync(src)) {
|
|
156
|
+
locations.push(src);
|
|
157
|
+
src = path.normalize(this.adapter.expandFileName(path.basename(src)));
|
|
158
|
+
} // filename relative to data folder
|
|
159
|
+
if (!fs.existsSync(src)) {
|
|
160
|
+
locations.push(src);
|
|
161
|
+
src = path.normalize(this.adapter.expandFileName(path.join('img',path.basename(src))));
|
|
162
|
+
} // img/<filename> relative to data folder
|
|
163
|
+
if (!fs.existsSync(src)) {
|
|
164
|
+
this.warn(`Unable to copy icon from device definition, looked at ${locations.join(', ')} and ${src}`);
|
|
178
165
|
return;
|
|
179
166
|
}
|
|
180
|
-
|
|
167
|
+
fs.readFile(src, (err, data) => {
|
|
168
|
+
if (err) {
|
|
169
|
+
this.warn(`unable to read icon from ${src}: ${(err.message? err.message:' no message given')}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (data) {
|
|
173
|
+
this.adapter.writeFile(namespace, pathToAdminIcon, data, (err) => {
|
|
174
|
+
if (err) {
|
|
175
|
+
this.error(`error writing file ${path}: ${err.message ? err.message : 'no reason given'}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.debug('Updated image file ' + pathToAdminIcon);
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
this.error(`fs.readFile failed - neither error nor data is returned!`);
|
|
183
|
+
});
|
|
181
184
|
});
|
|
182
|
-
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
183
188
|
return modelDesc;
|
|
184
189
|
|
|
185
190
|
}
|
|
@@ -430,13 +435,19 @@ class StatesController extends EventEmitter {
|
|
|
430
435
|
}
|
|
431
436
|
|
|
432
437
|
async triggerComposite(_deviceId, stateDesc, interactive) {
|
|
433
|
-
const deviceId =
|
|
438
|
+
const deviceId = typeof _deviceId === 'string' ? _deviceId.replace('0x', '') : `group_${_deviceId}`;
|
|
434
439
|
const idParts = stateDesc.id.split('.').slice(-2);
|
|
435
440
|
const key = `${deviceId}.${idParts[0]}`;
|
|
436
|
-
|
|
441
|
+
if (this.compositeData[key] == undefined)
|
|
442
|
+
this.compositeData[key] = {triggers: [stateDesc.id]};
|
|
443
|
+
else this.compositeData[key].triggers.push(stateDesc.id);
|
|
444
|
+
const handle = this.compositeData[key].handle;
|
|
437
445
|
const factor = (interactive ? 10 : 1);
|
|
438
446
|
if (handle) this.adapter.clearTimeout(handle);
|
|
439
|
-
this.compositeData[key] = this.adapter.setTimeout(async () => {
|
|
447
|
+
this.compositeData[key].handle = this.adapter.setTimeout(async () => {
|
|
448
|
+
for (const trigger of this.compositeData[key].triggers) {
|
|
449
|
+
this.emit('acknowledge_state', deviceId, undefined, { id:trigger} , undefined );
|
|
450
|
+
}
|
|
440
451
|
delete this.compositeData[key];
|
|
441
452
|
const parts = stateDesc.id.split('.');
|
|
442
453
|
parts.pop();
|
|
@@ -699,7 +710,7 @@ class StatesController extends EventEmitter {
|
|
|
699
710
|
statename = arr[1];
|
|
700
711
|
}
|
|
701
712
|
if (commonStates.find(statedesc => statename === statedesc.id) === undefined &&
|
|
702
|
-
devStates.states.find(statedesc => statename === statedesc
|
|
713
|
+
devStates.states.find(statedesc => statename === statedesc?.id) === undefined
|
|
703
714
|
) {
|
|
704
715
|
this.cleanupRequired |= markOnly;
|
|
705
716
|
if (state.common.hasOwnProperty('custom') && !force && !markOnly) {
|
|
@@ -740,43 +751,50 @@ class StatesController extends EventEmitter {
|
|
|
740
751
|
setTimeout(() => this.updateState(dev_id, name, outValue, common), timeout);
|
|
741
752
|
}
|
|
742
753
|
|
|
743
|
-
async updateState(devId, name, value, common
|
|
754
|
+
async updateState(devId, name, value, common) {
|
|
744
755
|
const new_common = {name: name, color:null};
|
|
745
756
|
const stateId = devId + '.' + name;
|
|
746
|
-
if (common) {
|
|
757
|
+
if (common && typeof common === 'object') {
|
|
747
758
|
for (const key in common) {
|
|
748
759
|
if (common[key] !== undefined) new_common[key] = common[key];
|
|
749
760
|
}
|
|
750
761
|
}
|
|
751
|
-
|
|
762
|
+
|
|
752
763
|
const stobj = await this.adapter.getObjectAsync(stateId);
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
764
|
+
const type = (stobj?.common?.type) ? stobj.common.type : new_common.type;
|
|
765
|
+
// first check value
|
|
766
|
+
if (value !== undefined && type === 'number') {
|
|
767
|
+
const minval = parseFloat(stobj?.common ? stobj.common.min : new_common.min);
|
|
768
|
+
const maxval = parseFloat(stobj?.common ? stobj.common.max : new_common.max);
|
|
769
|
+
let nval = (typeof value == 'number' ? value : parseFloat(value));
|
|
770
|
+
if (isNaN(nval)) {
|
|
771
|
+
if (minval !== undefined && typeof minval === 'number')
|
|
772
|
+
nval = minval;
|
|
773
|
+
else
|
|
774
|
+
nval = 0;
|
|
760
775
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
+
if (nval < minval) {
|
|
777
|
+
new_common.color = '#FF0000'
|
|
778
|
+
value = minval
|
|
779
|
+
this.stashErrors(`${stateId}.min`,`State value for ${stateId} has value "${nval}" less than min "${minval}".`, false );
|
|
780
|
+
} else if (nval > maxval) {
|
|
781
|
+
new_common.color = '#FF0000'
|
|
782
|
+
value = maxval;
|
|
783
|
+
this.stashErrors(`${stateId}.max`,`State value for ${stateId} has value "${nval}" greater than max "${maxval}".`, false );
|
|
784
|
+
}
|
|
785
|
+
else new_common.color = new_common.color ? new_common.color : null;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (stobj) {
|
|
789
|
+
// always update common if common is provided
|
|
790
|
+
if (stobj.common) {
|
|
791
|
+
for (const property in new_common) {
|
|
792
|
+
if (stobj.common[property] != undefined) {
|
|
793
|
+
if (stobj.common[property] === new_common[property]) {
|
|
794
|
+
delete new_common[property];
|
|
776
795
|
}
|
|
777
796
|
}
|
|
778
797
|
}
|
|
779
|
-
else hasChanges = true; // we dont have common => we need common
|
|
780
798
|
}
|
|
781
799
|
} else {
|
|
782
800
|
const matches = stateId.match((/\./g));
|
|
@@ -790,42 +808,14 @@ class StatesController extends EventEmitter {
|
|
|
790
808
|
await this.adapter.extendObjectAsync(id, {type: 'channel', common: { name:channel}, native:{}})
|
|
791
809
|
}
|
|
792
810
|
}
|
|
793
|
-
hasChanges = true;
|
|
794
811
|
}
|
|
795
812
|
|
|
796
|
-
// first check value
|
|
797
|
-
if (value !== undefined) {
|
|
798
|
-
const type = stobj ? stobj.common.type : new_common.type;
|
|
799
|
-
if (type === 'number') {
|
|
800
|
-
const minval = (stobj ? stobj.common.min : new_common.min);
|
|
801
|
-
const maxval = (stobj ? stobj.common.max : new_common.max);
|
|
802
|
-
let nval = (typeof value == 'number' ? value : parseFloat(value));
|
|
803
|
-
if (isNaN(nval)) {
|
|
804
|
-
if (minval !== undefined && typeof minval === 'number')
|
|
805
|
-
nval = minval;
|
|
806
|
-
else
|
|
807
|
-
nval = 0;
|
|
808
|
-
}
|
|
809
|
-
if (typeof minval == 'number' && nval < minval) {
|
|
810
|
-
hasChanges = true;
|
|
811
|
-
new_common.color = '#FF0000'
|
|
812
|
-
value = minval
|
|
813
|
-
this.stashErrors(`${stateId}.min`,`State value for ${stateId} has value "${nval}" less than min "${minval}".`, false );
|
|
814
|
-
}
|
|
815
|
-
if (typeof maxval == 'number' && nval > maxval) {
|
|
816
|
-
hasChanges = true;
|
|
817
|
-
new_common.color = '#FF0000'
|
|
818
|
-
value = maxval;
|
|
819
|
-
this.stashErrors(`${stateId}.max`,`State value for ${stateId} has value "${nval}" greater than max "${maxval}".`, false );
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
813
|
//
|
|
824
814
|
// only change object when any common property has changed
|
|
825
|
-
if (
|
|
815
|
+
if (Object.keys(new_common).length) {
|
|
826
816
|
this.adapter.extendObject(stateId, {type: 'state', common: new_common, native: {}}, () =>
|
|
827
817
|
value !== undefined && this.setState_typed(stateId, value, true, stobj ? stobj.common.type : new_common.type));
|
|
828
|
-
} else if (value !== undefined) {
|
|
818
|
+
} else if (value !== undefined && stobj) {
|
|
829
819
|
this.setState_typed(stateId, value, true, stobj.common.type);
|
|
830
820
|
}
|
|
831
821
|
}
|
|
@@ -1088,7 +1078,7 @@ class StatesController extends EventEmitter {
|
|
|
1088
1078
|
}
|
|
1089
1079
|
}
|
|
1090
1080
|
|
|
1091
|
-
async syncDevStates(dev, model
|
|
1081
|
+
async syncDevStates(dev, model) {
|
|
1092
1082
|
if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
|
|
1093
1083
|
const devId = utils.zbIdorIeeetoAdId(this.adapter, dev.ieeeAddr, false);
|
|
1094
1084
|
// devId - iobroker device id
|
|
@@ -1131,8 +1121,9 @@ class StatesController extends EventEmitter {
|
|
|
1131
1121
|
min: statedesc.min,
|
|
1132
1122
|
max: statedesc.max,
|
|
1133
1123
|
states: statedesc.states,
|
|
1124
|
+
color: null,
|
|
1134
1125
|
};
|
|
1135
|
-
this.updateState(devId, statedesc.id, undefined, common
|
|
1126
|
+
this.updateState(devId, statedesc.id, undefined, common);
|
|
1136
1127
|
}
|
|
1137
1128
|
this.deleteOrphanedDeviceStates(dev.ieeeAddr, model, false, undefined, true);
|
|
1138
1129
|
}
|
|
@@ -1185,6 +1176,7 @@ class StatesController extends EventEmitter {
|
|
|
1185
1176
|
role: statedesc.role,
|
|
1186
1177
|
min: statedesc.min,
|
|
1187
1178
|
max: statedesc.max,
|
|
1179
|
+
color: null,
|
|
1188
1180
|
};
|
|
1189
1181
|
|
|
1190
1182
|
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
@@ -1210,7 +1202,7 @@ class StatesController extends EventEmitter {
|
|
|
1210
1202
|
);
|
|
1211
1203
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
1212
1204
|
} else {
|
|
1213
|
-
this.updateState(devId, stateID, value, common,
|
|
1205
|
+
this.updateState(devId, stateID, value, common, false);
|
|
1214
1206
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
1215
1207
|
}
|
|
1216
1208
|
}
|
package/lib/zigbeecontroller.js
CHANGED
|
@@ -1230,6 +1230,20 @@ class ZigbeeController extends EventEmitter {
|
|
|
1230
1230
|
*/
|
|
1231
1231
|
}
|
|
1232
1232
|
|
|
1233
|
+
|
|
1234
|
+
async getConverterValue(converter, target, key, preparedValue, preparedObject, meta) {
|
|
1235
|
+
if (preparedObject) {
|
|
1236
|
+
// try {
|
|
1237
|
+
return { result:await converter.convertSet(target, key, preparedObject, meta), fromObject:true };
|
|
1238
|
+
// }
|
|
1239
|
+
// catch (error)
|
|
1240
|
+
// {
|
|
1241
|
+
// return { result:await converter.convertSet(target, key, preparedValue, meta), fromObject:false };
|
|
1242
|
+
// }
|
|
1243
|
+
}
|
|
1244
|
+
else return { result:await converter.convertSet(target, key, preparedValue, meta), fromObject:false };
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1233
1247
|
// publish via converter
|
|
1234
1248
|
//
|
|
1235
1249
|
async publishFromState(deviceId, model, stateModel, stateList, options, debugID, has_elevated_debug) {
|
|
@@ -1328,6 +1342,10 @@ class ZigbeeController extends EventEmitter {
|
|
|
1328
1342
|
}
|
|
1329
1343
|
}
|
|
1330
1344
|
if (converter === undefined) {
|
|
1345
|
+
if (stateDesc.isInternalState) {
|
|
1346
|
+
this.emit('acknowledge_state', deviceId, undefined, stateDesc , undefined );
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1331
1349
|
const message = `No converter available for '${model}' with key '${stateDesc.id}' `;
|
|
1332
1350
|
if (has_elevated_debug) {
|
|
1333
1351
|
this.emit('device_debug', { ID:debugID, data: { error: 'NOCONV',states:[{id:stateDesc.id, value:value, payload:'no converter'}] , IO:false }, message:message});
|
|
@@ -1353,7 +1371,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
1353
1371
|
|
|
1354
1372
|
const epName = stateDesc.epname !== undefined ? stateDesc.epname : (stateDesc.prop || stateDesc.id);
|
|
1355
1373
|
const key = stateDesc.setattr || stateDesc.prop || stateDesc.id;
|
|
1356
|
-
const message = `convert ${key} with value ${safeJsonStringify(preparedValue)} and options ${safeJsonStringify(preparedOptions)} for device ${deviceId} with Endpoint ${epName}`;
|
|
1374
|
+
const message = `convert ${key} with value ${safeJsonStringify(preparedValue)} and options ${safeJsonStringify(preparedOptions)} for device ${deviceId} ${stateDesc.epname ? `with Endpoint ${epName}` : 'without Endpoint'}`;
|
|
1357
1375
|
if (has_elevated_debug) {
|
|
1358
1376
|
this.emit('device_debug', { ID:debugID, data: { flag: '04', payload: {key:key, ep: stateDesc.epname, value:preparedValue, options:preparedOptions}, IO:false }, message:message});
|
|
1359
1377
|
}
|
|
@@ -1371,7 +1389,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
1371
1389
|
if (this.debugActive) this.debug(`target: ${safeJsonStringify(target)}`);
|
|
1372
1390
|
|
|
1373
1391
|
const meta = {
|
|
1374
|
-
endpoint_name:
|
|
1392
|
+
endpoint_name: stateDesc.epname,
|
|
1375
1393
|
options: preparedOptions,
|
|
1376
1394
|
device: entity.device,
|
|
1377
1395
|
mapped: model === 'group' ? [] : mappedModel,
|
|
@@ -1398,10 +1416,22 @@ class ZigbeeController extends EventEmitter {
|
|
|
1398
1416
|
}
|
|
1399
1417
|
}
|
|
1400
1418
|
let retry = 2;
|
|
1419
|
+
/*
|
|
1420
|
+
let preparedObject = null;
|
|
1421
|
+
try {
|
|
1422
|
+
preparedObject = JSON.parse(preparedValue);
|
|
1423
|
+
}
|
|
1424
|
+
catch (error)
|
|
1425
|
+
{ }
|
|
1426
|
+
*/
|
|
1427
|
+
|
|
1401
1428
|
do
|
|
1402
1429
|
{
|
|
1403
1430
|
try {
|
|
1404
1431
|
const result = await converter.convertSet(target, key, preparedValue, meta);
|
|
1432
|
+
const fromObject = false;
|
|
1433
|
+
const preparedObject = null;
|
|
1434
|
+
//const { result, fromObject } = await this.getConverterValue(converter, target, key, preparedValue, preparedObject, meta);
|
|
1405
1435
|
const message = `convert result ${safeJsonStringify(result)} for device ${deviceId}`;
|
|
1406
1436
|
if (isGroup)
|
|
1407
1437
|
this.emit('published', deviceId, model, stateModel, stateList, options, debugID, has_elevated_debug );
|
|
@@ -1411,7 +1441,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
1411
1441
|
else
|
|
1412
1442
|
if (this.debugActive) this.debug(message);
|
|
1413
1443
|
if (result !== undefined) {
|
|
1414
|
-
if (
|
|
1444
|
+
if (!stateDesc.noack) {
|
|
1415
1445
|
this.emit('acknowledge_state', deviceId, model, stateDesc, value );
|
|
1416
1446
|
}
|
|
1417
1447
|
// process sync state list
|
|
@@ -1419,7 +1449,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
1419
1449
|
}
|
|
1420
1450
|
else {
|
|
1421
1451
|
if (has_elevated_debug) {
|
|
1422
|
-
const
|
|
1452
|
+
const stringvalue = fromObject ? safeJsonStringify(preparedObject) : typeof preparedValue == 'object' ? safeJsonStringify(preparedValue) : preparedValue;
|
|
1453
|
+
const message = `Convert does not return a result result for ${key} with ${stringvalue} on device ${deviceId}.`;
|
|
1423
1454
|
this.emit('device_debug', { ID:debugID, data: { flag: '06' , IO:false }, message:message});
|
|
1424
1455
|
}
|
|
1425
1456
|
}
|
|
@@ -1574,14 +1605,16 @@ class ZigbeeController extends EventEmitter {
|
|
|
1574
1605
|
for (const converter of mappedModel.toZigbee) {
|
|
1575
1606
|
if (converter.hasOwnProperty('convertGet')) {
|
|
1576
1607
|
cCount++;
|
|
1577
|
-
const sources =
|
|
1578
|
-
if (converter.endpoints
|
|
1579
|
-
|
|
1608
|
+
const sources = new Set();
|
|
1609
|
+
if (!converter.endpoints) sources.add(entity.device.endpoints[0]);
|
|
1610
|
+
const epCandidates = converter.endpoints ? converter.endpoints : epmap ? Object.keys(epmap) : undefined;
|
|
1611
|
+
if (epCandidates) {
|
|
1612
|
+
for (const epname of epCandidates) {
|
|
1580
1613
|
const source = entity.device.endpoints.find((id) => id.ID == epmap[epname]);
|
|
1581
|
-
if (source) sources.
|
|
1614
|
+
if (source) sources.add(source);
|
|
1582
1615
|
}
|
|
1583
1616
|
}
|
|
1584
|
-
if (sources.
|
|
1617
|
+
if (sources.size == 0) sources.add(entity.device.endpoints[0]);
|
|
1585
1618
|
for (const source of sources) {
|
|
1586
1619
|
for (const k of converter.key)
|
|
1587
1620
|
try {
|