iobroker.zigbee2mqtt 0.2.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/rgb.js CHANGED
@@ -73,14 +73,12 @@ function cie_to_rgb(x, y, brightness) {
73
73
  green = green / red;
74
74
  blue = blue / red;
75
75
  red = 1.0;
76
- }
77
- else if (green > blue && green > red && green > 1.0) {
76
+ } else if (green > blue && green > red && green > 1.0) {
78
77
 
79
78
  red = red / green;
80
79
  blue = blue / green;
81
80
  green = 1.0;
82
- }
83
- else if (blue > red && blue > green && blue > 1.0) {
81
+ } else if (blue > red && blue > green && blue > 1.0) {
84
82
 
85
83
  red = red / blue;
86
84
  green = green / blue;
@@ -157,7 +155,9 @@ function hsvToRGB(h, s, v) {
157
155
  s = s / 100;
158
156
  v = v / 100;
159
157
 
160
- let r; let g; let b;
158
+ let r;
159
+ let g;
160
+ let b;
161
161
  if (arguments.length === 1) {
162
162
  s = h.s, v = h.v, h = h.h;
163
163
  }
@@ -167,12 +167,24 @@ function hsvToRGB(h, s, v) {
167
167
  const q = v * (1 - f * s);
168
168
  const t = v * (1 - (1 - f) * s);
169
169
  switch (i % 6) {
170
- case 0: r = v, g = t, b = p; break;
171
- case 1: r = q, g = v, b = p; break;
172
- case 2: r = p, g = v, b = t; break;
173
- case 3: r = p, g = q, b = v; break;
174
- case 4: r = t, g = p, b = v; break;
175
- case 5: r = v, g = p, b = q; break;
170
+ case 0:
171
+ r = v, g = t, b = p;
172
+ break;
173
+ case 1:
174
+ r = q, g = v, b = p;
175
+ break;
176
+ case 2:
177
+ r = p, g = v, b = t;
178
+ break;
179
+ case 3:
180
+ r = p, g = q, b = v;
181
+ break;
182
+ case 4:
183
+ r = t, g = p, b = v;
184
+ break;
185
+ case 5:
186
+ r = v, g = p, b = q;
187
+ break;
176
188
  }
177
189
  return {
178
190
  r: Math.round(r * 255),
@@ -185,17 +197,29 @@ function rgbToHSV(r, g, b, numeric) {
185
197
  if (arguments.length === 1) {
186
198
  g = r.g, b = r.b, r = r.r;
187
199
  }
188
- const max = Math.max(r, g, b); const min = Math.min(r, g, b);
200
+ const max = Math.max(r, g, b);
201
+ const min = Math.min(r, g, b);
189
202
  const d = max - min;
190
203
  let h;
191
204
  const s = (max === 0 ? 0 : d / max);
192
205
  const v = max / 255;
193
206
 
194
207
  switch (max) {
195
- case min: h = 0; break;
196
- case r: h = (g - b) + d * (g < b ? 6 : 0); h /= 6 * d; break;
197
- case g: h = (b - r) + d * 2; h /= 6 * d; break;
198
- case b: h = (r - g) + d * 4; h /= 6 * d; break;
208
+ case min:
209
+ h = 0;
210
+ break;
211
+ case r:
212
+ h = (g - b) + d * (g < b ? 6 : 0);
213
+ h /= 6 * d;
214
+ break;
215
+ case g:
216
+ h = (b - r) + d * 2;
217
+ h /= 6 * d;
218
+ break;
219
+ case b:
220
+ h = (r - g) + d * 4;
221
+ h /= 6 * d;
222
+ break;
199
223
  }
200
224
  if (numeric) return {
201
225
  // @ts-ignore
@@ -210,6 +234,7 @@ function rgbToHSV(r, g, b, numeric) {
210
234
  v: (v * 100).toFixed(3),
211
235
  };
212
236
  }
237
+
213
238
  function colorArrayFromString(value) {
214
239
  if (typeof (value) === 'string') {
215
240
  const rv = [];
@@ -260,4 +285,4 @@ exports.hsvToRGB = hsvToRGB;
260
285
  exports.rgbToHSV = rgbToHSV;
261
286
  exports.colorArrayFromString = colorArrayFromString;
262
287
  exports.colorStringFromRGBArray = colorStringFromRGBArray;
263
- exports.hsvToRGBString = hsvToRGBString;
288
+ exports.hsvToRGBString = hsvToRGBString;
package/lib/states.js CHANGED
@@ -274,17 +274,16 @@ const states = {
274
274
  },
275
275
  voltage: {
276
276
  id: 'voltage',
277
- name: 'Battery voltage',
277
+ name: 'Voltage',
278
278
  icon: undefined,
279
- role: 'battery.voltage',
279
+ role: 'value.voltage',
280
280
  write: false,
281
281
  read: true,
282
282
  type: 'number',
283
283
  unit: 'V',
284
- getter: payload => payload.voltage / 1000,
285
284
  def: 0,
286
285
  },
287
- ecozy_voltage: {
286
+ battery_voltage: {
288
287
  id: 'voltage',
289
288
  name: 'Battery voltage',
290
289
  icon: undefined,
@@ -293,9 +292,18 @@ const states = {
293
292
  read: true,
294
293
  type: 'number',
295
294
  unit: 'V',
296
- getter: payload => payload.voltage * 10,
297
295
  def: 0,
298
296
  },
297
+ energy: {
298
+ id: 'energy',
299
+ name: 'Sum of consumed energy',
300
+ icon: undefined,
301
+ role: 'value.power.consumption',
302
+ write: false,
303
+ read: true,
304
+ type: 'number',
305
+ unit: 'kWh'
306
+ },
299
307
  battery: {
300
308
  id: 'battery',
301
309
  prop: 'battery',
@@ -546,6 +554,17 @@ const states = {
546
554
  type: 'boolean',
547
555
  getter: payload => (payload.button_right === 'hold'),
548
556
  },
557
+ device_temperature: {
558
+ id: 'device_temperature',
559
+ name: 'Temperature of the device',
560
+ icon: undefined,
561
+ role: 'value.temperature',
562
+ write: false,
563
+ read: true,
564
+ type: 'number',
565
+ unit: '°C',
566
+ def: 0,
567
+ },
549
568
  temperature: {
550
569
  id: 'temperature',
551
570
  name: 'Temperature',
@@ -736,7 +755,7 @@ const states = {
736
755
  type: 'boolean',
737
756
  def: false,
738
757
  },
739
- smoke_detected2: { // for Heiman
758
+ smoke_detected2: { // for Heiman
740
759
  id: 'smoke',
741
760
  prop: 'smoke',
742
761
  name: 'Smoke leak detected',
@@ -747,7 +766,7 @@ const states = {
747
766
  type: 'boolean',
748
767
  def: false,
749
768
  },
750
- co_detected: { // for Heiman
769
+ co_detected: { // for Heiman
751
770
  id: 'carbon_monoxide',
752
771
  prop: 'carbon_monoxide',
753
772
  name: 'CO leak detected',
@@ -1074,11 +1093,11 @@ const states = {
1074
1093
  unit: 'V'
1075
1094
  },
1076
1095
  load_current: {
1077
- id: 'current',
1096
+ id: 'load_current',
1078
1097
  prop: 'current',
1079
1098
  name: 'Load current',
1080
1099
  icon: undefined,
1081
- role: 'value',
1100
+ role: 'value.current',
1082
1101
  write: false,
1083
1102
  read: true,
1084
1103
  type: 'number',
@@ -1232,12 +1251,15 @@ const states = {
1232
1251
  y: xy[1]
1233
1252
  };
1234
1253
  },
1235
- setterOpt: (value, options) => {
1236
- const hasTransitionTime = options && options.hasOwnProperty('transition_time');
1237
- const transitionTime = hasTransitionTime ? options.transition_time : 0;
1238
- return { ...options, transition: transitionTime };
1239
- },
1254
+ // setterOpt: (value, options) => {
1255
+ // const hasTransitionTime = options && options.hasOwnProperty('transition_time');
1256
+ // const transitionTime = hasTransitionTime ? options.transition_time : 0;
1257
+ // return { ...options, transition: transitionTime };
1258
+ // },
1240
1259
  getter: payload => {
1260
+ if (payload.color_mode != 'xy') {
1261
+ return undefined;
1262
+ }
1241
1263
  if (payload.color && payload.color.hasOwnProperty('x') && payload.color.hasOwnProperty('y')) {
1242
1264
  const colorval = rgb.cie_to_rgb(payload.color.x, payload.color.y);
1243
1265
  return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
@@ -2625,8 +2647,7 @@ const states = {
2625
2647
  max: 1,
2626
2648
  type: 'number',
2627
2649
  },
2628
- thermostat_keypad_lockout:
2629
- {
2650
+ thermostat_keypad_lockout: {
2630
2651
  id: 'keypad_lockout',
2631
2652
  name: 'Keypad Lockout',
2632
2653
  prop: 'keypad_lockout',
@@ -5447,6 +5468,7 @@ const states = {
5447
5468
  write: false,
5448
5469
  read: true,
5449
5470
  type: 'boolean',
5471
+ def: false,
5450
5472
  isEvent: true,
5451
5473
  getter: payload => (payload.action === 'brightness_move_up') ? true : undefined,
5452
5474
  },
@@ -5471,6 +5493,7 @@ const states = {
5471
5493
  write: false,
5472
5494
  read: true,
5473
5495
  type: 'boolean',
5496
+ def: false,
5474
5497
  isEvent: true,
5475
5498
  getter: payload => (payload.action === 'brightness_move_down') ? true : undefined,
5476
5499
  },
@@ -5842,8 +5865,7 @@ const states = {
5842
5865
  setter: (value, options) => {
5843
5866
  try {
5844
5867
  return JSON.parse(value);
5845
- }
5846
- catch (err) {
5868
+ } catch (err) {
5847
5869
  const effectjson = {
5848
5870
  colors: [{ r: 255, g: 0, b: 0 }, { r: 0, g: 255, b: 0 }, { r: 0, g: 0, b: 255 }],
5849
5871
  speed: 10,
@@ -6417,4 +6439,4 @@ module.exports = {
6417
6439
  states: states,
6418
6440
  unitLookup: unitLookup,
6419
6441
  nameLookup: nameLookup,
6420
- };
6442
+ };
@@ -0,0 +1,138 @@
1
+ const utils = require('./utils');
2
+ const incStatsQueue = [];
3
+ const timeOutCache = {};
4
+
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
+ }
134
+ }
135
+
136
+ module.exports = {
137
+ StatesController
138
+ };
package/lib/utils.js CHANGED
@@ -106,26 +106,28 @@ function getDeviceIcon(definition) {
106
106
  return icon;
107
107
  }
108
108
 
109
- function removeDeviceByIeee(devices, ieee_address) {
110
- const idx = devices.findIndex(x => x.ieee_address == ieee_address);
111
- if (idx > -1) {
112
- devices.splice(idx, 1);
113
- }
114
- }
115
109
  function clearArray(array) {
116
110
  while (array.length > 0) {
117
111
  array.pop();
118
112
  }
119
113
  }
120
114
 
121
- exports.bulbLevelToAdapterLevel = bulbLevelToAdapterLevel;
122
- exports.adapterLevelToBulbLevel = adapterLevelToBulbLevel;
123
- exports.bytesArrayToWordArray = bytesArrayToWordArray;
124
- exports.toMired = toMired;
125
- exports.miredKelvinConversion = miredKelvinConversion;
126
- exports.decimalToHex = decimalToHex;
127
- exports.getZbId = getZbId;
128
- exports.getAdId = getAdId;
129
- exports.removeDeviceByIeee = removeDeviceByIeee;
130
- exports.getDeviceIcon = getDeviceIcon;
131
- exports.clearArray = clearArray;
115
+ function moveArray(source, target) {
116
+ while (source.length > 0) {
117
+ target.push(source.shift());
118
+ }
119
+ }
120
+
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,
133
+ };
@@ -0,0 +1,84 @@
1
+ const WebSocket = require('ws');
2
+ let wsClient;
3
+ const wsHeartbeatIntervall = 5000;
4
+ const restartTimeout = 1000;
5
+ let ping;
6
+ let pingTimeout;
7
+ let autoRestartTimeout;
8
+
9
+ class WebsocketController {
10
+ constructor(adapter) {
11
+ this.adapter = adapter;
12
+ }
13
+
14
+ async initWsClient(server, port) {
15
+ try {
16
+ wsClient = new WebSocket(`ws://${server}:${port}/api`);
17
+
18
+ wsClient.on('open', () => {
19
+ // Send ping to server
20
+ this.sendPingToServer();
21
+ // Start Heartbeat
22
+ this.wsHeartbeat();
23
+ });
24
+
25
+ wsClient.on('pong', () => {
26
+ this.wsHeartbeat();
27
+ });
28
+
29
+ wsClient.on('close', async () => {
30
+ clearTimeout(pingTimeout);
31
+ clearTimeout(ping);
32
+
33
+ if (wsClient.readyState === WebSocket.CLOSED) {
34
+ this.autoRestart();
35
+ }
36
+ });
37
+
38
+ wsClient.on('message', () => { });
39
+
40
+ wsClient.on('error', (err) => { this.adapter.log.debug(err); });
41
+
42
+ return wsClient;
43
+ } catch (err) {
44
+ this.adapter.log.error(err);
45
+ }
46
+ }
47
+
48
+ async send(message) {
49
+ wsClient.send(message);
50
+ }
51
+
52
+ async sendPingToServer() {
53
+ //this.logDebug('Send ping to server');
54
+ wsClient.ping();
55
+ ping = setTimeout(() => {
56
+ this.sendPingToServer();
57
+ }, wsHeartbeatIntervall);
58
+ }
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
+ }
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
+ }
74
+
75
+ async allTimerClear() {
76
+ clearTimeout(pingTimeout);
77
+ clearTimeout(ping);
78
+ clearTimeout(autoRestartTimeout);
79
+ }
80
+ }
81
+
82
+ module.exports = {
83
+ WebsocketController
84
+ };
@@ -0,0 +1,80 @@
1
+ class Z2mController {
2
+ constructor(adapter, deviceCache, groupCache, logCustomizations) {
3
+ this.adapter = adapter;
4
+ this.groupCache = groupCache;
5
+ this.deviceCache = deviceCache;
6
+ this.logCustomizations = logCustomizations;
7
+ }
8
+
9
+ async createZ2MMessage(id, state) {
10
+ const splitedID = id.split('.');
11
+ if (splitedID.length < 4) {
12
+ this.adapter.log.warn(`state ${id} not valid`);
13
+ return;
14
+ }
15
+
16
+ const ieee_address = splitedID[2];
17
+ const stateName = splitedID[3];
18
+
19
+ const device = this.groupCache.concat(this.deviceCache).find(d => d.ieee_address == ieee_address);
20
+ if (!device) {
21
+ return;
22
+ }
23
+
24
+ const deviceState = device.states.find(s => s.id == stateName);
25
+ if (!deviceState) {
26
+ return;
27
+ }
28
+
29
+ let stateVal = state.val;
30
+ if (deviceState.setter) {
31
+ stateVal = deviceState.setter(state.val);
32
+ }
33
+
34
+ let stateID = deviceState.id;
35
+ if (deviceState.prop) {
36
+ stateID = deviceState.prop;
37
+ }
38
+
39
+ let topic = `${device.ieee_address}/set`;
40
+ if (device.ieee_address.includes('group_')) {
41
+ topic = `${device.id}/set`;
42
+ }
43
+
44
+ const controlObj = {
45
+ payload: {
46
+ [stateID]: stateVal
47
+ },
48
+ topic: topic
49
+ };
50
+ // set stats with the mentioned role or ids always immediately to ack = true, because these are not reported back by Zigbee2MQTT
51
+ if (['button'].includes(deviceState.role) || ['brightness_move', 'color_temp_move'].includes(stateID)) {
52
+ this.adapter.setState(id, state, true);
53
+ }
54
+
55
+ return controlObj;
56
+ }
57
+
58
+ async proxyZ2MLogs(messageObj) {
59
+ const logMessage = messageObj.payload.message;
60
+ if (this.logCustomizations.logfilter.some(x => logMessage.includes(x))) {
61
+ return;
62
+ }
63
+
64
+ const logLevel = messageObj.payload.level;
65
+ switch (logLevel) {
66
+ case 'debug':
67
+ case 'info':
68
+ case 'error':
69
+ this.adapter.log[logLevel](logMessage);
70
+ break;
71
+ case 'warning':
72
+ this.adapter.log.warn(logMessage);
73
+ break;
74
+ }
75
+ }
76
+ }
77
+
78
+ module.exports = {
79
+ Z2mController: Z2mController
80
+ };