iobroker.zigbee 2.0.2 → 2.0.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.
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const EventEmitter = require('events').EventEmitter;
3
+ const safeJsonStringify = require('./json');
4
+ const { EventEmitter } = require('events');
4
5
  const statesMapping = require('./devices');
5
- const getAdId = require('./utils').getAdId;
6
- const getZbId = require('./utils').getZbId;
6
+ const { getAdId, getZbId } = require('./utils');
7
7
  const fs = require('fs');
8
8
  const axios = require('axios');
9
9
  const localConfig = require('./localConfig');
@@ -14,7 +14,8 @@ const localConfig = require('./localConfig');
14
14
  const { exec } = require('child_process');
15
15
  const { tmpdir } = require('os');
16
16
  const path = require('path');
17
- const { numberWithinRange } = require('zigbee-herdsman-converters/lib/utils');
17
+ const { throwDeprecation } = require('process');
18
+ const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
18
19
 
19
20
 
20
21
  class StatesController extends EventEmitter {
@@ -32,6 +33,7 @@ class StatesController extends EventEmitter {
32
33
  this.ImagesToDownload = [];
33
34
  this.stashedErrors = {};
34
35
  this.stashedUnknownModels = [];
36
+ this.debugMessages = { nodevice:{ in:[], out: []} };
35
37
  }
36
38
 
37
39
  info(message, data) {
@@ -85,6 +87,10 @@ class StatesController extends EventEmitter {
85
87
  return rv;
86
88
  }
87
89
 
90
+ debugMessagesById() {
91
+ return this.debugMessages;
92
+ }
93
+
88
94
  async AddModelFromHerdsman(device, model) {
89
95
  // this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
90
96
  if (this.localConfig.getOverrideWithKey(model, 'legacy', true)) {
@@ -152,6 +158,7 @@ class StatesController extends EventEmitter {
152
158
  if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
153
159
  return;
154
160
  }
161
+ const debugId = Date.now();
155
162
  if (state && !state.ack) {
156
163
  if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
157
164
  return;
@@ -166,16 +173,18 @@ class StatesController extends EventEmitter {
166
173
  return;
167
174
  }
168
175
 
169
- if (this.checkDebugDevice(id))
170
- this.warn(`ELEVATED O01: User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`);
171
-
172
- this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
173
176
  const devId = getAdId(this.adapter, id); // iobroker device id
174
177
  let deviceId = getZbId(id); // zigbee device id
178
+
179
+ if (this.checkDebugDevice(id)) {
180
+ const message = `User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`;
181
+ this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
182
+ } else
183
+ this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
175
184
  // const stateKey = id.split('.')[3];
176
185
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
177
186
  if (arr[1] === undefined) {
178
- this.warn(`unable to extract id from state ${id}`);
187
+ //this.warn(`unable to extract id from state ${id}`);
179
188
  return;
180
189
  }
181
190
  const stateKey = arr[1];
@@ -199,7 +208,7 @@ class StatesController extends EventEmitter {
199
208
 
200
209
  }
201
210
  this.collectOptions(id.split('.')[2], model, options =>
202
- this.publishFromState(deviceId, model, stateKey, state, options));
211
+ this.publishFromState(deviceId, model, stateKey, state, options, debugId));
203
212
  }
204
213
  });
205
214
  }
@@ -305,29 +314,38 @@ class StatesController extends EventEmitter {
305
314
  }, (stateDesc.compositeTimeout ? stateDesc.compositeTimeout : 100) * factor);
306
315
  }
307
316
 
