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