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/README.md +14 -0
- package/admin/i18n/de/translations.json +7 -4
- package/admin/i18n/en/translations.json +6 -3
- package/admin/i18n/es/translations.json +6 -3
- package/admin/i18n/fr/translations.json +6 -3
- package/admin/i18n/it/translations.json +6 -3
- package/admin/i18n/nl/translations.json +6 -3
- package/admin/i18n/pl/translations.json +6 -3
- package/admin/i18n/pt/translations.json +6 -3
- package/admin/i18n/ru/translations.json +6 -3
- package/admin/i18n/zh-cn/translations.json +6 -3
- package/admin/jsonConfig.json +8 -3
- package/io-package.json +45 -6
- package/lib/exposes.js +195 -163
- package/lib/groups.js +27 -4
- package/lib/states.js +59 -36
- package/lib/utils.js +1 -1
- package/main.js +111 -101
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
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
|
|
444
|
-
if (deviceState.role
|
|
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
|
-
|
|
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
|
/**
|