308
- async publishFromState(deviceId, model, stateKey, state, options) {
317
+ async publishFromState(deviceId, model, stateKey, state, options, debugId) {
309
318
  this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
310
319
  const elevated = this.checkDebugDevice(deviceId);
311
320
 
312
- if (elevated) this.warn(`ELEVATED O02: Change state '${stateKey}' at device ${deviceId} type '${model}'`);
321
+ if (elevated) {
322
+ const message = (`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
323
+ this.emit('device_debug', { ID:debugId, data: { ID: deviceId, model: model, flag:'02', IO:false }, message:message});
324
+ }
313
325
 
314
326
  const devStates = await this.getDevStates(deviceId, model);
315
327
  if (!devStates) {
316
- if (elevated) this.error(`ELEVATED OE1: no device states for device ${deviceId} type '${model}'`);
328
+ if (elevated) {
329
+ const message = (`no device states for device ${deviceId} type '${model}'`);
330
+ this.emit('device_debug', { ID:debugId, data: { error: 'NOSTATES' , IO:false }, message:message});
331
+ }
317
332
  return;
318
333
  }
319
334
  const commonStates = statesMapping.commonStates.find(statedesc => stateKey === statedesc.id);
320
335
  const stateDesc = (commonStates === undefined ? devStates.states.find(statedesc => stateKey === statedesc.id) : commonStates);
321
336
  const stateModel = devStates.stateModel;
322
337
  if (!stateDesc) {
323
- this.error(`No state available for '${model}' with key '${stateKey}'`);
338
+ const message = (`No state available for '${model}' with key '${stateKey}'`);
339
+ if (elevated) this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:'unknown', payload:'unknown'}], error: 'NOSTKEY' , IO:false }, message:message});
324
340
  return;
325
341
  }
326
342
 
327
343
  const value = state.val;
328
344
  if (value === undefined || value === '') {
329
- if (elevated)
330
- this.error(`ELEVATED OE2: no value for device ${deviceId} type '${model}'`);
345
+ if (elevated) {
346
+ const message = (`no value for device ${deviceId} type '${model}'`);
347
+ this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:'--', payload:'error', ep:stateDesc.epname}],error: 'NOVAL' , IO:false }, message:message});
348
+ }
331
349
  return;
332
350
  }
333
351
  let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0, source:state.from}];
@@ -344,7 +362,8 @@ class StatesController extends EventEmitter {
344
362
  }
345
363
  } catch (e) {
346
364
  this.sendError(e);
347
- this.error('Exception caught in publishfromstate');
365
+ if (elevated) this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:state.val, payload:'unknown'}], error: 'EXLINK' , IO:false }});
366
+ this.error('Exception caught in publishfromstate: ' + (e && e.message ? e.message : 'no error message given'));
348
367
  }
349
368
 
350
369
  });
@@ -359,7 +378,7 @@ class StatesController extends EventEmitter {
359
378
  readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
360
379
  }
361
380
 
362
- this.emit('changed', deviceId, model, stateModel, stateList, options);
381
+ this.emit('changed', deviceId, model, stateModel, stateList, options, debugId);
363
382
  }
364
383
 
365
384
  async renameDevice(id, newName) {
@@ -413,7 +432,7 @@ class StatesController extends EventEmitter {
413
432
  let statename = state._id;
414
433
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
415
434
  if (arr[1] === undefined) {
416
- this.warn(`unable to extract id from state ${statename}`);
435
+ this.debug(`unable to extract id from state ${statename}`);
417
436
  const idx = statename.lastIndexOf('.');
418
437
  if (idx > -1) {
419
438
  statename = statename.slice(idx + 1);
@@ -755,7 +774,7 @@ class StatesController extends EventEmitter {
755
774
  this.adapter.fileExists(namespace, target, async (err,result) => {
756
775
  if (result) return;
757
776
  const src = `${tmpdir()}/${path.basename(target)}`;
758
- const msg = `downloading ${url} to ${src}`;
777
+ //const msg = `downloading ${url} to ${src}`;
759
778
  if (this.ImagesToDownload.indexOf(url) ==-1) {
760
779
  await this.downloadIcon(url, src)
761
780
  try {
@@ -773,7 +792,6 @@ class StatesController extends EventEmitter {
773
792
  this.info(`copied ${src} to ${target}.`)
774
793
  fs.rm(src, (err) => {
775
794
  if (err) this.warn(`error removing ${src} : ${JSON.stringify(err)}`);
776
- else this.info(`removed ${src}`)
777
795
  });
778
796
  })
779
797
  }
@@ -861,102 +879,257 @@ class StatesController extends EventEmitter {
861
879
  this.emit('debugmessage', {id: id, message:message});
862
880
  }
863
881
 
864
- async publishToState(devId, model, payload) {
865
- const devStates = await this.getDevStates(`0x${devId}`, model);
882
+ async publishToState(devId, model, payload, debugId) {
883
+ try {
884
+ if (!debugId) debugId = Date.now();
885
+ const devStates = await this.getDevStates(`0x${devId}`, model);
886
+
887
+ const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
888
+
889
+ const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
890
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'01', IO:true }, message:message});
891
+ else this.debug(message);
892
+ if (!devStates) {
893
+ const message = `no device states for device ${devId} type '${model}'`;
894
+ if (has_elevated_debug)this.emit('device_debug', { ID:debugId, data: { error:'NOSTATE',states:[{ id:'--', value:'--', payload:payload}], IO:true }, message:message});
895
+ else this.debug(message);
896
+ return;
897
+ }
898
+ // find states for payload
899
+ let has_published = false;
900
+ if (devStates.states !== undefined) {
901
+ try {
902
+ const states = statesMapping.commonStates.concat(
903
+ devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id))
904
+ );
905
+
906
+ for (const stateInd in states) {
907
+ const statedesc = states[stateInd];
908
+ let value;
909
+ if (statedesc.getter) {
910
+ value = statedesc.getter(payload);
911
+ } else {
912
+ value = payload[statedesc.prop || statedesc.id];
913
+ }
914
+ // checking value
915
+ if (value === undefined || value === null) {
916
+ continue;
917
+ }
866
918
 
867
- const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
919
+ let stateID = statedesc.id;
920
+
921
+ const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
922
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'02', IO:true }, message});
923
+ else this.debug(message);
924
+
925
+ const common = {
926
+ name: statedesc.name,
927
+ type: statedesc.type,
928
+ unit: statedesc.unit,
929
+ read: statedesc.read,
930
+ write: statedesc.write,
931
+ icon: statedesc.icon,
932
+ role: statedesc.role,
933
+ min: statedesc.min,
934
+ max: statedesc.max,
935
+ };
936
+
937
+ if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
938
+ stateID = `${stateID}.${value.stateid}`;
939
+ if (value.hasOwnProperty('unit')) {
940
+ common.unit = value.unit;
941
+ }
942
+ common.name = value.name ? value.name : value.stateid;
943
+ common.role = value.role ? `value.${value.role}` : 'number';
944
+ value = value.value;
945
+ }
868
946
 
947
+ // if needs to return value to back after timeout
948
+ if (statedesc.isEvent) {
949
+ this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, (typeof value == typeof (!value) ? !value : ''));
950
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
869
951
 
870
- if (has_elevated_debug)
871
- {
872
- this.elevatedMessage(devId, `ELEVATED I01: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`, false);
952
+ } else {
953
+ if (statedesc.prepublish) {
954
+ this.collectOptions(devId, model, options =>
955
+ statedesc.prepublish(devId, value, newvalue => {
956
+ this.updateState(devId, stateID, newvalue, common) }, options)
957
+ );
958
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
959
+ } else {
960
+ this.updateState(devId, stateID, value, common, debugId);
961
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
962
+ }
963
+ }
964
+ has_published = true;
965
+ }
966
+ } catch (e) {
967
+ const message = `unable to enumerate states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`;
968
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'ESTATE', IO:true }, message:message});
969
+ else this.debug(message);
970
+ }
971
+ const message = `No value published for device ${devId}`;
972
+ if (!has_published && has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
973
+ else this.debug(message);
974
+ }
975
+ else {
976
+ const message = `ELEVATED IE05 - NOSTATE: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`;
977
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOSTATE', IO:true }, message});
978
+ else this.debug(message);
979
+ }
873
980
  }
874
- if (!devStates) {
875
- if (has_elevated_debug)
876
- this.elevatedMessage(devId, `ELEVATED IE02: no device states for device ${devId} type '${model}'`, true)
877
- return;
981
+ catch (error) {
982
+ this.error('Something went horribly wrong: ' + (error && error.message ? error.message : 'no reason given'));
878
983
  }
879
- // find states for payload
880
- let has_published = false;
984
+ }
881
985
 
882
- if (devStates.states !== undefined) {
883
- try {
884
- const states = statesMapping.commonStates.concat(
885
- devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id))
886
- );
887
-
888
- for (const stateInd in states) {
889
- const statedesc = states[stateInd];
890
- let value;
891
- if (statedesc.getter) {
892
- value = statedesc.getter(payload);
893
- } else {
894
- value = payload[statedesc.prop || statedesc.id];
895
- }
896
- // checking value
897
- if (value === undefined || value === null) {
898
- continue;
899
- }
986
+ async processConverters(converters, devId, model, mappedModel, message, meta, debugId) {
987
+ for (const converter of converters) {
988
+ const publish = (payload, dID) => {
989
+ if (typeof payload === 'object') {
990
+ this.publishToState(devId, model, payload,dID);
991
+ }
992
+ };
900
993
 
901
- let stateID = statedesc.id;
994
+ const options = await new Promise((resolve, reject) => {
995
+ this.collectOptions(devId, model, (options) => {
996
+ resolve(options);
997
+ });
998
+ });
902
999
 
903
- if (has_elevated_debug) {
904
- this.elevatedMessage(devId, `ELEVATED I02: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`, false);
905
- }
1000
+ const payload = await new Promise((resolve, reject) => {
1001
+ const payloadConv = converter.convert(mappedModel, message, publish, options, meta);
1002
+ if (typeof payloadConv === 'object') {
1003
+ resolve(payloadConv);
1004
+ }
1005
+ });
906
1006
 
907
- const common = {
908
- name: statedesc.name,
909
- type: statedesc.type,
910
- unit: statedesc.unit,
911
- read: statedesc.read,
912
- write: statedesc.write,
913
- icon: statedesc.icon,
914
- role: statedesc.role,
915
- min: statedesc.min,
916
- max: statedesc.max,
917
- };
918
-
919
- if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
920
- stateID = `${stateID}.${value.stateid}`;
921
- if (value.hasOwnProperty('unit')) {
922
- common.unit = value.unit;
923
- }
924
- common.name = value.name ? value.name : value.stateid;
925
- common.role = value.role ? `value.${value.role}` : 'number';
926
- value = value.value;
927
- }
1007
+ publish(payload, debugId);
1008
+ }
1009
+ }
928
1010
 
929
- // if needs to return value to back after timeout
930
- if (statedesc.isEvent) {
931
- this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, (typeof value == typeof (!value) ? !value : ''));
932
- } else {
933
- if (statedesc.prepublish) {
934
- this.collectOptions(devId, model, options =>
935
- statedesc.prepublish(devId, value, newvalue =>
936
- this.updateState(devId, stateID, newvalue, common), options)
937
- );
938
- } else {
939
- this.updateState(devId, stateID, value, common);
1011
+
1012
+ async onZigbeeEvent(type, entity, message) {
1013
+ this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
1014
+
1015
+ const device = entity.device;
1016
+ const mappedModel = entity.mapped;
1017
+ const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
1018
+ const cluster = message.cluster;
1019
+ const devId = device.ieeeAddr.substr(2);
1020
+ const meta = {device};
1021
+
1022
+ const has_elevated_debug = this.checkDebugDevice(devId);
1023
+ const debugId = Date.now();
1024
+
1025
+ // raw message data for logging and msg_from_zigbee
1026
+ const msgForState = Object.assign({}, message);
1027
+ delete msgForState['device'];
1028
+ delete msgForState['endpoint'];
1029
+ msgForState['endpoint_id'] = message.endpoint.ID;
1030
+
1031
+ if (has_elevated_debug) {
1032
+ const message = `Zigbee Event of Type ${type} from device ${device.ieeeAddr}, incoming event: ${safeJsonStringify(msgForState)}`;
1033
+ this.emit('device_debug', { ID:debugId, data: { ID: device.ieeeAddr, payload:safeJsonStringify(msgForState), flag:'01', IO:true }, message:message});
1034
+
1035
+ }
1036
+ // this assigment give possibility to use iobroker logger in code of the converters, via meta.logger
1037
+ meta.logger = this;
1038
+
1039
+ await this.adapter.checkIfModelUpdate(entity);
1040
+
1041
+ let _voltage = 0;
1042
+ let _temperature = 0;
1043
+ let _humidity = 0;
1044
+
1045
+ let isMessure = false;
1046
+ let isBattKey = false;
1047
+
1048
+ if (mappedModel && mappedModel.meta && mappedModel.meta.battery) {
1049
+ const isVoltage = mappedModel.meta.battery.hasOwnProperty('voltageToPercentage');
1050
+
1051
+ if (isVoltage) {
1052
+ const keys = Object.keys(message.data);
1053
+
1054
+ for (const key of keys) {
1055
+ const value = message.data[key];
1056
+
1057
+ if (value && value[1]) {
1058
+ if (key == 65282 && value[1][1]) {
1059
+ _voltage = value[1][1].elmVal;
1060
+ isBattKey = true;
1061
+ break;
1062
+ }
1063
+ if (key == 65281) {
1064
+ _voltage = value[1];
1065
+ isBattKey = true;
1066
+ _temperature = value[100];
1067
+ _temperature = _temperature /100;
1068
+ _humidity = value[101];
1069
+ _humidity = _humidity / 100;
1070
+ isMessure = true;
1071
+ break;
940
1072
  }
941
1073
  }
942
- has_published = true;
943
1074
  }
944
- } catch (e) {
945
- this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`);
946
- if (has_elevated_debug)
947
- this.elevatedMessage(devId, `ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`, true);
948
1075
  }
949
- if (!has_published && has_elevated_debug) {
950
- this.elevatedMessage(devId, `ELEVATED IE04: No value published for device ${devId}`, true);
1076
+ }
951
1077
 
1078
+ // always publish link_quality and battery
1079
+ if (message.linkquality) { // send battery with
1080
+ this.publishToState(devId, model, {linkquality: message.linkquality}, debugId);
1081
+ if (isBattKey) {
1082
+ this.publishToState(devId, model, {voltage: _voltage}, debugId);
1083
+ const battProz = zigbeeHerdsmanConvertersUtils.batteryVoltageToPercentage(_voltage,entity.mapped.meta.battery.voltageToPercentage);
1084
+ this.publishToState(devId, model, {battery: battProz}, debugId);
1085
+ }
1086
+ if (isMessure) {
1087
+ this.publishToState(devId, model, {temperature: _temperature}, debugId);
1088
+ this.publishToState(devId, model, {humidity: _humidity}), debugId;
952
1089
  }
953
1090
  }
954
- else {
955
- if (has_elevated_debug)
956
- this.elevatedMessage(devId, `ELEVATED IE05: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`, true);
1091
+
1092
+ // publish raw event to "from_zigbee"
1093
+ // some cleanup
1094
+
1095
+ this.publishToState(devId, model, {msg_from_zigbee: safeJsonStringify(msgForState)}, -1);
1096
+
1097
+ if (!entity.mapped) {
1098
+ return;
1099
+ }
1100
+
1101
+ let converters = mappedModel.fromZigbee.filter(c => c && c.cluster === cluster && (
1102
+ Array.isArray(c.type) ? c.type.includes(type) : c.type === type));
1103
+
1104
+
1105
+ if (!converters.length && type === 'readResponse') {
1106
+ converters = mappedModel.fromZigbee.filter(c => c.cluster === cluster && (
1107
+ Array.isArray(c.type) ? c.type.includes('attributeReport') : c.type === 'attributeReport'));
957
1108
  }
1109
+
1110
+ if (!converters.length) {
1111
+ if (type !== 'readResponse') {
1112
+ const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
1113
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'NOCONV', IO:true }, message:message});
1114
+ else this.debug(message);
1115
+ }
1116
+ return;
1117
+ }
1118
+
1119
+ meta.state = { state: '' }; // for tuya
1120
+
1121
+ this.processConverters(converters, devId, model, mappedModel, message, meta, debugId)
1122
+ .catch((error) => {
1123
+ // 'Error: Expected one of: 0, 1, got: 'undefined''
1124
+ if (cluster !== '64529') {
1125
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'EPROC', IO:true }});
1126
+ this.error(`Error while processing converters DEVICE_ID: '${devId}' cluster '${cluster}' type '${type}'`);
1127
+ }
1128
+ });
958
1129
  }
