iobroker.zigbee 3.3.1-alpha.0 → 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 +49 -50
- package/admin/admin.js +39 -7
- package/admin/tab_m.html +19 -0
- package/io-package.json +26 -26
- package/lib/colors.js +182 -421
- package/lib/exposes.js +126 -202
- package/lib/groups.js +2 -5
- package/lib/legacy/devices.js +8 -5
- package/lib/models.js +101 -12
- package/lib/statescontroller.js +147 -134
- package/lib/zigbeecontroller.js +59 -10
- 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
|
}
|
|
@@ -580,8 +668,9 @@ async function clearModelDefinitions() {
|
|
|
580
668
|
|
|
581
669
|
function getStateDefinition(name, type, prop) {
|
|
582
670
|
if (typeof name != 'string') return getStateDefinition('ilstate', type, prop);
|
|
671
|
+
if (states.hasOwnProperty(name)) return states[name];
|
|
583
672
|
if (legacyStates.hasOwnProperty(name)) return legacyStates[name];
|
|
584
|
-
return
|
|
673
|
+
return {
|
|
585
674
|
id: name,
|
|
586
675
|
prop: prop || name,
|
|
587
676
|
name: name.replace('_',' '),
|
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
|
-
});
|
|
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'}`);
|
|
132
111
|
}
|
|
133
|
-
catch (err) {
|
|
134
|
-
this.warn(`${msg} -- failed: ${err?.message || 'no reason given'}`)
|
|
135
|
-
}
|
|
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,70 @@ class StatesController extends EventEmitter {
|
|
|
740
751
|
setTimeout(() => this.updateState(dev_id, name, outValue, common), timeout);
|
|
741
752
|
}
|
|
742
753
|
|
|
743
|
-
|
|
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
|
+
|
|
774
|
+
async updateState(devId, name, value, common) {
|
|
744
775
|
const new_common = {name: name, color:null};
|
|
745
776
|
const stateId = devId + '.' + name;
|
|
746
|
-
if (common) {
|
|
777
|
+
if (common && typeof common === 'object') {
|
|
747
778
|
for (const key in common) {
|
|
748
779
|
if (common[key] !== undefined) new_common[key] = common[key];
|
|
749
780
|
}
|
|
750
781
|
}
|
|
751
|
-
|
|
782
|
+
|
|
752
783
|
const stobj = await this.adapter.getObjectAsync(stateId);
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
784
|
+
const type = (stobj?.common?.type) ? stobj.common.type : new_common.type;
|
|
785
|
+
// first check value
|
|
786
|
+
if (value !== undefined && type === 'number') {
|
|
787
|
+
const minval = parseFloat(stobj?.common ? stobj.common.min : new_common.min);
|
|
788
|
+
const maxval = parseFloat(stobj?.common ? stobj.common.max : new_common.max);
|
|
789
|
+
let nval = (typeof value == 'number' ? value : parseFloat(value));
|
|
790
|
+
if (isNaN(nval)) {
|
|
791
|
+
if (minval !== undefined && typeof minval === 'number')
|
|
792
|
+
nval = minval;
|
|
793
|
+
else
|
|
794
|
+
nval = 0;
|
|
760
795
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
796
|
+
if (nval < minval) {
|
|
797
|
+
new_common.color = '#FF0000'
|
|
798
|
+
value = minval
|
|
799
|
+
this.stashErrors(`${stateId}.min`,`State value for ${stateId} has value "${nval}" less than min "${minval}".`, false );
|
|
800
|
+
} else if (nval > maxval) {
|
|
801
|
+
new_common.color = '#FF0000'
|
|
802
|
+
value = maxval;
|
|
803
|
+
this.stashErrors(`${stateId}.max`,`State value for ${stateId} has value "${nval}" greater than max "${maxval}".`, false );
|
|
804
|
+
}
|
|
805
|
+
else new_common.color = new_common.color ? new_common.color : null;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (stobj) {
|
|
809
|
+
// always update common if common is provided
|
|
810
|
+
if (stobj.common) {
|
|
811
|
+
for (const property in new_common) {
|
|
812
|
+
if (stobj.common[property] != undefined) {
|
|
813
|
+
if (this.deepCompare(stobj.common[property],new_common[property])) {
|
|
814
|
+
delete new_common[property];
|
|
776
815
|
}
|
|
777
816
|
}
|
|
778
817
|
}
|
|
779
|
-
else hasChanges = true; // we dont have common => we need common
|
|
780
818
|
}
|
|
781
819
|
} else {
|
|
782
820
|
const matches = stateId.match((/\./g));
|
|
@@ -790,42 +828,14 @@ class StatesController extends EventEmitter {
|
|
|
790
828
|
await this.adapter.extendObjectAsync(id, {type: 'channel', common: { name:channel}, native:{}})
|
|
791
829
|
}
|
|
792
830
|
}
|
|
793
|
-
hasChanges = true;
|
|
794
831
|
}
|
|
795
832
|
|
|
796
|
-
|
|
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
|
-
//
|
|
833
|
+
if (new_common.color=== null && (stobj?.common?.color === undefined || stobj?.common.color === null)) delete new_common.color;
|
|
824
834
|
// only change object when any common property has changed
|
|
825
|
-
if (
|
|
835
|
+
if (Object.keys(new_common).length) {
|
|
826
836
|
this.adapter.extendObject(stateId, {type: 'state', common: new_common, native: {}}, () =>
|
|
827
837
|
value !== undefined && this.setState_typed(stateId, value, true, stobj ? stobj.common.type : new_common.type));
|
|
828
|
-
} else if (value !== undefined) {
|
|
838
|
+
} else if (value !== undefined && stobj) {
|
|
829
839
|
this.setState_typed(stateId, value, true, stobj.common.type);
|
|
830
840
|
}
|
|
831
841
|
}
|
|
@@ -1088,7 +1098,7 @@ class StatesController extends EventEmitter {
|
|
|
1088
1098
|
}
|
|
1089
1099
|
}
|
|
1090
1100
|
|
|
1091
|
-
async syncDevStates(dev, model
|
|
1101
|
+
async syncDevStates(dev, model) {
|
|
1092
1102
|
if (this.debugActive) this.debug('synchronizing device states for ' + dev.ieeeAddr + ' (' + model + ')');
|
|
1093
1103
|
const devId = utils.zbIdorIeeetoAdId(this.adapter, dev.ieeeAddr, false);
|
|
1094
1104
|
// devId - iobroker device id
|
|
@@ -1131,8 +1141,9 @@ class StatesController extends EventEmitter {
|
|
|
1131
1141
|
min: statedesc.min,
|
|
1132
1142
|
max: statedesc.max,
|
|
1133
1143
|
states: statedesc.states,
|
|
1144
|
+
color: null,
|
|
1134
1145
|
};
|
|
1135
|
-
this.updateState(devId, statedesc.id, undefined, common
|
|
1146
|
+
this.updateState(devId, statedesc.id, undefined, common);
|
|
1136
1147
|
}
|
|
1137
1148
|
this.deleteOrphanedDeviceStates(dev.ieeeAddr, model, false, undefined, true);
|
|
1138
1149
|
}
|
|
@@ -1185,6 +1196,7 @@ class StatesController extends EventEmitter {
|
|
|
1185
1196
|
role: statedesc.role,
|
|
1186
1197
|
min: statedesc.min,
|
|
1187
1198
|
max: statedesc.max,
|
|
1199
|
+
color: null,
|
|
1188
1200
|
};
|
|
1189
1201
|
|
|
1190
1202
|
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
@@ -1210,7 +1222,7 @@ class StatesController extends EventEmitter {
|
|
|
1210
1222
|
);
|
|
1211
1223
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
1212
1224
|
} else {
|
|
1213
|
-
this.updateState(devId, stateID, value, common,
|
|
1225
|
+
this.updateState(devId, stateID, value, common, false);
|
|
1214
1226
|
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
1215
1227
|
}
|
|
1216
1228
|
}
|
|
@@ -1226,6 +1238,7 @@ class StatesController extends EventEmitter {
|
|
|
1226
1238
|
const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
|
|
1227
1239
|
|
|
1228
1240
|
const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
|
|
1241
|
+
|
|
1229
1242
|
if (has_elevated_debug)
|
|
1230
1243
|
this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'03', IO:true }, message:message});
|
|
1231
1244
|
else
|