iobroker.zigbee 3.2.5 → 3.3.0

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/utils.js CHANGED
@@ -126,6 +126,7 @@ function zbIdorIeeetoAdId(adapter, source, withNamespace) {
126
126
  function adIdtoZbIdorIeee(adapter, source) {
127
127
  const s = `${source}`.replace(`${adapter.namespace}.`, '');
128
128
  if (s.startsWith('group')) return Number(s.substring(6));
129
+ if (s.startsWith('0x')) return s;
129
130
  if (s.length === 16 && Number(`0x${s}`)) return `0x${s}`;
130
131
  return 'illegal'
131
132
  }
@@ -140,7 +141,7 @@ const ikeaTradfriManufacturerID = [4476];
140
141
 
141
142
  function sanitizeImageParameter(parameter) {
142
143
  const replaceByDash = [/\?/g, /&/g, /[^a-z\d\-_./:]/gi, /[/]/gi];
143
- let sanitized = parameter;
144
+ let sanitized = parameter || 'illegalParameter';
144
145
  replaceByDash.forEach(r => sanitized = sanitized.replace(r, '-'));
145
146
  return sanitized;
146
147
  }
@@ -154,7 +155,7 @@ function getDeviceIcon(definition) {
154
155
  }
155
156
 
156
157
  function getModelRegEx( model) {
157
- const stripModel = (model) ? model.replace(/\0.*$/g, '').trim() : '';
158
+ const stripModel = (model && typeof model == 'string') ? model.replace(/\0.*$/g, '').trim() : '';
158
159
  return stripModel;
159
160
  }
160
161
 
@@ -214,8 +215,6 @@ function removeFromArray(arr, toRemove) {
214
215
  return removed;
215
216
  }
216
217
 
217
-
218
-
219
218
  exports.secondsToMilliseconds = seconds => seconds * 1000;
220
219
  exports.bulbLevelToAdapterLevel = bulbLevelToAdapterLevel;
221
220
  exports.adapterLevelToBulbLevel = adapterLevelToBulbLevel;
@@ -28,6 +28,10 @@ class BaseExtension {
28
28
  this.zigbee.debug(`${this.name}:${message}`, data);
29
29
  }
30
30
 
31
+ pairingMessage(message) {
32
+ this.zigbee.emit('pairing', `${this.name}:${message}`)
33
+ }
34
+
31
35
  sendError(error, message) {
32
36
  this.zigbee.sendError(error, message);
33
37
  }
@@ -33,7 +33,7 @@ class DeviceAvailability extends BaseExtension {
33
33
  this.availability_timeout = 300; // wait 5 min for live check
34
34
  this.timers = {};
35
35
  this.ping_counters = {};
36
- this.max_ping = 3;
36
+ this.max_ping = 2;
37
37
  this.state = {};
38
38
  this.forcedNonPingable = {};
39
39
  this.number_of_registered_devices = 0;
@@ -51,9 +51,9 @@ class DeviceAvailability extends BaseExtension {
51
51
  this.elevate_debug = false;
52
52
  this.isStarted = false;
53
53
  this.active_ping = config.pingCluster != 'off';
54
- this.max_ping = 5;
55
54
  this.availability_timeout = Math.max(60, typeof config.pingTimeout == 'number' ? config.pingTimeout : 300);
56
55
  this.startReadDelay = config.readAllAtStart ? Math.max(500, Math.min(10000, config.startReadDelay * 1000)) : 0;
56
+ this.readAtAnnounce = config.readAtAnnounce;
57
57
  this.debugDevices = [];
58
58
  this.pingCluster = pingClusters[config.pingCluster] ? pingClusters[config.pingCluster] : {};
59
59
  this.availableTime = config.availableUpdateTime ? config.availableUpdateTime : Number.MAX_SAFE_INTEGER;
@@ -80,7 +80,7 @@ class DeviceAvailability extends BaseExtension {
80
80
  isPingable(device) {
81
81
 
82
82
  if (this.active_ping) {
83
- if (this.forced_ping && forcedPingable.find(d => d && d.hasOwnProperty('zigbeeModel') && d.zigbeeModel.includes(device.modelID))) {
83
+ if (forcedPingable.find(d => d && device?.zigbeeModel?.includes(device.modelID))) {
84
84
  return true;
85
85
  }
86
86
 
@@ -176,6 +176,13 @@ class DeviceAvailability extends BaseExtension {
176
176
  }
177
177
  }
178
178
 
179
+ async onDeviceRemove(device) {
180
+ if (this.timers[device.ieeeAddr]) {
181
+ clearTimeout(this.timers[device.ieeeAddr]);
182
+ }
183
+ delete this.timers[device.ieeeAddr];
184
+ }
185
+
179
186
  async handleIntervalPingable(device, entity) {
180
187
  if (!this.isStarted) return;
181
188
  if (!this.active_ping) {
@@ -239,7 +246,7 @@ class DeviceAvailability extends BaseExtension {
239
246
  this.publishAvailability(device, false, false, has_elevated_debug ? debugID : undefined);
240
247
  if (pingCount.failed++ <= this.max_ping) {
241
248
  const message = `Failed to ping ${ieeeAddr} ${device.modelID} for ${JSON.stringify(pingCount)} attempts`
242
- if (pingCount.failed < 2 && pingCount.reported < this.max_ping) {
249
+ if (pingCount.reported < this.max_ping) {
243
250
  if (!has_elevated_debug)
244
251
  this.info(message);
245
252
  pingCount.reported++;
@@ -348,9 +355,9 @@ class DeviceAvailability extends BaseExtension {
348
355
  }
349
356
  }
350
357
 
351
- async onZigbeeEvent(data) {
358
+ async onZigbeeEvent(data, mappedDevice) {
352
359
  const device = data.device;
353
- if (!device || this.forcedNonPingable[device.ieeeAddr]) {
360
+ if (!device) {
354
361
  return;
355
362
  }
356
363
 
@@ -360,26 +367,12 @@ class DeviceAvailability extends BaseExtension {
360
367
  // When a zigbee message from a device is received we know the device is still alive.
361
368
  // => reset the timer.
362
369
  this.setTimerPingable(device, 1);
363
- const pc = this.ping_counters[device.ieeeAddr];
364
- if (pc == undefined) {
365
- this.ping_counters[device.ieeeAddr] = {failed: 0, reported: 0};
366
- } else {
367
- this.ping_counters[device.ieeeAddr].failed++;
368
- }
370
+ this.ping_counters[device.ieeeAddr] = {failed: 0, reported: 0};
369
371
 
370
372
  const online = this.state.hasOwnProperty(device.ieeeAddr) && this.state[device.ieeeAddr];
371
373
  if (online && data.type === 'deviceAnnounce' && !utils.isIkeaTradfriDevice(device)) {
372
- const entity = await this.zigbee.resolveEntity(device);
373
- if (entity && entity.mapped) {
374
- /**
375
- * In case the device is powered off AND on within the availability timeout,
376
- * zigbee2qmtt does not detect the device as offline (device is still marked online).
377
- * When a device is turned on again the state could be out of sync.
378
- * https://github.com/Koenkk/zigbee2mqtt/issues/1383#issuecomment-489412168
379
- * endDeviceAnnce is typically send when a device comes online.
380
- *
381
- * This isn't needed for TRADFRI devices as they already send the state themself.
382
- */
374
+ // We only try to read the states if readAtAnnounce and resend_states is not active
375
+ if (mappedDevice && !this.readAtAnnounce && !device.options?.resend_states) {
383
376
  this.onReconnect(device);
384
377
  }
385
378
  }
@@ -37,17 +37,30 @@ class DeviceConfigure extends BaseExtension {
37
37
  this.doConfigure(configureItem.dev, configureItem.mapped);
38
38
  }
39
39
 
40
- PushDeviceToQueue(device, mappedDevice) {
40
+ PushDeviceToQueue(device, mappedDevice, priority) {
41
41
  const id = device.ieeeAddr;
42
- for (const candidate of this.deviceConfigureQueue) {
43
- if (candidate.id == id) {
44
- this.debug('no duplicate entry in queue');
42
+
43
+ const item = this.deviceConfigureQueue.find((candidate) => candidate.id === id);
44
+ if (item) {
45
+ this.debug('preventing dupicate entries in configure queue');
46
+ if (priority) {
47
+ const idx = this.deviceConfigureQueue.indexOf(item);
48
+ if (idx > -1) this.deviceConfigureQueue.splice(idx, 1);
49
+ }
50
+ else {
45
51
  return;
46
52
  }
47
53
  }
48
- this.deviceConfigureQueue.push({ id: device.ieeeAddr, dev:device, mapped:mappedDevice });
49
- if (this.configureIntervall) return;
50
- this.configureIntervall = setInterval(async () => await this.handleConfigureQueue(), 5000);
54
+ if (priority) {
55
+ // priority means its a new device, we want to configure it NOW
56
+ this.pairingMessage(`configuring ${id}`);
57
+ this.doConfigure(device, mappedDevice);
58
+ }
59
+ else {
60
+ this.deviceConfigureQueue.push({ id: device.ieeeAddr, dev:device, mapped:mappedDevice });
61
+ if (this.configureIntervall) return;
62
+ this.configureIntervall = setInterval(async () => await this.handleConfigureQueue(), 5000);
63
+ }
51
64
  }
52
65
 
53
66
  shouldConfigure(device, mappedDevice) {
@@ -105,7 +118,7 @@ class DeviceConfigure extends BaseExtension {
105
118
  }
106
119
  // check if the event is a 'deviceInterview successful' Event
107
120
  if (data.type == 'deviceJoined' || data.type == 'deviceInterview' && data.status == 'successful') {
108
- this.PushDeviceToQueue(device, mappedDevice);
121
+ this.PushDeviceToQueue(device, mappedDevice, true); // with priority => we want it configured immediately
109
122
  }
110
123
 
111
124
  } catch (error) {
@@ -78,7 +78,18 @@ class ZigbeeController extends EventEmitter {
78
78
  this.deviceQueryActive = [];
79
79
  this.storedOptions = undefined;
80
80
  this.isConfigured = false;
81
+ this.disabledDevices = new Set();
82
+ }
81
83
 
84
+ setDeviceEnable(id, enable) {
85
+ try {
86
+ const ieee = `0x${id.split('.').pop()}`;
87
+ if (enable) this.disabledDevices.add(ieee); else this.disabledDevices.delete(ieee);
88
+ return this.disabledDevices.has(ieee);
89
+ }
90
+ catch {
91
+ return false;
92
+ }
82
93
  }
83
94
 
84
95
 
@@ -99,7 +110,7 @@ class ZigbeeController extends EventEmitter {
99
110
  return _port;
100
111
  }
101
112
  catch (error) {
102
- console.warn(`unable to access ${port}`)
113
+ this.warn(`unable to access ${port}`)
103
114
  return '';
104
115
  }
105
116
  }
@@ -240,8 +251,8 @@ class ZigbeeController extends EventEmitter {
240
251
  const networkParameters = `Network parameters on Coordinator: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${this.reverseIEEE(extendedPanIDDebug)}`;
241
252
  this.emit('pairing',configParameters)
242
253
  this.emit('pairing',networkParameters);
243
- this.warn(configParameters)
244
- this.warn(networkParameters);
254
+ this.info(configParameters)
255
+ this.info(networkParameters);
245
256
  }
246
257
  catch (error) {
247
258
  this.emit('pairing',`Unable to obtain herdsman settings`);
@@ -867,9 +878,10 @@ class ZigbeeController extends EventEmitter {
867
878
  message = error.message;
868
879
 
869
880
  }
870
- this.warn(`Failed to remove ${deviceID ? 'device ' + deviceID : 'unspecified device'}: ${message}`);
871
- callback && callback(`Failed to remove ${deviceID ? 'device ' + deviceID : 'unspecified device'}: ${message}`);
881
+ const msg = `Failed to remove ${deviceID ? 'device ' + deviceID : 'unspecified device'}: ${message}`;
882
+ if (callback) callback(msg); else return { status:false, error:msg};
872
883
  }
884
+ if (!callback) return {status:true};
873
885
  }
874
886
 
875
887
  // Zigbee events
@@ -907,19 +919,19 @@ class ZigbeeController extends EventEmitter {
907
919
  this.emit('device_debug', {ID: Date.now(), data: {flag:'da', states:[{id: '--', value:'--', payload:message}] , IO:true} ,message:`Device '${friendlyName}' announced itself`});
908
920
  }
909
921
 
910
- if (entity.device && entity.device.modelID && entity.device.interviewState != 'SUCCESSFUL') {
911
- this.info(`ignoring device announcement for ${entity.device.modelID} due to interview state ${entity.device.interviewState}`);
912
- this.emit('pairing', `device interview state is ${entity.device.interviewState}`)
922
+ if (this.herdsman.getPermitJoin() && (entity?.device?.interviewState != 'SUCCESSFUL')) {
923
+ this.info(`ignoring device announcement for ${entity?.device?.modelID ? entity?.device?.modelID : 'unknown model'} due to interview state ${entity?.device?.interviewState} while the network is open.`);
924
+ this.emit('pairing', `ignoring device announcement while interview state is ${entity?.device?.interviewState}`);
913
925
  return;
914
926
  }
915
927
 
928
+
916
929
  if (this.warnOnDeviceAnnouncement) {
917
930
  this.warn(`Device '${friendlyName}' announced itself${this.readAtAnnounce ? ', trying to read its status' : ''}`);
918
931
  } else {
919
932
  this.info(`Device '${friendlyName}' announced itself${this.readAtAnnounce ? ', trying to read its status' : ''}`);
920
933
  }
921
934
 
922
- const networkOpen = this.herdsman.getPermitJoin();
923
935
  /*
924
936
  if (networkOpen && entity.device && entity.device.modelID && entity.device.interviewState != 'IN_PROGRESS')
925
937
  {
@@ -936,6 +948,7 @@ class ZigbeeController extends EventEmitter {
936
948
  }
937
949
  else
938
950
  if (this.readAtAnnounce) await this.doDeviceQuery(message.device || message.ieeeAddr, Date.now(), false);
951
+
939
952
  this.callExtensionMethod(
940
953
  'onZigbeeEvent',
941
954
  [{'device': message.device, 'type': 'deviceAnnounce', options: entity.options || {}}, entity ? entity.mapped : null]);
@@ -955,61 +968,62 @@ class ZigbeeController extends EventEmitter {
955
968
  }
956
969
 
957
970
  async handleDeviceInterview(message) {
958
- if (this.debugActive) this.debug('handleDeviceInterview', message);
971
+ if (this.debugActive) this.debug('handleDeviceInterview',JSON.stringify(message));
959
972
  // safeguard: We do not allow to start an interview if the network is not opened
960
973
  if (message.status === 'started' && !this.herdsman.getPermitJoin()) {
961
- this.warn(`Blocked interview for '${message.ieeeAddr}' because the network is closed`);
974
+ this.warn(`Ignored interview for '${message.ieeeAddr}' because the network is closed`);
962
975
  return;
963
976
  }
964
977
  try {
965
978
  const entity = await this.resolveEntity(message.device || message.ieeeAddr);
966
979
  const friendlyName = entity.name;
967
- if (message.status === 'successful') {
968
- this.info(`Successfully interviewed '${friendlyName}', device has successfully been paired`);
969
-
970
- if (entity.mapped) {
971
- const {vendor, description, model} = entity.mapped;
972
- this.info(
973
- `Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
974
- );
975
-
976
- const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
977
- this.emit('pairing', 'Interview successful', JSON.stringify(log));
978
- //entity.device.modelID = entity.device._modelID;
979
- this.callExtensionMethod(
980
- 'onZigbeeEvent',
981
- [{...message,type:'deviceInterview', options: entity.options || {}}, entity.mapped],
982
- );
983
- this.emit('new', entity);
984
- // send to extensions again (for configure)
985
- } else {
986
- if (this.debugActive) this.debug(
987
- `Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
988
- `please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
989
- );
990
- const frName = {friendly_name: friendlyName, supported: false};
991
- this.emit('pairing', 'Interview successful', JSON.stringify(frName));
992
- //entity.device.modelID = entity.device._modelID;
980
+ const onZigbeeEventparameters = [message, entity.mapped ? entity.mapped: null];
981
+
982
+ switch (message.status) {
983
+ case 'successful': {
984
+ this.info(`Successfully interviewed '${friendlyName}', device has successfully been paired`);
985
+ const msgArr = [];
986
+
987
+ if (entity.mapped) {
988
+ const {vendor, description, model} = entity.mapped;
989
+ this.info(
990
+ `Device '${friendlyName}' is supported, identified as: ${vendor} ${description} (${model})`
991
+ );
992
+ msgArr.push(`firendly name: ${friendlyName}`);
993
+ if (model) msgArr.push(`model: ${model}`); else msgArr.push(`model: unknown`);
994
+
995
+ if (vendor) msgArr.push(`vendor: ${vendor}`);
996
+ if (description) msgArr.push(`description: ${description}`);
997
+ msgArr.push('supported:true');
998
+ onZigbeeEventparameters[0]={...message,type:'deviceInterview', options: entity.options || {}};
999
+ } else {
1000
+ if (this.debugActive) this.debug(
1001
+ `Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
1002
+ `please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`
1003
+ );
1004
+ }
1005
+ this.emit('pairing', 'Interview successful', msgArr.join(', '));
993
1006
  this.emit('new', entity);
1007
+ break;
994
1008
  }
995
- } else if (message.status === 'failed') {
996
- this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! `);
997
- //this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! ${message.error}`);
998
- this.emit('pairing', 'Interview failed', friendlyName);
999
- this.callExtensionMethod(
1000
- 'onZigbeeEvent',
1001
- [message, entity ? entity.mapped : null],
1002
- );
1003
- } else {
1004
- if (message.status === 'started') {
1005
- this.callExtensionMethod(
1006
- 'onZigbeeEvent',
1007
- [message, entity ? entity.mapped : null],
1008
- );
1009
- this.info(`Starting interview of '${friendlyName}'`);
1010
- this.emit('pairing', 'Interview started', friendlyName);
1009
+ case 'failed': {
1010
+ this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! `);
1011
+ this.emit('pairing', 'Interview failed', friendlyName);
1012
+ break;
1013
+ }
1014
+ default: {
1015
+ if (message.status === 'started') {
1016
+ this.info(`Starting interview of '${friendlyName}'`);
1017
+ this.emit('pairing', 'Interview started', friendlyName);
1018
+ }
1019
+
1011
1020
  }
1012
1021
  }
1022
+
1023
+ this.callExtensionMethod(
1024
+ 'onZigbeeEvent',
1025
+ onZigbeeEventparameters,
1026
+ );
1013
1027
  }
1014
1028
  catch (error) {
1015
1029
  this.error('handleDeviceInterview: ' + (error && error.message ? error.message : 'no error message'));
@@ -1040,25 +1054,67 @@ class ZigbeeController extends EventEmitter {
1040
1054
 
1041
1055
  async getMap(callback) {
1042
1056
  try {
1057
+ const MapData = { lqi:{}, routing:{}, sdev:[], edev:[], ddev: []};
1043
1058
  this.info('Collecting Map Data');
1044
1059
  const devices = this.herdsman.getDevices(true);
1045
1060
  const lqis = [];
1046
1061
  const routing = [];
1047
1062
  const errors = [];
1048
1063
 
1049
- await Promise.all(devices.filter((d) => d.type !== 'EndDevice').map(async device =>
1064
+ const mappables = devices.filter((d) => d.type !== 'EndDevice');
1065
+ let cnt = mappables.length;
1066
+ this.emit('pairing', `Map Devices left:${cnt}`)
1067
+ for (const device of mappables)
1050
1068
  {
1069
+ cnt--;
1070
+ if (this.disabledDevices.has(device.ieeeAddr)) {
1071
+ MapData.ddev.push(device.ieeeAddr);
1072
+ lqis.push({
1073
+ parent: 'undefined',
1074
+ networkAddress: 0,
1075
+ ieeeAddr: device.ieeeAddr,
1076
+ lqi: 'undefined',
1077
+ relationship: 0,
1078
+ depth: 0,
1079
+ status: 'disabled',
1080
+ });
1081
+ continue;
1082
+ }
1051
1083
  let resolved = await this.resolveEntity(device, 0);
1052
1084
  if (!resolved) {
1053
1085
  resolved = { name:'unresolved device', device:device }
1054
1086
  if (this.debugActive) this.debug('resolve Entity failed for ' + device.ieeeAddr)
1055
1087
  }
1056
- let result;
1088
+
1089
+ let attemptRouting = true;
1057
1090
 
1058
1091
  try {
1059
- result = await device.lqi();
1092
+ const result = await device.lqi();
1093
+ MapData.sdev.push(`lqi ${device.ieeeAddr}`);
1094
+ MapData.lqi[device.ieeeAddr] = result;
1095
+ const r_arr = Array.isArray(result) ? result : result?.neighbors;
1096
+ if (r_arr) {
1097
+ for (const dev of r_arr) {
1098
+ const ieeeAddr = dev.ieeeAddr || dev.eui64;
1099
+ if (dev !== undefined && ieeeAddr !== '0xffffffffffffffff') {
1100
+ const lq = (dev.linkquality == undefined) ? dev.lqi== undefined ? 0 : dev.lqi : dev.linkquality
1101
+ lqis.push({
1102
+ parent: (resolved ? resolved.device.ieeeAddr : undefined),
1103
+ networkAddress: dev.networkAddress || dev.nwkAddress,
1104
+ ieeeAddr: ieeeAddr,
1105
+ lqi: lq,
1106
+ relationship: dev.relationship,
1107
+ depth: dev.depth,
1108
+ status: lq > 0 ? 'online' : 'offline',
1109
+ });
1110
+ }
1111
+ }
1112
+ }
1060
1113
  } catch (error) {
1061
- errors.push(`Failed to execute LQI for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${this.filterHerdsmanError(error.message)}.`);
1114
+ MapData.edev.push(device.ieeeAddr);
1115
+ const eReason = this.filterHerdsmanError(error.message);
1116
+ errors.push(`Failed to execute LQI for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${eReason}.`);
1117
+ attemptRouting = eReason != 'Timeout'
1062
1118
  lqis.push({
1063
1119
  parent: 'undefined',
1064
1120
  networkAddress: 0,
@@ -1070,48 +1126,37 @@ class ZigbeeController extends EventEmitter {
1070
1126
  });
1071
1127
  }
1072
1128
 
1073
- if (result !== undefined) {
1074
- for (const dev of result.neighbors) {
1075
- if (dev !== undefined && dev.ieeeAddr !== '0xffffffffffffffff') {
1076
- lqis.push({
1077
- parent: (resolved ? resolved.device.ieeeAddr : undefined),
1078
- networkAddress: dev.networkAddress,
1079
- ieeeAddr: dev.ieeeAddr,
1080
- lqi: dev.linkquality,
1081
- relationship: dev.relationship,
1082
- depth: dev.depth,
1083
- status: dev.linkquality > 0 ? 'online' : 'offline',
1084
- });
1085
- }
1086
- }
1087
- }
1088
-
1089
- try {
1090
- result = await device.routingTable();
1091
- } catch (error) {
1092
- if (error) {
1093
- errors.push(`Failed to collect routing table for '${resolved ? resolved.name : 'unresolved device'} (${resolved ? resolved.device.modelID : 'unknown'}') : ${this.filterHerdsmanError(error.message)}`);
1094
- }
1095
- }
1096
-
1097
- if (result !== undefined) {
1098
- if (result.table !== undefined) {
1099
- for (const dev of result.table) {
1129
+ if (attemptRouting) try {
1130
+ const result = await device.routingTable();
1131
+ const r_arr = Array.isArray(result) ? result : result?.table;
1132
+ MapData.routing[device.ieeeAddr] = result;
1133
+ if (r_arr !== undefined) {
1134
+ for (const dev of r_arr) {
1100
1135
  routing.push({
1101
1136
  source: resolved.device.ieeeAddr,
1102
1137
  destination: dev.destinationAddress,
1103
- nextHop: dev.nextHop,
1138
+ nextHop: dev.nextHop ? dev.nextHop: dev.nextHopAddress,
1104
1139
  status: dev.status,
1105
1140
  });
1106
1141
  }
1107
1142
  }
1143
+ } catch (error) {
1144
+ MapData.edev.push(`routing ${device.ieeeAddr}`);
1145
+ if (error) {
1146
+ errors.push(`Failed to collect routing table for '${resolved?.name || 'unresolved device'} (${resolved?.device?.modelID || 'unknown'}') : ${this.filterHerdsmanError(error.message)}`);
1147
+ }
1108
1148
  }
1149
+ else errors.push(`Omitted collecting routing table for '${resolved?.name || 'unresolved device'} (${resolved?.device?.modelID ||'unknown'}') : LQI timed out`);
1150
+ this.emit('pairing', `Map Devices left: ${cnt}`);
1109
1151
  }
1110
- ));
1152
+ this.emit('pairing', 'Map data collection complete');
1153
+
1154
+ const fs = require('fs');
1155
+ fs.writeFileSync(this.adapter.expandFileName('mapdata.json'), JSON.stringify(MapData));
1111
1156
 
1112
1157
  callback && callback({lqis, routing, errors});
1113
1158
  if (errors.length) {
1114
- if (this.debugActive) this.debug(`Map Data collection complete with ${errors.length} issues:`);
1159
+ this.info(`Map Data collection complete with ${errors.length} issues:`);
1115
1160
  for (const msg of errors)
1116
1161
  if (this.debugActive) this.debug(msg);
1117
1162
  }
@@ -1387,6 +1432,7 @@ class ZigbeeController extends EventEmitter {
1387
1432
  if (error.code === 25 && retry > 0) {
1388
1433
  this.warn(`Error ${error.code} on send command to ${deviceId}. (${retry} tries left.), Error: ${error.message}`);
1389
1434
  retry--;
1435
+ await new Promise(resolve => setTimeout(resolve, 200));
1390
1436
  }
1391
1437
  else {
1392
1438
  retry = 0;
@@ -1641,7 +1687,7 @@ class ZigbeeController extends EventEmitter {
1641
1687
 
1642
1688
  async removeDevFromGroup(devId, groupId, epid) {
1643
1689
  if (this.debugActive) this.debug(`removeDevFromGroup with ${devId}, ${groupId}, ${epid}`);
1644
- let entity;
1690
+ let entity; // needed to have access to entity outside in the catch path.
1645
1691
  try {
1646
1692
  entity = await this.resolveEntity(devId);
1647
1693
  const group = await this.resolveEntity(groupId);