iobroker.zigbee 1.8.1 → 1.8.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.
- package/.eslintignore +2 -0
- package/.eslintrc.json +37 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/auto-merge.yml +17 -0
- package/.github/dependabot.yml +24 -0
- package/.github/stale.yml +13 -0
- package/.github/workflows/codeql.yml +41 -0
- package/.github/workflows/dependabot-automerge.yml +22 -0
- package/.github/workflows/test-and-release.yml +149 -0
- package/.releaseconfig.json +3 -0
- package/.travis/wiki.sh +28 -0
- package/.travis.yml +41 -0
- package/README.md +31 -8
- package/admin/admin.js +466 -482
- package/admin/i18n/de/translations.json +2 -2
- package/admin/index_m.html +1 -1
- package/admin/tab_m.html +3 -44
- package/admin/words.js +2 -2
- package/gulpfile.js +464 -0
- package/io-package.json +18 -25
- package/lib/backup.js +2 -2
- package/lib/binding.js +37 -32
- package/lib/colors.js +158 -163
- package/lib/commands.js +90 -99
- package/lib/developer.js +12 -9
- package/lib/devices.js +179 -169
- package/lib/exclude.js +36 -30
- package/lib/exposes.js +139 -163
- package/lib/groups.js +83 -81
- package/lib/json.js +6 -5
- package/lib/networkmap.js +3 -2
- package/lib/ota.js +18 -34
- package/lib/rgb.js +72 -114
- package/lib/seriallist.js +20 -25
- package/lib/states.js +526 -511
- package/lib/statescontroller.js +183 -206
- package/lib/utils.js +23 -24
- package/lib/zbBaseExtension.js +4 -4
- package/lib/zbDelayedAction.js +13 -5
- package/lib/zbDeviceAvailability.js +65 -69
- package/lib/zbDeviceConfigure.js +21 -9
- package/lib/zbDeviceEvent.js +4 -3
- package/lib/zigbeecontroller.js +103 -109
- package/main.js +147 -163
- package/package.json +15 -29
- package/test/integration.js +5 -0
- package/test/mocha.custom.opts +2 -0
- package/test/mocha.setup.js +14 -0
- package/test/package.js +5 -0
- package/test/unit.js +5 -0
- package/docs/de/img/CC2531.png +0 -0
- package/docs/de/img/CC2538_CC2592_PA.PNG +0 -0
- package/docs/de/img/CC2591.png +0 -0
- package/docs/de/img/boards.jpg +0 -0
- package/docs/de/img/cc26x2r.PNG +0 -0
- package/docs/de/img/results.jpg +0 -0
- package/docs/de/img/sku_429478_2.png +0 -0
- package/docs/de/img/sku_429601_2.png +0 -0
- package/docs/de/readme.md +0 -27
- package/docs/en/img/CC2531.png +0 -0
- package/docs/en/img/CC2591.png +0 -0
- package/docs/en/img/deconz.png +0 -0
- package/docs/en/img/sku_429478_2.png +0 -0
- package/docs/en/img/sku_429601_2.png +0 -0
- package/docs/en/readme.md +0 -30
- package/docs/flashing_via_arduino_(en).md +0 -110
- package/docs/ru/img/CC2531.png +0 -0
- package/docs/ru/img/CC2591.png +0 -0
- package/docs/ru/img/sku_429478_2.png +0 -0
- package/docs/ru/img/sku_429601_2.png +0 -0
- package/docs/ru/readme.md +0 -28
- package/docs/tutorial/CC2530_20190425.zip +0 -0
- package/docs/tutorial/CC2530_CC2591_20190515.zip +0 -0
- package/docs/tutorial/CC2530_CC2592_20190515.zip +0 -0
- package/docs/tutorial/CC2531_20190425.zip +0 -0
- package/docs/tutorial/adm5_1.PNG +0 -0
- package/docs/tutorial/adm5_2.PNG +0 -0
- package/docs/tutorial/cat.PNG +0 -0
- package/docs/tutorial/groups-1.png +0 -0
- package/docs/tutorial/groups-2.png +0 -0
- package/docs/tutorial/inst.PNG +0 -0
- package/docs/tutorial/reflash-finish.PNG +0 -0
- package/docs/tutorial/reflash-step0.png +0 -0
- package/docs/tutorial/reflash-step1.PNG +0 -0
- package/docs/tutorial/reflash-step2.PNG +0 -0
- package/docs/tutorial/settings.png +0 -0
- package/docs/tutorial/tab-dev-1.png +0 -0
- package/docs/tutorial/zigbee.png +0 -0
- package/docs/tutorial/zigbee15.png +0 -0
package/lib/utils.js
CHANGED
|
@@ -24,7 +24,7 @@ function bulbLevelToAdapterLevel(bulbLevel) {
|
|
|
24
24
|
// - Bulb level range [2...254] is linearly mapped to adapter level range [1...100].
|
|
25
25
|
if (bulbLevel >= 2) {
|
|
26
26
|
// Perform linear mapping of range [2...254] to [1...100]
|
|
27
|
-
return Math.round((bulbLevel - 2) * 99 / 252) + 1;
|
|
27
|
+
return (Math.round((bulbLevel - 2) * 99 / 252) + 1);
|
|
28
28
|
} else {
|
|
29
29
|
// The bulb is considered off. Even a bulb level of "1" is considered as off.
|
|
30
30
|
return 0;
|
|
@@ -42,7 +42,7 @@ function adapterLevelToBulbLevel(adapterLevel) {
|
|
|
42
42
|
// Please read the comments there regarding the rules applied here for mapping the values.
|
|
43
43
|
if (adapterLevel) {
|
|
44
44
|
// Perform linear mapping of range [1...100] to [2...254]
|
|
45
|
-
return Math.round((adapterLevel - 1) * 252 / 99) + 2;
|
|
45
|
+
return (Math.round((adapterLevel - 1) * 252 / 99) + 2);
|
|
46
46
|
} else {
|
|
47
47
|
// Switch the bulb off. Some bulbs need "0" (IKEA), others "1" (HUE), and according to the
|
|
48
48
|
// ZigBee docs "1" is the "minimum possible level"... we choose "0" here which seems to work.
|
|
@@ -53,7 +53,7 @@ function adapterLevelToBulbLevel(adapterLevel) {
|
|
|
53
53
|
function bytesArrayToWordArray(ba) {
|
|
54
54
|
const wa = [];
|
|
55
55
|
for (let i = 0; i < ba.length; i++) {
|
|
56
|
-
wa[(i / 2) | 0] |= ba[i] << (8
|
|
56
|
+
wa[(i / 2) | 0] |= ba[i] << (8*(i % 2));
|
|
57
57
|
}
|
|
58
58
|
return wa;
|
|
59
59
|
}
|
|
@@ -62,7 +62,7 @@ function bytesArrayToWordArray(ba) {
|
|
|
62
62
|
// If smaller, it is assumed to be mired.
|
|
63
63
|
function toMired(t) {
|
|
64
64
|
let miredValue = t;
|
|
65
|
-
if (t > 1000)
|
|
65
|
+
if (t > 1000){
|
|
66
66
|
miredValue = miredKelvinConversion(t);
|
|
67
67
|
}
|
|
68
68
|
return miredValue;
|
|
@@ -80,7 +80,7 @@ function miredKelvinConversion(t) {
|
|
|
80
80
|
*/
|
|
81
81
|
function decimalToHex(decimal, padding) {
|
|
82
82
|
let hex = Number(decimal).toString(16);
|
|
83
|
-
padding = typeof padding === 'undefined' || padding === null ? 2 : padding;
|
|
83
|
+
padding = typeof (padding) === 'undefined' || padding === null ? padding = 2 : padding;
|
|
84
84
|
|
|
85
85
|
while (hex.length < padding) {
|
|
86
86
|
hex = '0' + hex;
|
|
@@ -91,25 +91,24 @@ function decimalToHex(decimal, padding) {
|
|
|
91
91
|
|
|
92
92
|
function getZbId(adapterDevId) {
|
|
93
93
|
const idx = adapterDevId.indexOf('group');
|
|
94
|
-
if (idx > 0)
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
return `0x${adapterDevId.split('.')[2]}`;
|
|
94
|
+
if (idx > 0) return adapterDevId.substr(idx+6);
|
|
95
|
+
return '0x' + adapterDevId.split('.')[2];
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
function getAdId(adapter, id) {
|
|
101
|
-
return
|
|
99
|
+
return adapter.namespace + '.' + id.split('.')[2]; // iobroker device id
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
function flatten(arr) {
|
|
105
|
-
return arr.reduce((flat, toFlatten) =>
|
|
106
|
-
flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten)
|
|
103
|
+
return arr.reduce((flat, toFlatten) => {
|
|
104
|
+
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
|
|
105
|
+
}, []);
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
const forceEndDevice = flatten(
|
|
110
109
|
['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM']
|
|
111
|
-
.map(model => zigbeeHerdsmanConverters.devices.find((d) => d.model === model))
|
|
112
|
-
.map(mappedModel => mappedModel.zigbeeModel));
|
|
110
|
+
.map((model) => zigbeeHerdsmanConverters.devices.find((d) => d.model === model))
|
|
111
|
+
.map((mappedModel) => mappedModel.zigbeeModel));
|
|
113
112
|
|
|
114
113
|
// Xiaomi uses 4151 and 4447 (lumi.plug) as manufacturer ID.
|
|
115
114
|
const xiaomiManufacturerID = [4151, 4447];
|
|
@@ -118,7 +117,7 @@ const ikeaTradfriManufacturerID = [4476];
|
|
|
118
117
|
function sanitizeImageParameter(parameter) {
|
|
119
118
|
const replaceByDash = [/\?/g, /&/g, /[^a-z\d\-_./:]/gi, /[/]/gi];
|
|
120
119
|
let sanitized = parameter;
|
|
121
|
-
replaceByDash.forEach(r => sanitized = sanitized.replace(r, '-'));
|
|
120
|
+
replaceByDash.forEach((r) => sanitized = sanitized.replace(r, '-'));
|
|
122
121
|
return sanitized;
|
|
123
122
|
}
|
|
124
123
|
|
|
@@ -133,7 +132,7 @@ function getDeviceIcon(definition) {
|
|
|
133
132
|
return icon;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
exports.secondsToMilliseconds = seconds => seconds * 1000;
|
|
135
|
+
exports.secondsToMilliseconds = (seconds) => seconds * 1000;
|
|
137
136
|
exports.bulbLevelToAdapterLevel = bulbLevelToAdapterLevel;
|
|
138
137
|
exports.adapterLevelToBulbLevel = adapterLevelToBulbLevel;
|
|
139
138
|
exports.bytesArrayToWordArray = bytesArrayToWordArray;
|
|
@@ -142,11 +141,11 @@ exports.miredKelvinConversion = miredKelvinConversion;
|
|
|
142
141
|
exports.decimalToHex = decimalToHex;
|
|
143
142
|
exports.getZbId = getZbId;
|
|
144
143
|
exports.getAdId = getAdId;
|
|
145
|
-
exports.isRouter = device => device.type === 'Router' && !forceEndDevice.includes(device.modelID);
|
|
146
|
-
exports.isBatteryPowered = device => device.powerSource && device.powerSource === 'Battery';
|
|
147
|
-
exports.isXiaomiDevice = device =>
|
|
148
|
-
device.modelID !== 'lumi.router' &&
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
exports.isIkeaTradfriDevice = device => ikeaTradfriManufacturerID.includes(device.manufacturerID);
|
|
152
|
-
exports.getDeviceIcon
|
|
144
|
+
exports.isRouter = (device) => device.type === 'Router' && !forceEndDevice.includes(device.modelID);
|
|
145
|
+
exports.isBatteryPowered = (device) => device.powerSource && device.powerSource === 'Battery';
|
|
146
|
+
exports.isXiaomiDevice = (device) => {
|
|
147
|
+
return device.modelID !== 'lumi.router' && xiaomiManufacturerID.includes(device.manufacturerID) &&
|
|
148
|
+
(!device.manufacturerName || !device.manufacturerName.startsWith('Trust'));
|
|
149
|
+
};
|
|
150
|
+
exports.isIkeaTradfriDevice = (device) => ikeaTradfriManufacturerID.includes(device.manufacturerID);
|
|
151
|
+
exports.getDeviceIcon = getDeviceIcon;
|
package/lib/zbBaseExtension.js
CHANGED
|
@@ -14,18 +14,18 @@ class BaseExtension {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
error(message, data) {
|
|
17
|
-
this.zigbee.error(
|
|
17
|
+
this.zigbee.error(this.name + ':' + message, data);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
warn(message, data) {
|
|
21
|
-
this.zigbee.warn(
|
|
21
|
+
this.zigbee.warn(this.name + ':' + message, data);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
debug(message, data) {
|
|
25
25
|
if (this.elevate_debug)
|
|
26
|
-
this.zigbee.warn(
|
|
26
|
+
this.zigbee.warn('DE ' + this.name + ':' + message, data);
|
|
27
27
|
else
|
|
28
|
-
this.zigbee.debug(
|
|
28
|
+
this.zigbee.debug(this.name + ':' + message, data);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
sendError(error, message) {
|
package/lib/zbDelayedAction.js
CHANGED
|
@@ -8,11 +8,12 @@ class DelayedAction extends BaseExtension {
|
|
|
8
8
|
|
|
9
9
|
this.actions = {};
|
|
10
10
|
this.zigbee.delayAction = this.delayAction.bind(this);
|
|
11
|
-
this.name =
|
|
11
|
+
this.name = "DelayedAction";
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
setOptions(options) {
|
|
15
|
-
|
|
15
|
+
if (typeof(options) != 'object') return false;
|
|
16
|
+
return true;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
|
|
@@ -29,7 +30,11 @@ class DelayedAction extends BaseExtension {
|
|
|
29
30
|
// return false;
|
|
30
31
|
// }
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
if (device.interviewing === true) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return true;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
async onZigbeeStarted() {
|
|
@@ -92,7 +97,10 @@ class DelayedAction extends BaseExtension {
|
|
|
92
97
|
}
|
|
93
98
|
const foundDev = await this.zigbee.getDevice(device.ieeeAddr);
|
|
94
99
|
if (!foundDev) {
|
|
95
|
-
this.debug(
|
|
100
|
+
this.debug(
|
|
101
|
+
`No found device ${device.ieeeAddr} ${device.modelID}, ` +
|
|
102
|
+
`for doAction`
|
|
103
|
+
);
|
|
96
104
|
delete this.actions[device.ieeeAddr];
|
|
97
105
|
return;
|
|
98
106
|
}
|
|
@@ -112,7 +120,7 @@ class DelayedAction extends BaseExtension {
|
|
|
112
120
|
try {
|
|
113
121
|
// do action
|
|
114
122
|
await actionDef.action(device);
|
|
115
|
-
this.info(`Do action
|
|
123
|
+
this.info(`Do action succesfully ${device.ieeeAddr} ${device.modelID}`);
|
|
116
124
|
toDelete.push(actionDef);
|
|
117
125
|
} catch (error) {
|
|
118
126
|
this.sendError(error);
|
|
@@ -20,7 +20,7 @@ const forcedPingable = [
|
|
|
20
20
|
// will result in warnings "illegal state x,y" or "illegal state h,s" for color
|
|
21
21
|
// and possibly sudden changes in value due to the support for color_temp
|
|
22
22
|
// in mired and Kelvin.
|
|
23
|
-
const toZigbeeCandidates = ['local_temperature',
|
|
23
|
+
const toZigbeeCandidates = ['local_temperature','state', 'brightness']; //, 'color', 'color_temp'];
|
|
24
24
|
const Hours25 = 1000 * 60 * 60 * 25;
|
|
25
25
|
const MinAvailabilityTimeout = 300; // ping every 5 minutes with few devices
|
|
26
26
|
const MaxAvailabilityTimeout = 1800; // ping every 30 minutes with many devices;
|
|
@@ -44,76 +44,65 @@ class DeviceAvailability extends BaseExtension {
|
|
|
44
44
|
// force publish availability for new devices
|
|
45
45
|
this.zigbee.on('new', (entity) => {
|
|
46
46
|
// wait for 1s for creating device states
|
|
47
|
-
setTimeout(() =>
|
|
48
|
-
this.publishAvailability(entity.device, true, true)
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
this.publishAvailability(entity.device, true, true);
|
|
49
|
+
}, 1000);
|
|
49
50
|
});
|
|
50
51
|
this.startDevicePingQueue = []; // simple fifo array for starting device pings
|
|
51
52
|
this.startDevicePingTimeout = null; // handle for the timeout which empties the queue
|
|
52
53
|
this.startDevicePingDelay = 200; // 200 ms delay between starting the ping timeout
|
|
53
|
-
this.name =
|
|
54
|
+
this.name = "DeviceAvailability";
|
|
54
55
|
this.elevate_debug = false;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
setOptions(options) {
|
|
58
|
-
if (typeof
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (options.
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
if (options.disableForcedPing) {
|
|
65
|
-
this.forced_ping = false;
|
|
66
|
-
}
|
|
67
|
-
if (typeof options.pingTimeout === 'number') {
|
|
68
|
-
this.availability_timeout = Math.min(60, options.pingTimeout);
|
|
69
|
-
}
|
|
70
|
-
if (typeof options.pingCount === 'number') {
|
|
71
|
-
this.max_ping = Math.min(2, options.pingCount);
|
|
72
|
-
}
|
|
59
|
+
if (typeof(options) != 'object') return false;
|
|
60
|
+
if (options.disableActivePing) this.active_ping = false;
|
|
61
|
+
if (options.disableForcedPing) this.forced_ping = false;
|
|
62
|
+
if (typeof(options.pingTimeout)=='number') this.availability_timeout = Math.min(60,options.pingTimeout);
|
|
63
|
+
if (typeof(options.pingCount)=='number') this.max_ping = Math.min(2, options.pingCount);
|
|
73
64
|
return true;
|
|
74
65
|
}
|
|
75
66
|
|
|
76
67
|
isPingable(device) {
|
|
77
|
-
if (this.active_ping)
|
|
78
|
-
|
|
68
|
+
if (this.active_ping)
|
|
69
|
+
{
|
|
70
|
+
if (this.forced_ping && forcedPingable.find((d) => d && d.hasOwnProperty('zigbeeModel') && d.zigbeeModel.includes(device.modelID))) {
|
|
79
71
|
return true;
|
|
80
72
|
}
|
|
81
73
|
|
|
82
|
-
|
|
74
|
+
const result = utils.isRouter(device) && !utils.isBatteryPowered(device);
|
|
75
|
+
return result;
|
|
83
76
|
}
|
|
84
77
|
return false;
|
|
85
78
|
}
|
|
86
79
|
|
|
87
80
|
async getAllPingableDevices() {
|
|
88
81
|
const clients = await this.zigbee.getClients();
|
|
89
|
-
return clients.filter(d => this.isPingable(d));
|
|
82
|
+
return clients.filter((d) => this.isPingable(d));
|
|
90
83
|
}
|
|
91
84
|
|
|
92
85
|
async registerDevicePing(device, entity) {
|
|
93
|
-
this.debug(
|
|
86
|
+
this.debug('register device Ping for ' + JSON.stringify(device.ieeeAddr));
|
|
94
87
|
this.forcedNonPingable[device.ieeeAddr] = false;
|
|
95
88
|
// this.warn(`Called registerDevicePing for '${device}' of '${entity}'`);
|
|
96
|
-
if (!this.isPingable(device))
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
89
|
+
if (!this.isPingable(device)) return;
|
|
99
90
|
// ensure we do not already have this device in the queue
|
|
100
|
-
// TODO: Following does not work, may be `if (this.startDevicePingQueue.find(item => item && item.device === device)) { return; }`
|
|
101
91
|
this.startDevicePingQueue.forEach(item => {
|
|
102
|
-
if (item && item.device == device)
|
|
92
|
+
if (item && item.device == device)
|
|
103
93
|
return;
|
|
104
|
-
}
|
|
105
94
|
});
|
|
106
95
|
this.number_of_registered_devices++;
|
|
107
|
-
this.availability_timeout = Math.max(Math.min(this.number_of_registered_devices * AverageTimeBetweenPings,
|
|
108
|
-
this.startDevicePingQueue.push({device, entity});
|
|
109
|
-
if (this.startDevicePingTimeout == null)
|
|
110
|
-
this.startDevicePingTimeout = setTimeout(async
|
|
111
|
-
await this.startDevicePing()
|
|
112
|
-
|
|
96
|
+
this.availability_timeout = Math.max(Math.min(this.number_of_registered_devices * AverageTimeBetweenPings,MaxAvailabilityTimeout ),MinAvailabilityTimeout);
|
|
97
|
+
this.startDevicePingQueue.push({device:device, entity:entity});
|
|
98
|
+
if (this.startDevicePingTimeout == null)
|
|
99
|
+
this.startDevicePingTimeout = setTimeout(async() => {
|
|
100
|
+
await this.startDevicePing();
|
|
101
|
+
}, this.startDevicePingDelay);
|
|
113
102
|
}
|
|
114
103
|
|
|
115
104
|
async deregisterDevicePing(device) {
|
|
116
|
-
this.info(
|
|
105
|
+
this.info('deregister device Ping for deactivated device ' + JSON.stringify(device.ieeeAddr));
|
|
117
106
|
this.forcedNonPingable[device.ieeeAddr] = true;
|
|
118
107
|
if (this.timers[device.ieeeAddr]) {
|
|
119
108
|
clearTimeout(this.timers[device.ieeeAddr]);
|
|
@@ -124,15 +113,15 @@ class DeviceAvailability extends BaseExtension {
|
|
|
124
113
|
// this.warn(JSON.stringify(this));
|
|
125
114
|
this.startDevicePingTimeout = null;
|
|
126
115
|
const item = this.startDevicePingQueue.shift();
|
|
127
|
-
if (this.startDevicePingQueue.length >
|
|
128
|
-
this.startDevicePingTimeout = setTimeout(async
|
|
129
|
-
await this.startDevicePing()
|
|
116
|
+
if (this.startDevicePingQueue.length >0) {
|
|
117
|
+
this.startDevicePingTimeout = setTimeout(async() => {
|
|
118
|
+
await this.startDevicePing();
|
|
119
|
+
}, this.startDevicePingDelay);
|
|
130
120
|
}
|
|
131
121
|
if (item && item.hasOwnProperty('device')) {
|
|
132
122
|
this.handleIntervalPingable(item.device, item.entity);
|
|
133
123
|
}
|
|
134
124
|
}
|
|
135
|
-
|
|
136
125
|
async onZigbeeStarted() {
|
|
137
126
|
// As some devices are not checked for availability (e.g. battery powered devices)
|
|
138
127
|
// we mark these device as online by default.
|
|
@@ -142,20 +131,22 @@ class DeviceAvailability extends BaseExtension {
|
|
|
142
131
|
const clients = await this.zigbee.getClients();
|
|
143
132
|
// this.warn('onZigbeeStarted called');
|
|
144
133
|
for (const device of clients) {
|
|
134
|
+
|
|
145
135
|
if (this.isPingable(device)) {
|
|
146
136
|
// this.setTimerPingable(device);
|
|
147
137
|
} else {
|
|
148
138
|
// this.warn(`Setting '${device.ieeeAddr}' as available - battery driven`);
|
|
149
139
|
this.publishAvailability(device, true);
|
|
150
|
-
this.timers[device.ieeeAddr] = setInterval(() =>
|
|
151
|
-
this.handleIntervalNotPingable(device)
|
|
140
|
+
this.timers[device.ieeeAddr] = setInterval(() => {
|
|
141
|
+
this.handleIntervalNotPingable(device);
|
|
142
|
+
},utils.secondsToMilliseconds(this.availability_timeout));
|
|
152
143
|
}
|
|
153
144
|
}
|
|
154
145
|
}
|
|
155
146
|
|
|
156
147
|
async handleIntervalPingable(device, entity) {
|
|
157
148
|
const ieeeAddr = device.ieeeAddr;
|
|
158
|
-
const resolvedEntity = entity ? entity
|
|
149
|
+
const resolvedEntity = (entity ? entity: await this.zigbee.resolveEntity(ieeeAddr));
|
|
159
150
|
if (!resolvedEntity) {
|
|
160
151
|
this.debug(`Stop pinging '${ieeeAddr}' ${device.modelID}, device is not known anymore`);
|
|
161
152
|
return;
|
|
@@ -163,8 +154,8 @@ class DeviceAvailability extends BaseExtension {
|
|
|
163
154
|
if (this.isPingable(device)) {
|
|
164
155
|
let pingCount = this.ping_counters[device.ieeeAddr];
|
|
165
156
|
if (pingCount === undefined) {
|
|
166
|
-
this.ping_counters[device.ieeeAddr] = {failed: 0, reported: 0};
|
|
167
|
-
pingCount = {failed: 0, reported: 0};
|
|
157
|
+
this.ping_counters[device.ieeeAddr] = { failed: 0, reported: 0};
|
|
158
|
+
pingCount = { failed: 0, reported: 0};
|
|
168
159
|
}
|
|
169
160
|
|
|
170
161
|
// first see if we can "ping" the device by reading a Status
|
|
@@ -179,7 +170,8 @@ class DeviceAvailability extends BaseExtension {
|
|
|
179
170
|
return;
|
|
180
171
|
}
|
|
181
172
|
}
|
|
182
|
-
}
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
183
175
|
this.sendError(error);
|
|
184
176
|
this.debug(`Exception in readState of '${device.ieeeAddr}' - error : '${error}'`);
|
|
185
177
|
// intentionally empty: Just present to ensure we cause no harm
|
|
@@ -193,22 +185,26 @@ class DeviceAvailability extends BaseExtension {
|
|
|
193
185
|
this.ping_counters[device.ieeeAddr].failed = 0;
|
|
194
186
|
} catch (error) {
|
|
195
187
|
this.publishAvailability(device, false);
|
|
196
|
-
if (pingCount.failed++ <= this.max_ping)
|
|
188
|
+
if (pingCount.failed++ <= this.max_ping)
|
|
189
|
+
{
|
|
197
190
|
if (pingCount.failed < 2 && pingCount.reported < this.max_ping) {
|
|
198
191
|
this.warn(`Failed to ping ${ieeeAddr} ${device.modelID}`);
|
|
199
192
|
pingCount.reported++;
|
|
200
|
-
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
201
195
|
this.debug(`Failed to ping ${ieeeAddr} ${device.modelID} on ${pingCount} consecutive attempts`);
|
|
202
196
|
}
|
|
203
197
|
this.setTimerPingable(device, pingCount.failed);
|
|
204
|
-
this.ping_counters[device.ieeeAddr]
|
|
205
|
-
}
|
|
198
|
+
this.ping_counters[device.ieeeAddr]= pingCount;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
206
201
|
this.warn(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
|
|
207
202
|
}
|
|
208
203
|
}
|
|
209
204
|
}
|
|
210
205
|
}
|
|
211
206
|
|
|
207
|
+
|
|
212
208
|
async handleIntervalNotPingable(device) {
|
|
213
209
|
const entity = await this.zigbee.resolveEntity(device.ieeeAddr);
|
|
214
210
|
if (!entity || !device.lastSeen) {
|
|
@@ -224,14 +220,13 @@ class DeviceAvailability extends BaseExtension {
|
|
|
224
220
|
}
|
|
225
221
|
|
|
226
222
|
setTimerPingable(device, factor) {
|
|
227
|
-
if (factor === undefined || factor < 1)
|
|
228
|
-
factor = 1;
|
|
229
|
-
}
|
|
223
|
+
if (factor === undefined || factor < 1) factor = 1;
|
|
230
224
|
if (this.timers[device.ieeeAddr]) {
|
|
231
225
|
clearTimeout(this.timers[device.ieeeAddr]);
|
|
232
226
|
}
|
|
233
|
-
this.timers[device.ieeeAddr] = setTimeout(async
|
|
234
|
-
await this.handleIntervalPingable(device)
|
|
227
|
+
this.timers[device.ieeeAddr] = setTimeout(async() => {
|
|
228
|
+
await this.handleIntervalPingable(device);
|
|
229
|
+
}, utils.secondsToMilliseconds(this.availability_timeout * factor));
|
|
235
230
|
}
|
|
236
231
|
|
|
237
232
|
async stop() {
|
|
@@ -239,7 +234,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
239
234
|
clearTimeout(timer);
|
|
240
235
|
}
|
|
241
236
|
const clients = await this.zigbee.getClients();
|
|
242
|
-
clients.forEach(device => this.publishAvailability(device, false));
|
|
237
|
+
clients.forEach((device) => this.publishAvailability(device, false));
|
|
243
238
|
}
|
|
244
239
|
|
|
245
240
|
async onReconnect(device) {
|
|
@@ -274,8 +269,8 @@ class DeviceAvailability extends BaseExtension {
|
|
|
274
269
|
const payload = {available: available};
|
|
275
270
|
this.debug(`Publish available for ${ieeeAddr} = ${available}`);
|
|
276
271
|
this.zigbee.emit('publish', ieeeAddr.substr(2), entity.mapped.model, payload);
|
|
277
|
-
this.debug(`Publish LQ for ${ieeeAddr} = ${(available ? 10
|
|
278
|
-
this.zigbee.emit('publish', ieeeAddr.substr(2), entity.mapped.model, {linkquality: (available ? 10
|
|
272
|
+
this.debug(`Publish LQ for ${ieeeAddr} = ${(available ? 10: 0)}`);
|
|
273
|
+
this.zigbee.emit('publish', ieeeAddr.substr(2), entity.mapped.model, { linkquality: (available ? 10: 0) });
|
|
279
274
|
}
|
|
280
275
|
}
|
|
281
276
|
}
|
|
@@ -294,22 +289,23 @@ class DeviceAvailability extends BaseExtension {
|
|
|
294
289
|
this.setTimerPingable(device, 1);
|
|
295
290
|
const pc = this.ping_counters[device.ieeeAddr];
|
|
296
291
|
if (pc == undefined) {
|
|
297
|
-
this.ping_counters[device.ieeeAddr] = {failed:
|
|
298
|
-
}
|
|
292
|
+
this.ping_counters[device.ieeeAddr] = { failed:0, reported:0 };
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
299
295
|
this.ping_counters[device.ieeeAddr].failed++;
|
|
300
296
|
}
|
|
301
297
|
|
|
302
298
|
const online = this.state.hasOwnProperty(device.ieeeAddr) && this.state[device.ieeeAddr];
|
|
303
299
|
if (online && data.type === 'deviceAnnounce' && !utils.isIkeaTradfriDevice(device)) {
|
|
304
300
|
/**
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
301
|
+
* In case the device is powered off AND on within the availability timeout,
|
|
302
|
+
* zigbee2qmtt does not detect the device as offline (device is still marked online).
|
|
303
|
+
* When a device is turned on again the state could be out of sync.
|
|
304
|
+
* https://github.com/Koenkk/zigbee2mqtt/issues/1383#issuecomment-489412168
|
|
305
|
+
* endDeviceAnnce is typically send when a device comes online.
|
|
306
|
+
*
|
|
307
|
+
* This isn't needed for TRADFRI devices as they already send the state themself.
|
|
308
|
+
*/
|
|
313
309
|
this.onReconnect(device);
|
|
314
310
|
}
|
|
315
311
|
}
|
package/lib/zbDeviceConfigure.js
CHANGED
|
@@ -10,17 +10,20 @@ const forcedConfigureOnEachStart = [
|
|
|
10
10
|
zigbeeHerdsmanConverters.devices.find((d) => d.model === 'ZK03840')
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
|
|
13
15
|
class DeviceConfigure extends BaseExtension {
|
|
14
16
|
constructor(zigbee, options) {
|
|
15
17
|
super(zigbee, options);
|
|
16
18
|
|
|
17
19
|
this.configuring = new Set();
|
|
18
20
|
this.attempts = {};
|
|
19
|
-
this.name =
|
|
21
|
+
this.name = "DeviceConfigure";
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
setOptions(options) {
|
|
23
|
-
|
|
25
|
+
if (typeof(options) != 'object') return false;
|
|
26
|
+
return true;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
shouldConfigure(device, mappedDevice) {
|
|
@@ -34,8 +37,11 @@ class DeviceConfigure extends BaseExtension {
|
|
|
34
37
|
zigbeeHerdsmanConverters.getConfigureKey(mappedDevice)) {
|
|
35
38
|
return false;
|
|
36
39
|
}
|
|
40
|
+
if (device.interviewing === true) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
37
43
|
|
|
38
|
-
return
|
|
44
|
+
return true;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
async onZigbeeStarted() {
|
|
@@ -47,7 +53,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
47
53
|
|
|
48
54
|
if (forcedConfigureOnEachStart.find((d) => d && d.hasOwnProperty('zigbeeModel') && d.zigbeeModel.includes(device.modelID))) {
|
|
49
55
|
this.debug(`DeviceConfigure ${device.ieeeAddr} ${device.modelID} forced by adapter config`);
|
|
50
|
-
device.meta.configured = -1; // Force a
|
|
56
|
+
device.meta.configured = -1; // Force a reconfigure for this device
|
|
51
57
|
}
|
|
52
58
|
if (this.shouldConfigure(device, mappedDevice)) {
|
|
53
59
|
this.debug(`DeviceConfigure ${device.ieeeAddr} ${device.modelID} needed`);
|
|
@@ -58,7 +64,9 @@ class DeviceConfigure extends BaseExtension {
|
|
|
58
64
|
}
|
|
59
65
|
} catch (error) {
|
|
60
66
|
this.sendError(error);
|
|
61
|
-
this.error(
|
|
67
|
+
this.error(
|
|
68
|
+
`Failed to DeviceConfigure.onZigbeeStarted (${error.stack})`,
|
|
69
|
+
);
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
72
|
|
|
@@ -70,11 +78,13 @@ class DeviceConfigure extends BaseExtension {
|
|
|
70
78
|
}
|
|
71
79
|
} catch (error) {
|
|
72
80
|
this.sendError(error);
|
|
73
|
-
this.error(
|
|
81
|
+
this.error(
|
|
82
|
+
`Failed to DeviceConfigure.onZigbeeEvent (${error.stack})`,
|
|
83
|
+
);
|
|
74
84
|
}
|
|
75
85
|
}
|
|
76
86
|
|
|
77
|
-
onDeviceRemove(device)
|
|
87
|
+
onDeviceRemove(device){
|
|
78
88
|
try {
|
|
79
89
|
if (this.configuring.has(device.ieeeAddr)) {
|
|
80
90
|
this.configuring.delete(device.ieeeAddr);
|
|
@@ -85,11 +95,13 @@ class DeviceConfigure extends BaseExtension {
|
|
|
85
95
|
}
|
|
86
96
|
} catch (error) {
|
|
87
97
|
this.sendError(error);
|
|
88
|
-
this.error(
|
|
98
|
+
this.error(
|
|
99
|
+
`Failed to DeviceConfigure.onDeviceRemove (${error.stack})`,
|
|
100
|
+
);
|
|
89
101
|
}
|
|
90
102
|
}
|
|
91
103
|
|
|
92
|
-
onDeviceLeave(data, entity)
|
|
104
|
+
onDeviceLeave(data, entity){
|
|
93
105
|
if (entity) {
|
|
94
106
|
this.onDeviceRemove(entity.device);
|
|
95
107
|
} else {
|
package/lib/zbDeviceEvent.js
CHANGED
|
@@ -6,9 +6,10 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
|
|
|
6
6
|
class DeviceEvent extends BaseExtension {
|
|
7
7
|
constructor(zigbee, options) {
|
|
8
8
|
super(zigbee, options);
|
|
9
|
-
this.name =
|
|
9
|
+
this.name = "DeviceEvent";
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
async onZigbeeStarted() {
|
|
13
14
|
for (const device of await this.zigbee.getClients()) {
|
|
14
15
|
this.callOnEvent(device, 'start', {});
|
|
@@ -16,8 +17,8 @@ class DeviceEvent extends BaseExtension {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
setOptions(options) {
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
if (typeof(options) != 'object') return false;
|
|
21
|
+
return true;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
onZigbeeEvent(data, mappedDevice) {
|