iobroker.zigbee 3.2.4 → 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/README.md +15 -0
- package/admin/admin.js +376 -267
- package/admin/index_m.html +21 -32
- package/admin/tab_m.html +14 -2
- package/io-package.json +31 -31
- package/lib/commands.js +120 -76
- package/lib/exclude.js +1 -1
- package/lib/exposes.js +187 -77
- package/lib/groups.js +28 -15
- package/lib/{devices.js → legacy/devices.js} +27 -3
- package/lib/{states.js → legacy/states.js} +3 -3
- package/lib/localConfig.js +42 -0
- package/lib/models.js +615 -0
- package/lib/networkmap.js +15 -5
- package/lib/statescontroller.js +314 -299
- package/lib/utils.js +3 -4
- package/lib/zbBaseExtension.js +4 -0
- package/lib/zbDeviceAvailability.js +16 -23
- package/lib/zbDeviceConfigure.js +21 -8
- package/lib/zigbeecontroller.js +134 -88
- package/main.js +38 -42
- package/package.json +14 -15
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;
|
package/lib/zbBaseExtension.js
CHANGED
|
@@ -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 =
|
|
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 (
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
373
|
-
if (
|
|
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
|
}
|
package/lib/zbDeviceConfigure.js
CHANGED
|
@@ -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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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) {
|
package/lib/zigbeecontroller.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
244
|
-
this.
|
|
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
|
-
|
|
871
|
-
callback
|
|
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 (
|
|
911
|
-
this.info(`ignoring device announcement for ${entity
|
|
912
|
-
this.emit('pairing', `device interview state is ${entity
|
|
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',
|
|
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(`
|
|
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
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
)
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
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);
|