959
1130
 
1131
+
1132
+
960
1133
  }
961
1134
 
962
1135
  module.exports = StatesController;
@@ -178,7 +178,7 @@ class DeviceAvailability extends BaseExtension {
178
178
  this.publishAvailability(device, false);
179
179
  if (pingCount.failed++ <= this.max_ping) {
180
180
  if (pingCount.failed < 2 && pingCount.reported < this.max_ping) {
181
- this.warn(`Failed to ping ${ieeeAddr} ${device.modelID}`);
181
+ this.info(`Failed to ping ${ieeeAddr} ${device.modelID}`);
182
182
  pingCount.reported++;
183
183
  } else {
184
184
  this.debug(`Failed to ping ${ieeeAddr} ${device.modelID} on ${pingCount} consecutive attempts`);
@@ -186,7 +186,7 @@ class DeviceAvailability extends BaseExtension {
186
186
  this.setTimerPingable(device, pingCount.failed);
187
187
  this.ping_counters[device.ieeeAddr] = pingCount;
188
188
  } else {
189
- this.warn(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
189
+ this.info(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
190
190
  }
191
191
  }
192
192
  }
@@ -14,6 +14,8 @@ class DeviceConfigure extends BaseExtension {
14
14
  this.configuring = new Set();
15
15
  this.attempts = {};
16
16
  this.name = 'DeviceConfigure';
17
+
18
+ this.configureKeys = {};
17
19
  }
18
20
 
19
21
  setOptions(options) {
@@ -27,23 +29,24 @@ class DeviceConfigure extends BaseExtension {
27
29
  if (!mappedDevice || !mappedDevice.configure) {
28
30
  return false;
29
31
  }
30
- if (device.meta.hasOwnProperty('configured') &&
31
- zigbeeHerdsmanConverters.getConfigureKey(mappedDevice)) {
32
- return false;
33
- }
34
-
35
- return (device.interviewing !== true && this.checkDelayedConfigure(device.ieeeAddr)>0);
32
+ const delayedAttempts = this.checkDelayedConfigure(device, 0);
33
+ if (!this.configureKeys.hasOwnProperty(device.ieeeAddr)) this.configureKeys[device.ieeeAddr] = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
34
+ if (delayedAttempts > 0 && !device.interviewing) return true;
35
+ if (device.meta.hasOwnProperty('configured') && device.meta.configured !== this.configureKeys[device.ieeeAddr]) return true;
36
+ return (device.interviewing !== true && this.checkDelayedConfigure(device)>0);
36
37
  }
37
38
 
38
39
  checkDelayedConfigure(device, num) {
39
40
  if (!this.delayedConfigure.hasOwnProperty(device.ieeeAddr)) {
40
41
  if (num && num > 0) {
42
+ // this.warn('adding dev ' + device.ieeeAddr + ' to delayedConfigure with ' + num + ' attempts');
41
43
  this.delayedConfigure[device.ieeeAddr] = { maxAttempts:num };
42
44
  return num;
43
45
  }
44
46
  return 0;
45
47
  }
46
48
  const dc = this.delayedConfigure[device.ieeeAddr];
49
+ // this.warn('checkDelayedConfigure for ' + device.ieeeAddr + ' with ' + JSON.stringify(dc));
47
50
  dc.maxAttempts--;
48
51
  if (dc.maxAttempts > 0) return dc.maxAttempts;
49
52
  if (num && num > 0) {
@@ -90,6 +93,7 @@ class DeviceConfigure extends BaseExtension {
90
93
  try {
91
94
  const device = data.device;
92
95
  if (this.shouldConfigure(device, mappedDevice)) {
96
+ this.debug('ShouldConfigure ' + device.ieeeAddr);
93
97
  this.configure(device, mappedDevice);
94
98
  }
95
99
  } catch (error) {
@@ -98,6 +102,7 @@ class DeviceConfigure extends BaseExtension {
98
102
  }
99
103
  }
100
104
 
105
+
101
106
  onDeviceRemove(device) {
102
107
  try {
103
108
  if (this.configuring.has(device.ieeeAddr)) {
@@ -141,14 +146,16 @@ class DeviceConfigure extends BaseExtension {
141
146
  const coordinatorEndpoint = await this.zigbee.getDevicesByType('Coordinator')[0].endpoints[0];
142
147
  try {
143
148
  if (mappedDevice) {
144
- this.info(`-> Configuring ${device.ieeeAddr} ${device.modelID}`);
145
- const promises = [];
146
- promises.push(mappedDevice.configure);
147
- await Promise.all(promises.map(callback => callback(device, coordinatorEndpoint, mappedDevice)))
148
-
149
- //await mappedDevice.configure(device, coordinatorEndpoint, this);
150
-
149
+ if (mappedDevice.configure === undefined) return `No configure available for ${device.ieeeAddr} ${device.modelID}.`;
150
+ this.info(`Configuring ${device.ieeeAddr} ${device.modelID}`);
151
+ if (typeof mappedDevice.configure === 'function') await mappedDevice.configure(device, coordinatorEndpoint, this);
152
+ else {
153
+ const promises = [];
154
+ promises.push(...mappedDevice.configure);
155
+ await Promise.all(promises.map(callback => callback(device, coordinatorEndpoint, mappedDevice)))
156
+ }
151
157
  device.meta.configured = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
158
+ this.configureKeys[device.ieeeAddr] = device.meta.configured;
152
159
  device.save();
153
160
  this.info(`DeviceConfigure successful ${device.ieeeAddr} ${device.modelID}`);
154
161
  this.delayedConfigureAttempt(device, true);
@@ -162,8 +169,8 @@ class DeviceConfigure extends BaseExtension {
162
169
  if (error && error.message && error.message.match(/(\d+)ms/gm)) {
163
170
  // timeout message - we do want to start the configure chain
164
171
  const num = this.delayedConfigureAttempt(device, false);
165
- this.info(`Delayed configure for ${device.ieeeAddr} ${device.modelID}: ${num} attempts remaining`)
166
- return `Delayed configure for ${device.ieeeAddr} ${device.modelID}: ${num} attempts remaining`;
172
+ this.warn(`Timeout trying to configure ${device.ieeeAddr} ${device.modelID}: ${num} attempts remaining`)
173
+ return `Configuration timed out ${device.ieeeAddr} ${device.modelID}. The device did not repond in time to the configuration request. Another attempt will be made when the device is awake.`;
167
174
  } else {
168
175
  this.sendError(error);
169
176
  this.warn(`${device.ieeeAddr} ${device.modelID} Failed to configure. --> ${error && error.message ? error.message : ' no error message given'} `);