iobroker.zigbee2mqtt 0.1.0 → 0.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.
package/main.js CHANGED
@@ -14,24 +14,24 @@ const clearArray = require('./lib/utils').clearArray;
14
14
  let wsClient;
15
15
  let adapter;
16
16
  let createDevicesOrReady = false;
17
+ let isConnected = false;
17
18
  const incStatsQueue = [];
18
19
  const createCache = {};
19
20
  // eslint-disable-next-line prefer-const
20
21
  let deviceCache = [];
21
22
  // eslint-disable-next-line prefer-const
22
23
  let groupCache = [];
23
- const lastSeenCache = {};
24
24
  let ping;
25
25
  let pingTimeout;
26
26
  let autoRestartTimeout;
27
27
  const wsHeartbeatIntervall = 5000;
28
28
  const restartTimeout = 1000;
29
- const deviceAvailableTimeout = 10 * 60; // 10 Minutes
30
- const batteryDeviceAvailableTimeout = 24 * 60 * 60; // 24 Hours
31
- const checkAvailableInterval = 30 * 1000; // 10 Seconds
32
29
  let debugLogEnabled;
33
30
  let proxyZ2MLogsEnabled;
34
31
  let checkAvailableTimout;
32
+ let debugDevices = '';
33
+ let logfilter = [];
34
+ let useKelvin = false;
35
35
 
