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/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
- if (payload.color && payload.color.hasOwnProperty('x') && payload.color.hasOwnProperty('y')) {
263
- const colorval = cie_to_rgb(payload.color.x, payload.color.y);
264
- return '#' + decimalToHex(colorval[0],2) + decimalToHex(colorval[1],2) + decimalToHex(colorval[2],2);
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, ...lightStatesWithColor, ...onOffStates];
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
  }
@@ -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
- // source is a web address
103
- if (srcIcon.startsWith('http')) {
104
- try {
105
- //if (modelDesc) modelDesc.icon = pathToAdminIcon;
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
- const buffer = Buffer.from(srcIcon.replace(base64Match[0],''), 'base64');
125
- this.adapter.writeFile(namespace, pathToAdminIcon, buffer, (err) => {
126
- if (err) {
127
- this.warn(`${msg} -- failed: ${err?.message || 'no reason given'}`);
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
- // try 3 options for source file
147
- let src = srcIcon; // as given
148
- const locations=[];
149
- if (!fs.existsSync(src)) {
150
- src = path.normalize(this.adapter.expandFileName(src));
151
- locations.push(src);
152
- } // assumed relative to data folder
153
- if (!fs.existsSync(src)) {
154
- locations.push(src);
155
- src = path.normalize(this.adapter.expandFileName(path.basename(src)));
156
- } // filename relative to data folder
157
- if (!fs.existsSync(src)) {
158
- locations.push(src);
159
- src = path.normalize(this.adapter.expandFileName(path.join('img',path.basename(src))));
160
- } // img/<filename> relative to data folder
161
- if (!fs.existsSync(src)) {
162
- this.warn(`Unable to copy icon from device definition, looked at ${locations.join(', ')} and ${src}`);
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
- fs.readFile(src, (err, data) => {
166
- if (err) {
167
- this.warn(`unable to read ${src}: ${(err.message? err.message:' no message given')}`);
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
- if (data) {
171
- this.adapter.writeFile(namespace, pathToAdminIcon, data, (err) => {
172
- if (err) {
173
- this.error(`error writing file ${path}: ${err.message ? err.message : 'no reason given'}`);
174
- return;
175
- }
176
- this.debug('Updated image file ' + pathToAdminIcon);
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
- this.error(`fs.readFile failed - neither error nor data is returned!`);
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 = (_deviceId.replace('0x', ''));
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
- const handle = this.compositeData[key]
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.id) === undefined
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, force) {
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
- // check if state exist
762
+
752
763
  const stobj = await this.adapter.getObjectAsync(stateId);
753
- let hasChanges = Boolean(force); // if force is set we always update the state.
754
- if (stobj) {
755
- // update state - not change name and role (user can it changed)
756
- if (!force) {
757
- if (stobj.common.name) {
758
- delete new_common.name;
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
- // always force allow change of level.color roles.
762
- if ((name.includes('color') || force) && (stobj.common.role != new_common.role)) {
763
- console.info(`${force ? 'forc' : 'allow'}ing role change for ${name}`)
764
- } else delete new_common.role;
765
-
766
- // check whether any common property is different
767
- if (!force) {
768
- if (stobj.common) {
769
- for (const property in new_common) {
770
- if (stobj.common.hasOwnProperty(property)) {
771
- if (stobj.common[property] === new_common[property]) {
772
- delete new_common[property];
773
- } else {
774
- hasChanges = true;
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 (hasChanges) {
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, force) {
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, force);
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, debugId);
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
  }
@@ -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: epName,
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 (stateModel && !isGroup && !stateDesc.noack) {
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 message = `Convert does not return a result result for ${key} with ${safeJsonStringify(preparedValue)} on device ${deviceId}.`;
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 && epmap) {
1579
- for (const epname of converter.endpoints) {
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.push(source);
1614
+ if (source) sources.add(source);
1582
1615
  }
1583
1616
  }
1584
- if (sources.length == 0) sources.push(entity.device.endpoints[0]);
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 {