iobroker.zigbee2mqtt 2.1.0 → 2.2.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.
@@ -3,136 +3,136 @@ const incStatsQueue = [];
3
3
  const timeOutCache = {};
4
4
 
5
5
  class StatesController {
6
- constructor(adapter, deviceCache, groupCache, logCustomizations) {
7
- this.adapter = adapter;
8
- this.groupCache = groupCache;
9
- this.deviceCache = deviceCache;
10
- this.logCustomizations = logCustomizations;
11
- }
12
-
13
- async processDeviceMessage(messageObj) {
14
- // Is payload present?
15
- if (messageObj.payload == '') {
16
- return;
17
- }
18
-
19
- const device = this.groupCache.concat(this.deviceCache).find(x => x.id == messageObj.topic);
20
- if (device) {
21
- try {
22
- this.setDeviceState(messageObj, device);
23
- } catch (error) {
24
- this.adapter.log.error(error);
25
- }
26
- } else {
27
- incStatsQueue[incStatsQueue.length] = messageObj;
28
- this.adapter.log.debug(`Device: ${messageObj.topic} not found, queue state in incStatsQueue!`);
29
- }
30
- }
31
-
32
- async setDeviceState(messageObj, device) {
33
- if (this.logCustomizations.debugDevices.includes(device.ieee_address)) {
34
- this.adapter.log.warn(`--->>> fromZ2M -> ${device.ieee_address} states: ${JSON.stringify(messageObj)}`);
35
- }
36
-
37
- for (const [key, value] of Object.entries(messageObj.payload)) {
38
- let states;
39
- if (key == 'action') {
40
- states = device.states.filter(x => (x.prop && x.prop == key));
41
- } else {
42
- states = device.states.filter(x => (x.prop && x.prop == key) || x.id == key);
43
- }
44
-
45
- for (const state of states) {
46
- if (!state) {
47
- continue;
48
- }
49
-
50
- const stateName = `${device.ieee_address}.${state.id}`;
51
-
52
- try {
53
- if (state.isEvent) {
54
- if (state.getter) {
55
- await this.setStateWithTimeoutAsync(stateName, state.getter(messageObj.payload), 300);
56
- } else {
57
- await this.setStateWithTimeoutAsync(stateName, value, 300);
58
- }
59
- } else {
60
- if (state.getter) {
61
- await this.setStateChangedAsync(stateName, state.getter(messageObj.payload));
62
- } else {
63
- await this.setStateChangedAsync(stateName, value);
64
- }
65
- }
66
- } catch (err) {
67
- incStatsQueue[incStatsQueue.length] = messageObj;
68
- this.adapter.log.debug(`Can not set ${stateName}, queue state in incStatsQueue!`);
69
- }
70
- }
71
- }
72
- }
73
-
74
- async setStateAsync(stateName, value) {
75
- if (value !== undefined) {
76
- await this.adapter.setStateAsync(stateName, value, true);
77
- }
78
- }
79
-
80
- async setStateChangedAsync(stateName, value) {
81
- if (value !== undefined) {
82
- await this.adapter.setStateChangedAsync(stateName, value, true);
83
- }
84
- }
85
-
86
- async setStateWithTimeoutAsync(stateName, value, timeout) {
87
- if (value !== undefined) {
88
- await this.adapter.setStateAsync(stateName, value, true);
89
- if (timeOutCache[stateName]) {
90
- clearTimeout(timeOutCache[stateName]);
91
- }
92
- timeOutCache[stateName] = setTimeout(() => {
93
- this.adapter.setStateAsync(stateName, !value, true);
94
- }, timeout);
95
- }
96
- }
97
-
98
- async processQueue() {
99
- const oldIncStatsQueue = [];
100
- utils.moveArray(incStatsQueue, oldIncStatsQueue);
101
- while (oldIncStatsQueue.length > 0) {
102
- this.processDeviceMessage(oldIncStatsQueue.shift());
103
- }
104
- }
105
-
106
- async subscribeWritableStates() {
107
- await this.adapter.unsubscribeObjectsAsync('*');
108
- for (const device of this.groupCache.concat(this.deviceCache)) {
109
- for (const state of device.states) {
110
- if (state.write == true) {
111
- this.adapter.subscribeStatesAsync(`${device.ieee_address}.${state.id}`);
112
- }
113
- }
114
- }
115
- this.adapter.subscribeStatesAsync('info.debugmessages');
116
- this.adapter.subscribeStatesAsync('info.logfilter');
117
- }
118
-
119
- async setAllAvailableToFalse() {
120
- for (const device of this.deviceCache) {
121
- for (const state of device.states) {
122
- if (state.id == 'available') {
123
- await this.adapter.setStateChangedAsync(`${device.ieee_address}.${state.id}`, false, true);
124
- }
125
- }
126
- }
127
- }
128
-
129
- async allTimerClear() {
130
- for (const timer in timeOutCache) {
131
- clearTimeout(timeOutCache[timer]);
132
- }
133
- }
6
+ constructor(adapter, deviceCache, groupCache, logCustomizations) {
7
+ this.adapter = adapter;
8
+ this.groupCache = groupCache;
9
+ this.deviceCache = deviceCache;
10
+ this.logCustomizations = logCustomizations;
11
+ }
12
+
13
+ processDeviceMessage(messageObj) {
14
+ // Is payload present?
15
+ if (messageObj.payload == '') {
16
+ return;
17
+ }
18
+
19
+ const device = this.groupCache.concat(this.deviceCache).find(x => x.id == messageObj.topic);
20
+ if (device) {
21
+ try {
22
+ this.setDeviceStateSafely(messageObj, device);
23
+ } catch (error) {
24
+ this.adapter.log.error(error);
25
+ }
26
+ } else {
27
+ incStatsQueue[incStatsQueue.length] = messageObj;
28
+ this.adapter.log.debug(`Device: ${messageObj.topic} not found, queue state in incStatsQueue!`);
29
+ }
30
+ }
31
+
32
+ async setDeviceStateSafely(messageObj, device) {
33
+ if (this.logCustomizations.debugDevices.includes(device.ieee_address)) {
34
+ this.adapter.log.warn(`--->>> fromZ2M -> ${device.ieee_address} states: ${JSON.stringify(messageObj)}`);
35
+ }
36
+
37
+ for (const [key, value] of Object.entries(messageObj.payload)) {
38
+ let states;
39
+ if (key == 'action') {
40
+ states = device.states.filter(x => (x.prop && x.prop == key));
41
+ } else {
42
+ states = device.states.filter(x => (x.prop && x.prop == key) || x.id == key);
43
+ }
44
+
45
+ for (const state of states) {
46
+ if (!state) {
47
+ continue;
48
+ }
49
+
50
+ const stateName = `${device.ieee_address}.${state.id}`;
51
+
52
+ try {
53
+ if (state.isEvent) {
54
+ if (state.getter) {
55
+ await this.setStateWithTimeoutAsync(stateName, state.getter(messageObj.payload), 300);
56
+ } else {
57
+ await this.setStateWithTimeoutAsync(stateName, value, 300);
58
+ }
59
+ } else {
60
+ if (state.getter) {
61
+ await this.setStateChangedSafelyAsync(stateName, state.getter(messageObj.payload));
62
+ } else {
63
+ await this.setStateChangedSafelyAsync(stateName, value);
64
+ }
65
+ }
66
+ } catch (err) {
67
+ incStatsQueue[incStatsQueue.length] = messageObj;
68
+ this.adapter.log.debug(`Can not set ${stateName}, queue state in incStatsQueue!`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ async setStateSafelyAsync(stateName, value) {
75
+ if (value !== undefined) {
76
+ await this.adapter.setStateAsync(stateName, value, true);
77
+ }
78
+ }
79
+
80
+ async setStateChangedSafelyAsync(stateName, value) {
81
+ if (value !== undefined) {
82
+ await this.adapter.setStateChangedAsync(stateName, value, true);
83
+ }
84
+ }
85
+
86
+ async setStateWithTimeoutAsync(stateName, value, timeout) {
87
+ if (value !== undefined) {
88
+ await this.adapter.setStateAsync(stateName, value, true);
89
+ if (timeOutCache[stateName]) {
90
+ clearTimeout(timeOutCache[stateName]);
91
+ }
92
+ timeOutCache[stateName] = setTimeout(() => {
93
+ this.adapter.setStateAsync(stateName, !value, true);
94
+ }, timeout);
95
+ }
96
+ }
97
+
98
+ processQueue() {
99
+ const oldIncStatsQueue = [];
100
+ utils.moveArray(incStatsQueue, oldIncStatsQueue);
101
+ while (oldIncStatsQueue.length > 0) {
102
+ this.processDeviceMessage(oldIncStatsQueue.shift());
103
+ }
104
+ }
105
+
106
+ async subscribeWritableStates() {
107
+ await this.adapter.unsubscribeObjectsAsync('*');
108
+ for (const device of this.groupCache.concat(this.deviceCache)) {
109
+ for (const state of device.states) {
110
+ if (state.write == true) {
111
+ this.adapter.subscribeStatesAsync(`${device.ieee_address}.${state.id}`);
112
+ }
113
+ }
114
+ }
115
+ this.adapter.subscribeStatesAsync('info.debugmessages');
116
+ this.adapter.subscribeStatesAsync('info.logfilter');
117
+ }
118
+
119
+ async setAllAvailableToFalse() {
120
+ for (const device of this.deviceCache) {
121
+ for (const state of device.states) {
122
+ if (state.id == 'available') {
123
+ await this.adapter.setStateChangedAsync(`${device.ieee_address}.${state.id}`, false, true);
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ async allTimerClear() {
130
+ for (const timer in timeOutCache) {
131
+ clearTimeout(timeOutCache[timer]);
132
+ }
133
+ }
134
134
  }
135
135
 
136
136
  module.exports = {
137
- StatesController
137
+ StatesController
138
138
  };
package/lib/utils.js CHANGED
@@ -2,132 +2,132 @@
2
2
  * Converts a bulb level of range [0...254] to an adapter level of range [0...100]
3
3
  */
4
4
  function bulbLevelToAdapterLevel(bulbLevel) {
5
- // Convert from bulb levels [0...254] to adapter levels [0...100]:
6
- // - Bulb level 0 is a forbidden value according to the ZigBee spec "ZigBee Cluster Library
7
- // (for ZigBee 3.0) User Guide", but some bulbs (HUE) accept this value and interpret this
8
- // value as "switch the bulb off".
9
- // - A bulb level of "1" is the "minimum possible level" which should mean "bulb off",
10
- // but there are bulbs that do not switch off (they need "0", some IKEA bulbs are affected).
11
- // - No visible difference was seen between bulb level 1 and 2 on HUE LCT012 bulbs.
12
- //
13
- // Conclusion:
14
- // - We map adapter level "0" to the (forbidden) bulb level "0" that seems to switch all
15
- // known bulbs.
16
- // - Bulb level "1" is not used, but if received nevertheless, it is converted to
17
- // adapter level "0" (off).
18
- // - Bulb level range [2...254] is linearly mapped to adapter level range [1...100].
19
- if (bulbLevel >= 2) {
20
- // Perform linear mapping of range [2...254] to [1...100]
21
- return (Math.round((bulbLevel - 2) * 99 / 252) + 1);
22
- } else {
23
- // The bulb is considered off. Even a bulb level of "1" is considered as off.
24
- return 0;
25
- } // else
5
+ // Convert from bulb levels [0...254] to adapter levels [0...100]:
6
+ // - Bulb level 0 is a forbidden value according to the ZigBee spec "ZigBee Cluster Library
7
+ // (for ZigBee 3.0) User Guide", but some bulbs (HUE) accept this value and interpret this
8
+ // value as "switch the bulb off".
9
+ // - A bulb level of "1" is the "minimum possible level" which should mean "bulb off",
10
+ // but there are bulbs that do not switch off (they need "0", some IKEA bulbs are affected).
11
+ // - No visible difference was seen between bulb level 1 and 2 on HUE LCT012 bulbs.
12
+ //
13
+ // Conclusion:
14
+ // - We map adapter level "0" to the (forbidden) bulb level "0" that seems to switch all
15
+ // known bulbs.
16
+ // - Bulb level "1" is not used, but if received nevertheless, it is converted to
17
+ // adapter level "0" (off).
18
+ // - Bulb level range [2...254] is linearly mapped to adapter level range [1...100].
19
+ if (bulbLevel >= 2) {
20
+ // Perform linear mapping of range [2...254] to [1...100]
21
+ return (Math.round((bulbLevel - 2) * 99 / 252) + 1);
22
+ } else {
23
+ // The bulb is considered off. Even a bulb level of "1" is considered as off.
24
+ return 0;
25
+ } // else
26
26
  }
27
27
 
28
28
  /**
29
29
  * Converts an adapter level of range [0...100] to a bulb level of range [0...254]
30
30
  */
31
31
  function adapterLevelToBulbLevel(adapterLevel) {
32
- // Convert from adapter levels [0...100] to bulb levels [0...254].
33
- // This is the inverse of function bulbLevelToAdapterLevel().
34
- // Please read the comments there regarding the rules applied here for mapping the values.
35
- if (adapterLevel) {
36
- // Perform linear mapping of range [1...100] to [2...254]
37
- return (Math.round((adapterLevel - 1) * 252 / 99) + 2);
38
- } else {
39
- // Switch the bulb off. Some bulbs need "0" (IKEA), others "1" (HUE), and according to the
40
- // ZigBee docs "1" is the "minimum possible level"... we choose "0" here which seems to work.
41
- return 0;
42
- } // else
32
+ // Convert from adapter levels [0...100] to bulb levels [0...254].
33
+ // This is the inverse of function bulbLevelToAdapterLevel().
34
+ // Please read the comments there regarding the rules applied here for mapping the values.
35
+ if (adapterLevel) {
36
+ // Perform linear mapping of range [1...100] to [2...254]
37
+ return (Math.round((adapterLevel - 1) * 252 / 99) + 2);
38
+ } else {
39
+ // Switch the bulb off. Some bulbs need "0" (IKEA), others "1" (HUE), and according to the
40
+ // ZigBee docs "1" is the "minimum possible level"... we choose "0" here which seems to work.
41
+ return 0;
42
+ } // else
43
43
  }
44
44
 
45
45
  function bytesArrayToWordArray(ba) {
46
- const wa = [];
47
- for (let i = 0; i < ba.length; i++) {
48
- wa[(i / 2) | 0] |= ba[i] << (8 * (i % 2));
49
- }
50
- return wa;
46
+ const wa = [];
47
+ for (let i = 0; i < ba.length; i++) {
48
+ wa[(i / 2) | 0] |= ba[i] << (8 * (i % 2));
49
+ }
50
+ return wa;
51
51
  }
52
52
 
53
53
  // If the value is greater than 1000, kelvin is assumed.
54
54
  // If smaller, it is assumed to be mired.
55
55
  function toMired(t) {
56
- let miredValue = t;
57
- if (t > 1000) {
58
- miredValue = miredKelvinConversion(t);
59
- }
60
- return miredValue;
56
+ let miredValue = t;
57
+ if (t > 1000) {
58
+ miredValue = miredKelvinConversion(t);
59
+ }
60
+ return miredValue;
61
61
  }
62
62
 
63
63
  function miredKelvinConversion(t) {
64
- return Math.round(1000000 / t);
64
+ return Math.round(1000000 / t);
65
65
  }
66
66
 
67
67
  /**
68
68
  * Converts a decimal number to a hex string with zero-padding
69
69
  */
70
70
  function decimalToHex(decimal, padding) {
71
- let hex = Number(decimal).toString(16);
72
- padding = typeof (padding) === 'undefined' || padding === null ? padding = 2 : padding;
71
+ let hex = Number(decimal).toString(16);
72
+ padding = typeof (padding) === 'undefined' || padding === null ? padding = 2 : padding;
73
73
 
74
- while (hex.length < padding) {
75
- hex = `0${hex}`;
76
- }
74
+ while (hex.length < padding) {
75
+ hex = `0${hex}`;
76
+ }
77
77
 
78
- return hex;
78
+ return hex;
79
79
  }
80
80
 
81
81
  function getZbId(adapterDevId) {
82
- const idx = adapterDevId.indexOf('group');
83
- if (idx > 0) return adapterDevId.substr(idx + 6);
84
- return '0x' + adapterDevId.split('.')[2];
82
+ const idx = adapterDevId.indexOf('group');
83
+ if (idx > 0) return adapterDevId.substr(idx + 6);
84
+ return '0x' + adapterDevId.split('.')[2];
85
85
  }
86
86
 
87
87
  function getAdId(adapter, id) {
88
- return adapter.namespace + '.' + id.split('.')[2]; // iobroker device id
88
+ return adapter.namespace + '.' + id.split('.')[2]; // iobroker device id
89
89
  }
90
90
 
91
91
  function sanitizeImageParameter(parameter) {
92
- const replaceByDash = [/\?/g, /&/g, /[^a-z\d\-_./:]/gi, /[/]/gi];
93
- let sanitized = parameter;
94
- replaceByDash.forEach((r) => sanitized = sanitized.replace(r, '-'));
95
- return sanitized;
92
+ const replaceByDash = [/\?/g, /&/g, /[^a-z\d\-_./:]/gi, /[/]/gi];
93
+ let sanitized = parameter;
94
+ replaceByDash.forEach((r) => sanitized = sanitized.replace(r, '-'));
95
+ return sanitized;
96
96
  }
97
97
 
98
98
  function getDeviceIcon(definition) {
99
- let icon = definition.icon;
100
- if (icon) {
101
- icon = icon.replace('${model}', sanitizeImageParameter(definition.model));
102
- }
103
- if (!icon) {
104
- icon = `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.jpg`;
105
- }
106
- return icon;
99
+ let icon = definition.icon;
100
+ if (icon) {
101
+ icon = icon.replace('${model}', sanitizeImageParameter(definition.model));
102
+ }
103
+ if (!icon) {
104
+ icon = `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.jpg`;
105
+ }
106
+ return icon;
107
107
  }
108
108
 
109
109
  function clearArray(array) {
110
- while (array.length > 0) {
111
- array.pop();
112
- }
110
+ while (array.length > 0) {
111
+ array.pop();
112
+ }
113
113
  }
114
114
 
115
115
  function moveArray(source, target) {
116
- while (source.length > 0) {
117
- target.push(source.shift());
118
- }
116
+ while (source.length > 0) {
117
+ target.push(source.shift());
118
+ }
119
119
  }
120
120
 
121
121
  module.exports = {
122
- bulbLevelToAdapterLevel,
123
- adapterLevelToBulbLevel,
124
- bytesArrayToWordArray,
125
- toMired,
126
- miredKelvinConversion,
127
- decimalToHex,
128
- getZbId,
129
- getAdId,
130
- getDeviceIcon,
131
- clearArray,
132
- moveArray,
122
+ bulbLevelToAdapterLevel,
123
+ adapterLevelToBulbLevel,
124
+ bytesArrayToWordArray,
125
+ toMired,
126
+ miredKelvinConversion,
127
+ decimalToHex,
128
+ getZbId,
129
+ getAdId,
130
+ getDeviceIcon,
131
+ clearArray,
132
+ moveArray,
133
133
  };
@@ -7,78 +7,78 @@ let pingTimeout;
7
7
  let autoRestartTimeout;
8
8
 
9
9
  class WebsocketController {
10
- constructor(adapter) {
11
- this.adapter = adapter;
12
- }
10
+ constructor(adapter) {
11
+ this.adapter = adapter;
12
+ }
13
13
 
14
- async initWsClient(server, port) {
15
- try {
16
- wsClient = new WebSocket(`ws://${server}:${port}/api`);
14
+ initWsClient() {
15
+ try {
16
+ wsClient = new WebSocket(`ws://${this.adapter.config.wsServerIP}:${this.adapter.config.wsServerPort}/api`);
17
17
 
18
- wsClient.on('open', () => {
19
- // Send ping to server
20
- this.sendPingToServer();
21
- // Start Heartbeat
22
- this.wsHeartbeat();
23
- });
18
+ wsClient.on('open', () => {
19
+ // Send ping to server
20
+ this.sendPingToServer();
21
+ // Start Heartbeat
22
+ this.wsHeartbeat();
23
+ });
24
24
 
25
- wsClient.on('pong', () => {
26
- this.wsHeartbeat();
27
- });
25
+ wsClient.on('pong', () => {
26
+ this.wsHeartbeat();
27
+ });
28
28
 
29
- wsClient.on('close', async () => {
30
- clearTimeout(pingTimeout);
31
- clearTimeout(ping);
29
+ wsClient.on('close', async () => {
30
+ clearTimeout(pingTimeout);
31
+ clearTimeout(ping);
32
32
 
33
- if (wsClient.readyState === WebSocket.CLOSED) {
34
- this.autoRestart();
35
- }
36
- });
33
+ if (wsClient.readyState === WebSocket.CLOSED) {
34
+ this.autoRestart();
35
+ }
36
+ });
37
37
 
38
- wsClient.on('message', () => { });
38
+ wsClient.on('message', () => { });
39
39
 
40
- wsClient.on('error', (err) => { this.adapter.log.debug(err); });
40
+ wsClient.on('error', (err) => { this.adapter.log.debug(err); });
41
41
 
42
- return wsClient;
43
- } catch (err) {
44
- this.adapter.log.error(err);
45
- }
46
- }
42
+ return wsClient;
43
+ } catch (err) {
44
+ this.adapter.log.error(err);
45
+ }
46
+ }
47
47
 
48
- async send(message) {
49
- wsClient.send(message);
50
- }
48
+ send(message) {
49
+ wsClient.send(message);
50
+ }
51
51
 
52
- async sendPingToServer() {
53
- //this.logDebug('Send ping to server');
54
- wsClient.ping();
55
- ping = setTimeout(() => {
56
- this.sendPingToServer();
57
- }, wsHeartbeatIntervall);
58
- }
52
+ sendPingToServer() {
53
+ //this.logDebug('Send ping to server');
54
+ wsClient.ping();
55
+ ping = setTimeout(() => {
56
+ this.sendPingToServer();
57
+ }, wsHeartbeatIntervall);
58
+ }
59
59
 
60
- async wsHeartbeat() {
61
- clearTimeout(pingTimeout);
62
- pingTimeout = setTimeout(() => {
63
- this.adapter.log.warn('Websocked connection timed out');
64
- wsClient.terminate();
65
- }, wsHeartbeatIntervall + 1000);
66
- }
60
+ wsHeartbeat() {
61
+ clearTimeout(pingTimeout);
62
+ pingTimeout = setTimeout(() => {
63
+ this.adapter.log.warn('Websocked connection timed out');
64
+ wsClient.terminate();
65
+ }, wsHeartbeatIntervall + 1000);
66
+ }
67
67
 
68
- async autoRestart() {
69
- this.adapter.log.warn(`Start try again in ${restartTimeout / 1000} seconds...`);
70
- autoRestartTimeout = setTimeout(() => {
71
- this.adapter.onReady();
72
- }, restartTimeout);
73
- }
68
+ async autoRestart() {
69
+ this.adapter.log.warn(`Start try again in ${restartTimeout / 1000} seconds...`);
70
+ autoRestartTimeout = setTimeout(() => {
71
+ this.adapter.startWebsocket();
72
+ }, restartTimeout);
73
+ }
74
74
 
75
- async allTimerClear() {
76
- clearTimeout(pingTimeout);
77
- clearTimeout(ping);
78
- clearTimeout(autoRestartTimeout);
79
- }
75
+ async allTimerClear() {
76
+ clearTimeout(pingTimeout);
77
+ clearTimeout(ping);
78
+ clearTimeout(autoRestartTimeout);
79
+ }
80
80
  }
81
81
 
82
82
  module.exports = {
83
- WebsocketController
83
+ WebsocketController
84
84
  };