36
36
  class Zigbee2mqtt extends core.Adapter {
37
37
 
@@ -52,12 +52,27 @@ class Zigbee2mqtt extends core.Adapter {
52
52
  this.log.info(`Zigbee2MQTT Frontend Port: ${this.config.port}`);
53
53
  this.log.info(`Zigbee2MQTT Debug Log: ${this.config.debugLogEnabled ? 'activated' : 'deactivated'}`);
54
54
  this.log.info(`Proxy Zigbee2MQTT Logs to ioBroker Logs: ${this.config.proxyZ2MLogs ? 'activated' : 'deactivated'}`);
55
+ this.log.info(`Use Kelvin: ${this.config.useKelvin ? 'yes' : 'no'}`);
55
56
 
56
57
  this.setStateAsync('info.connection', false, true);
57
58
  this.createWsClient(this.config.server, this.config.port);
58
59
 
59
60
  debugLogEnabled = this.config.debugLogEnabled;
60
61
  proxyZ2MLogsEnabled = this.config.proxyZ2MLogs;
62
+ useKelvin = this.config.useKelvin;
63
+
64
+ const debugDevicesState = await this.getStateAsync('info.debugmessages');
65
+ if (debugDevicesState && debugDevicesState.val) {
66
+ debugDevices = String(debugDevicesState.val);
67
+ }
68
+
69
+ const logfilterState = await this.getStateAsync('info.logfilter');
70
+ if (logfilterState && logfilterState.val) {
71
+ logfilter = String(logfilterState.val).split(';').filter(x => x); // filter removes empty strings here
72
+ }
73
+
74
+
75
+ this.subscribeStatesAsync('*');
61
76
  }
62
77
 
63
78
  async createWsClient(server, port) {
@@ -68,23 +83,24 @@ class Zigbee2mqtt extends core.Adapter {
68
83
  // Set connection state
69
84
  this.setState('info.connection', true, true);
70
85
  this.log.info('Connect to server over websocket connection.');
86
+ isConnected = true;
71
87
  // Send ping to server
72
88
  this.sendPingToServer();
73
89
  // Start Heartbeat
74
90
  this.wsHeartbeat();
75
- // Start CheckAvailableTimer
76
- this.checkAvailableTimer();
77
91
  });
78
92
  wsClient.on('pong', () => {
79
93
  //this.logDebug('Receive pong from server');
80
94
  this.wsHeartbeat();
81
95
  });
82
96
  // On Close
83
- wsClient.on('close', () => {
97
+ wsClient.on('close', async () => {
84
98
  this.setState('info.connection', false, true);
85
99
  this.log.warn('Websocket disconnectet');
100
+ await this.setAllAvailableToFalse();
86
101
  clearTimeout(ping);
87
102
  clearTimeout(pingTimeout);
103
+ isConnected = false;
88
104
 
89
105
  if (wsClient.readyState === WebSocket.CLOSED) {
90
106
  this.autoRestart();
@@ -137,6 +153,7 @@ class Zigbee2mqtt extends core.Adapter {
137
153
  createDevicesOrReady = false;
138
154
  await this.createDeviceDefinitions(deviceCache, messageObj.payload);
139
155
  await this.createOrUpdateDevices(deviceCache);
156
+ this.subscribeWritableStates();
140
157
  createDevicesOrReady = true;
141
158
 
142
159
  // Now process all entries in the states queue
@@ -147,6 +164,7 @@ class Zigbee2mqtt extends core.Adapter {
147
164
  case 'bridge/groups':
148
165
  await this.createGroupDefinitions(groupCache, messageObj.payload);
149
166
  await this.createOrUpdateDevices(groupCache);
167
+ this.subscribeWritableStates();
150
168
  break;
151
169
  case 'bridge/event':
152
170
  break;
@@ -166,9 +184,24 @@ class Zigbee2mqtt extends core.Adapter {
166
184
  case 'bridge/response/touchlink/factory_reset':
167
185
  break;
168
186
  default:
169
- // States
170
187
  {
171
- if (!messageObj.topic.includes('/')) {
188
+ // {"payload":{"state":"online"},"topic":"FL.Licht.Links/availability"} ----> {"payload":{"available":true},"topic":"FL.Licht.Links"}
189
+ if (messageObj.topic.endsWith('/availability')) {
190
+ const topicSplit = messageObj.topic.split('/');
191
+ if (topicSplit.length == 2 && messageObj.payload && messageObj.payload.state) {
192
+ const newMessage = {
193
+ payload: { available: messageObj.payload.state == 'online' },
194
+ topic: topicSplit[0]
195
+ };
196
+ // As long as we are busy creating the devices, the states are written to the queue.
197
+ if (createDevicesOrReady == false) {
198
+ incStatsQueue[incStatsQueue.length] = newMessage;
199
+ break;
200
+ }
201
+ this.processDeviceMessage(newMessage);
202
+ }
203
+ // States
204
+ } else if (!messageObj.topic.includes('/')) {
172
205
  // As long as we are busy creating the devices, the states are written to the queue.
173
206
  if (createDevicesOrReady == false) {
174
207
  incStatsQueue[incStatsQueue.length] = messageObj;
@@ -188,18 +221,11 @@ class Zigbee2mqtt extends core.Adapter {
188
221
  return;
189
222
  }
190
223
 
191
- const device = deviceCache.find(x => x.id == messageObj.topic);
224
+ const device = groupCache.concat(deviceCache).find(x => x.id == messageObj.topic);
192
225
  if (device) {
193
226
  this.logDebug(`processDeviceMessage -> device: ${JSON.stringify(device)}`);
194
227
  try {
195
- // The state available must not be considered for the cacheLastSeen
196
- // Groups must not be considered for the cacheLastSeen
197
- if (messageObj.payload.available == undefined && !device.ieee_address.startsWith('group_')) {
198
- await this.cacheLastSeen(device, messageObj);
199
- }
200
228
  this.setDeviceState(messageObj, device);
201
- this.checkAvailable(device.ieee_address);
202
-
203
229
  } catch (error) {
204
230
  adapter.log.error(error);
205
231
  }
@@ -209,64 +235,11 @@ class Zigbee2mqtt extends core.Adapter {
209
235
  }
210
236
  }
211
237
 
212
- async cacheLastSeen(device, messageObj) {
213
- this.logDebug(`cacheLastSeen -> device: ${JSON.stringify(device)}`);
214
- this.logDebug(`cacheLastSeen -> messageObj: ${JSON.stringify(messageObj)}`);
215
- if (messageObj.payload.last_seen) {
216
- lastSeenCache[device.ieee_address] = new Date(messageObj.payload.last_seen).getTime();
217
- } else {
218
- lastSeenCache[device.ieee_address] = new Date().getTime();
219
- }
220
- this.logDebug(`cacheLastSeen -> deviceLastSeenCache: ${JSON.stringify(lastSeenCache)}`);
221
- }
222
-
223
- async checkAvailableTimer() {
224
- checkAvailableTimout = setTimeout(async () => {
225
- await this.checkAvailable(null);
226
- this.checkAvailableTimer();
227
- }, checkAvailableInterval);
228
- }
229
-
230
- async checkAvailable(ieee_address) {
231
- this.logDebug(`checkAvailable -> ieee_address: ${ieee_address}`);
232
- let checkList = {};
233
- if (ieee_address) {
234
- checkList[ieee_address] = null;
235
- }
236
- else {
237
- checkList = lastSeenCache;
238
- }
239
-
240
- for (const ieee_address in checkList) {
241
- const device = deviceCache.find(x => x.ieee_address == ieee_address);
242
-
243
- if (!device) {
244
- continue;
245
- }
246
-
247
- const isBatteryDevice = device.power_source == 'Battery' ? true : false;
248
- const offlineTimeout = isBatteryDevice ? batteryDeviceAvailableTimeout : deviceAvailableTimeout;
249
- const diffSec = Math.round((new Date().getTime() - lastSeenCache[ieee_address]) / 1000);
250
- const available = diffSec < offlineTimeout;
251
-
252
- this.logDebug(`checkAvailable -> device.id: ${device.id}, available: ${available}, diffSec: ${diffSec}, isBatteryDevice: ${isBatteryDevice}`);
253
-
254
- if (device.available == null || device.available != available) {
255
- this.logDebug(`checkAvailable -> device.id: ${device.id}, available: ${available}, diffSec: ${diffSec}, isBatteryDevice: ${isBatteryDevice}`);
256
- device.available = available;
257
- const messageObj = {
258
- topic: device.id,
259
- payload: {
260
- available: available,
261
- }
262
- };
238
+ async setDeviceState(messageObj, device) {
263
239
 
264
- this.processDeviceMessage(messageObj);
265
- }
240
+ if (debugDevices.includes(device.ieee_address)) {
241
+ this.log.warn(`--->>> fromZ2M -> ${device.ieee_address} states: ${JSON.stringify(messageObj)}`);
266
242
  }
267
- }
268
-
269
- async setDeviceState(messageObj, device) {
270
243
 
271
244
  for (const [key, value] of Object.entries(messageObj.payload)) {
272
245
  this.logDebug(`setDeviceState -> key: ${key}`);
@@ -284,13 +257,18 @@ class Zigbee2mqtt extends core.Adapter {
284
257
  if (!state) {
285
258
  continue;
286
259
  }
260
+
287
261
  const stateName = `${device.ieee_address}.${state.id}`;
288
262
 
289
- if (state.getter) {
290
- this.setState(stateName, state.getter(messageObj.payload), true);
291
- }
292
- else {
293
- this.setState(stateName, value, true);
263
+ try {
264
+ if (state.getter) {
265
+ await this.setStateAsync(stateName, state.getter(messageObj.payload), true);
266
+ }
267
+ else {
268
+ await this.setStateAsync(stateName, value, true);
269
+ }
270
+ } catch (err) {
271
+ this.log.warn(`Can not set ${stateName}`);
294
272
  }
295
273
  }
296
274
  }
@@ -300,7 +278,15 @@ class Zigbee2mqtt extends core.Adapter {
300
278
  clearArray(cache);
301
279
  for (const expose of exposes) {
302
280
  if (expose.definition != null) {
303
- await defineDeviceFromExposes(cache, expose.friendly_name, expose.ieee_address, expose.definition, expose.power_source);
281
+ // search for scenes in the endpoints and build them into an array
282
+ let scenes = [];
283
+ for (const key in expose.endpoints) {
284
+ if (expose.endpoints[key].scenes) {
285
+ scenes = scenes.concat(expose.endpoints[key].scenes);
286
+ }
287
+ }
288
+
289
+ await defineDeviceFromExposes(cache, expose.friendly_name, expose.ieee_address, expose.definition, expose.power_source, scenes, useKelvin);
304
290
  }
305
291
  }
306
292
  }
@@ -308,7 +294,7 @@ class Zigbee2mqtt extends core.Adapter {
308
294
  async createGroupDefinitions(cache, exposes) {
309
295
  clearArray(cache);
310
296
  for (const expose of exposes) {
311
- await defineGroupDevice(cache, expose.friendly_name, `group_${expose.id}`, expose.scenes);
297
+ await defineGroupDevice(cache, expose.friendly_name, `group_${expose.id}`, expose.scenes, useKelvin);
312
298
  }
313
299
  }
314
300
 
@@ -320,28 +306,30 @@ class Zigbee2mqtt extends core.Adapter {
320
306
  type: 'device',
321
307
  common: {
322
308
  name: deviceName,
323
- statusStates: {
324
- onlineId: `${this.name}.${this.instance}.${device.ieee_address}.available`
325
- },
326
309
  },
327
310
 
328
311
  native: {}
329
312
  };
313
+
314
+ if (!device.ieee_address.includes('group_')) {
315
+ deviceObj.common.statusStates = {
316
+ onlineId: `${this.name}.${this.instance}.${device.ieee_address}.available`
317
+ };
318
+ }
319
+
330
320
  //@ts-ignore
331
321
  await this.extendObjectAsync(device.ieee_address, deviceObj);
332
322
  createCache[device.ieee_address] = deviceObj;
333
323
  }
334
324
 
335
- // Special handling for groups, here it is checked whether the scenes match the current data from z2m.
325
+ // Here it is checked whether the scenes match the current data from z2m.
336
326
  // If necessary, scenes are automatically deleted from ioBroker.
337
- if (device.ieee_address.startsWith('group_')) {
338
- const sceneStates = await this.getStatesAsync(`${device.ieee_address}.scene_*`);
339
- const sceneIDs = Object.keys(sceneStates);
340
- for (const sceneID of sceneIDs) {
341
- const stateID = sceneID.split('.')[3];
342
- if (device.states.find(x => x.id == stateID) == null) {
343
- this.delObject(sceneID);
344
- }
327
+ const sceneStates = await this.getStatesAsync(`${device.ieee_address}.scene_*`);
328
+ const sceneIDs = Object.keys(sceneStates);
329
+ for (const sceneID of sceneIDs) {
330
+ const stateID = sceneID.split('.')[3];
331
+ if (device.states.find(x => x.id == stateID) == null) {
332
+ this.delObject(sceneID);
345
333
  }
346
334
  }
347
335
 
@@ -351,8 +339,6 @@ class Zigbee2mqtt extends core.Adapter {
351
339
  this.logDebug(`Orig. state: ${JSON.stringify(state)}`);
352
340
  this.logDebug(`Cleaned. state: ${JSON.stringify(iobState)}`);
353
341
 
354
-
355
-
356
342
  await this.extendObjectAsync(`${device.ieee_address}.${state.id}`, {
357
343
  type: 'state',
358
344
  common: iobState,
@@ -362,7 +348,6 @@ class Zigbee2mqtt extends core.Adapter {
362
348
  }
363
349
  }
364
350
  }
365
- this.subscribeWritableStates();
366
351
  }
367
352
 
368
353
  async copyAndCleanStateObj(state) {
@@ -385,7 +370,8 @@ class Zigbee2mqtt extends core.Adapter {
385
370
 
386
371
  async subscribeWritableStates() {
387
372
  await this.unsubscribeObjectsAsync('*');
388
- for (const device of deviceCache) {
373
+
374
+ for (const device of groupCache.concat(deviceCache)) {
389
375
  for (const state of device.states) {
390
376
  if (state.write == true) {
391
377
  this.subscribeStatesAsync(`${device.ieee_address}.${state.id}`);
@@ -406,7 +392,7 @@ class Zigbee2mqtt extends core.Adapter {
406
392
  const ieee_address = splitedID[2];
407
393
  const stateName = splitedID[3];
408
394
 
409
- const device = deviceCache.find(d => d.ieee_address == ieee_address);
395
+ const device = groupCache.concat(deviceCache).find(d => d.ieee_address == ieee_address);
410
396
 
411
397
  if (!device) {
412
398
  return;
@@ -440,8 +426,8 @@ class Zigbee2mqtt extends core.Adapter {
440
426
  },
441
427
  topic: topic
442
428
  };
443
- // set stats with role 'button' always immediately to ack = true, because these are not reported back by Zigbee2MQTT
444
- if (deviceState.role == 'button') {
429
+ // set stats with the mentioned role or ids always immediately to ack = true, because these are not reported back by Zigbee2MQTT
430
+ if (isConnected == true && (['button'].includes(deviceState.role) || ['brightness_move', 'color_temp_move'].includes(stateID))) {
445
431
  this.setState(id, state, true);
446
432
  }
447
433
 
@@ -451,9 +437,12 @@ class Zigbee2mqtt extends core.Adapter {
451
437
  async proxyZ2MLogs(messageObj) {
452
438
  this.logDebug(`proxyZ2MLogs -> messageObj: ${JSON.stringify(messageObj)}`);
453
439
 
454
- const logLevel = messageObj.payload.level;
455
440
  const logMessage = messageObj.payload.message;
441
+ if (logfilter.some(x => logMessage.includes(x))) {
442
+ return;
443
+ }
456
444
 
445
+ const logLevel = messageObj.payload.level;
457
446
  switch (logLevel) {
458
447
  case 'debug':
459
448
  case 'info':
@@ -472,8 +461,19 @@ class Zigbee2mqtt extends core.Adapter {
472
461
  }
473
462
  }
474
463
 
475
- onUnload(callback) {
464
+ async setAllAvailableToFalse() {
465
+ for (const device of deviceCache) {
466
+ for (const state of device.states) {
467
+ if (state.id == 'available') {
468
+ await this.setStateAsync(`${device.ieee_address}.${state.id}`, false, true);
469
+ }
470
+ }
471
+ }
472
+ }
473
+
474
+ async onUnload(callback) {
476
475
  try {
476
+ await this.setAllAvailableToFalse();
477
477
  clearTimeout(ping);
478
478
  clearTimeout(pingTimeout);
479
479
  clearTimeout(autoRestartTimeout);
@@ -488,10 +488,20 @@ class Zigbee2mqtt extends core.Adapter {
488
488
  if (state && state.ack == false) {
489
489
  const message = await this.createZ2MMessage(id, state);
490
490
  wsClient.send(message);
491
+
492
+ if (id.includes('info.debugmessages')) {
493
+ debugDevices = state.val;
494
+ this.setState(id, state.val, true);
495
+ }
496
+ if (id.includes('info.logfilter')) {
497
+ logfilter = state.val.split(';').filter(x => x); // filter removes empty strings here
498
+ this.setState(id, state.val, true);
499
+ }
491
500
  }
492
501
  }
493
502
  }
494
503
 
504
+
495
505
  if (require.main !== module) {
496
506
  // Export the constructor in compact mode
497
507
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.zigbee2mqtt",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Zigbee2MQTT adapter for ioBroker",
5
5
  "author": {
6
6
  "name": "Dennis Rathjen",