matterbridge-zigbee2mqtt 2.3.0-dev.4 → 2.3.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/CHANGELOG.md +1 -1
- package/dist/entity.d.ts +492 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/entity.js +251 -10
- package/dist/entity.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/payloadTypes.d.ts +25 -0
- package/dist/payloadTypes.d.ts.map +1 -0
- package/dist/payloadTypes.js +23 -0
- package/dist/payloadTypes.js.map +1 -0
- package/dist/platform.d.ts +88 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +48 -1
- package/dist/platform.js.map +1 -0
- package/dist/zigbee2mqtt.d.ts +182 -0
- package/dist/zigbee2mqtt.d.ts.map +1 -0
- package/dist/zigbee2mqtt.js +272 -22
- package/dist/zigbee2mqtt.js.map +1 -0
- package/dist/zigbee2mqttTypes.d.ts +350 -0
- package/dist/zigbee2mqttTypes.d.ts.map +1 -0
- package/dist/zigbee2mqttTypes.js +23 -0
- package/dist/zigbee2mqttTypes.js.map +1 -0
- package/npm-shrinkwrap.json +3 -3
- package/package.json +2 -2
- package/tsconfig.production.json +0 -13
package/dist/zigbee2mqtt.js
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Zigbee2MQTT and all the interfaces to communicate with zigbee2MQTT.
|
|
3
|
+
*
|
|
4
|
+
* @file zigbee2mqtt.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2023-06-30
|
|
7
|
+
* @version 2.3.3
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
1
23
|
import fs from 'fs';
|
|
2
24
|
import path from 'path';
|
|
3
25
|
import * as util from 'util';
|
|
@@ -8,7 +30,9 @@ import { AnsiLogger, rs, db, dn, gn, er, zb, hk, id, idn, ign, REVERSE, REVERSEO
|
|
|
8
30
|
import { mkdir } from 'fs/promises';
|
|
9
31
|
const writeFile = util.promisify(fs.writeFile);
|
|
10
32
|
export class Zigbee2MQTT extends EventEmitter {
|
|
33
|
+
// Logger
|
|
11
34
|
log;
|
|
35
|
+
// Instance properties
|
|
12
36
|
mqttHost;
|
|
13
37
|
mqttPort;
|
|
14
38
|
mqttTopic;
|
|
@@ -31,17 +55,19 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
31
55
|
z2mDevices;
|
|
32
56
|
z2mGroups;
|
|
33
57
|
loggedEntries = 0;
|
|
58
|
+
// Define our MQTT options
|
|
34
59
|
options = {
|
|
35
60
|
clientId: 'classZigbee2MQTT_' + crypto.randomBytes(8).toString('hex'),
|
|
36
61
|
keepalive: 60,
|
|
37
62
|
protocolId: 'MQTT',
|
|
38
63
|
protocolVersion: 5,
|
|
39
|
-
reconnectPeriod: 5000,
|
|
40
|
-
connectTimeout: 60 * 1000,
|
|
64
|
+
reconnectPeriod: 5000, // 1000
|
|
65
|
+
connectTimeout: 60 * 1000, // 30 * 1000
|
|
41
66
|
username: undefined,
|
|
42
67
|
password: undefined,
|
|
43
68
|
clean: true,
|
|
44
69
|
};
|
|
70
|
+
// Constructor
|
|
45
71
|
constructor(mqttHost, mqttPort, mqttTopic, mqttUsername = undefined, mqttPassword = undefined, protocolVersion = 5, debug = false) {
|
|
46
72
|
super();
|
|
47
73
|
this.mqttHost = mqttHost;
|
|
@@ -59,11 +85,11 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
59
85
|
this.z2mVersion = '';
|
|
60
86
|
this.z2mDevices = [];
|
|
61
87
|
this.z2mGroups = [];
|
|
62
|
-
this.log = new AnsiLogger({ logName: 'Zigbee2MQTT', logTimestampFormat: 4
|
|
88
|
+
this.log = new AnsiLogger({ logName: 'Zigbee2MQTT', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: debug ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
63
89
|
this.log.debug(`Created new instance with host: ${mqttHost} port: ${mqttPort} protocol ${protocolVersion} topic: ${mqttTopic} username: ${mqttUsername !== undefined && mqttUsername !== '' ? mqttUsername : 'undefined'} password: ${mqttPassword !== undefined && mqttPassword !== '' ? '*****' : 'undefined'}`);
|
|
64
90
|
}
|
|
65
91
|
setLogDebug(logDebug) {
|
|
66
|
-
this.log.logLevel = logDebug ? "debug" : "info"
|
|
92
|
+
this.log.logLevel = logDebug ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */;
|
|
67
93
|
}
|
|
68
94
|
async setDataPath(dataPath) {
|
|
69
95
|
try {
|
|
@@ -81,6 +107,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
81
107
|
}
|
|
82
108
|
}
|
|
83
109
|
}
|
|
110
|
+
// Get the URL for connect
|
|
84
111
|
getUrl() {
|
|
85
112
|
return 'mqtt://' + this.mqttHost + ':' + this.mqttPort.toString();
|
|
86
113
|
}
|
|
@@ -90,12 +117,13 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
90
117
|
.then((client) => {
|
|
91
118
|
this.log.debug('Connection established');
|
|
92
119
|
this.mqttClient = client;
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
93
121
|
this.mqttClient.on('connect', (packet) => {
|
|
94
|
-
this.log.debug(`MQTT client connect to ${this.getUrl()}${rs}`);
|
|
122
|
+
this.log.debug(`MQTT client connect to ${this.getUrl()}${rs}` /* , connack*/);
|
|
95
123
|
this.mqttIsConnected = true;
|
|
96
124
|
this.mqttIsReconnecting = false;
|
|
97
125
|
this.mqttIsEnding = false;
|
|
98
|
-
this.emit('mqtt_connect');
|
|
126
|
+
this.emit('mqtt_connect'); // Never emitted at the start cause we connect async
|
|
99
127
|
});
|
|
100
128
|
this.mqttClient.on('reconnect', () => {
|
|
101
129
|
this.log.debug(`MQTT client reconnect to ${this.getUrl()}${rs}`);
|
|
@@ -126,11 +154,17 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
126
154
|
this.log.debug('MQTT client error', error);
|
|
127
155
|
this.emit('mqtt_error', error);
|
|
128
156
|
});
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
129
158
|
this.mqttClient.on('packetsend', (packet) => {
|
|
159
|
+
// this.log.debug('classZigbee2MQTT=>Event packetsend');
|
|
130
160
|
});
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
131
162
|
this.mqttClient.on('packetreceive', (packet) => {
|
|
163
|
+
// this.log.debug('classZigbee2MQTT=>Event packetreceive');
|
|
132
164
|
});
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
133
166
|
this.mqttClient.on('message', (topic, payload, packet) => {
|
|
167
|
+
// this.log.debug(`classZigbee2MQTT=>Event message topic: ${topic} payload: ${payload.toString()} packet: ${debugStringify(packet)}`);
|
|
134
168
|
this.messageHandler(topic, payload);
|
|
135
169
|
});
|
|
136
170
|
this.log.debug('Started');
|
|
@@ -138,6 +172,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
138
172
|
this.mqttIsReconnecting = false;
|
|
139
173
|
this.mqttIsEnding = false;
|
|
140
174
|
this.emit('mqtt_connect');
|
|
175
|
+
// Send a heartbeat every 60 seconds
|
|
141
176
|
this.mqttKeepaliveInterval = setInterval(async () => {
|
|
142
177
|
this.log.debug('Publishing keepalive MQTT message');
|
|
143
178
|
try {
|
|
@@ -182,6 +217,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
182
217
|
async subscribe(topic) {
|
|
183
218
|
if (this.mqttClient && this.mqttIsConnected) {
|
|
184
219
|
this.log.debug(`Subscribing topic: ${topic}`);
|
|
220
|
+
// Use subscribeAsync for promise-based handling
|
|
185
221
|
this.mqttClient
|
|
186
222
|
.subscribeAsync(topic, { qos: 2 })
|
|
187
223
|
.then(() => {
|
|
@@ -205,6 +241,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
205
241
|
this.mqttPublishQueueTimeout = setInterval(async () => {
|
|
206
242
|
if (this.mqttClient && this.mqttPublishQueue.length > 0) {
|
|
207
243
|
this.log.debug(`**Publish ${REVERSE}[${this.mqttPublishQueue.length}-${this.mqttPublishInflights}]${REVERSEOFF} topic: ${this.mqttPublishQueue[0].topic} message: ${this.mqttPublishQueue[0].message}${rs}`);
|
|
244
|
+
// this.publish(this.mqttPublishQueue[0].topic, this.mqttPublishQueue[0].message);
|
|
208
245
|
try {
|
|
209
246
|
this.mqttPublishInflights++;
|
|
210
247
|
await this.mqttClient.publishAsync(this.mqttPublishQueue[0].topic, this.mqttPublishQueue[0].message, { qos: 2 });
|
|
@@ -257,13 +294,15 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
257
294
|
async writeBufferJSON(file, buffer) {
|
|
258
295
|
const filePath = path.join(this.mqttDataPath, file);
|
|
259
296
|
let jsonData;
|
|
297
|
+
// Parse the buffer to JSON
|
|
260
298
|
try {
|
|
261
299
|
jsonData = this.tryJsonParse(buffer.toString());
|
|
262
300
|
}
|
|
263
301
|
catch (error) {
|
|
264
302
|
this.log.error('writeBufferJSON: parsing error:', error);
|
|
265
|
-
return;
|
|
303
|
+
return; // Stop execution if parsing fails
|
|
266
304
|
}
|
|
305
|
+
// Write the JSON data to a file
|
|
267
306
|
writeFile(`${filePath}.json`, JSON.stringify(jsonData, null, 2))
|
|
268
307
|
.then(() => {
|
|
269
308
|
this.log.debug(`Successfully wrote to ${filePath}.json`);
|
|
@@ -274,6 +313,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
274
313
|
}
|
|
275
314
|
async writeFile(file, data) {
|
|
276
315
|
const filePath = path.join(this.mqttDataPath, file);
|
|
316
|
+
// Write the data to a file
|
|
277
317
|
writeFile(`${filePath}`, data)
|
|
278
318
|
.then(() => {
|
|
279
319
|
this.log.debug(`Successfully wrote to ${filePath}`);
|
|
@@ -282,6 +322,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
282
322
|
this.log.error(`Error writing to ${filePath}:`, error);
|
|
283
323
|
});
|
|
284
324
|
}
|
|
325
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
285
326
|
tryJsonParse(text) {
|
|
286
327
|
try {
|
|
287
328
|
return JSON.parse(text);
|
|
@@ -302,6 +343,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
302
343
|
else {
|
|
303
344
|
data = { state: payloadString };
|
|
304
345
|
}
|
|
346
|
+
// this.log.debug('classZigbee2MQTT=>Message bridge/state', data);
|
|
305
347
|
if (data.state === 'online') {
|
|
306
348
|
this.z2mIsOnline = true;
|
|
307
349
|
this.emit('online');
|
|
@@ -314,6 +356,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
314
356
|
}
|
|
315
357
|
else if (topic.startsWith(this.mqttTopic + '/bridge/info')) {
|
|
316
358
|
const data = this.tryJsonParse(payload.toString());
|
|
359
|
+
// this.log.info('classZigbee2MQTT=>Message bridge/info', data);
|
|
317
360
|
this.z2mPermitJoin = data.permit_join ? data.permit_join : false;
|
|
318
361
|
this.z2mPermitJoinTimeout = data.permit_join_timeout ? data.permit_join_timeout : 0;
|
|
319
362
|
this.z2mVersion = data.version ? data.version : '';
|
|
@@ -332,14 +375,14 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
332
375
|
this.log.info(`Message bridge/info advanced.legacy_availability_payload is ${data.config.advanced.legacy_availability_payload}`);
|
|
333
376
|
this.emit('info', this.z2mVersion, this.z2mIsAvailabilityEnabled, this.z2mPermitJoin, this.z2mPermitJoinTimeout);
|
|
334
377
|
this.emit('bridge-info', data);
|
|
335
|
-
if (this.log.logLevel === "debug")
|
|
378
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */)
|
|
336
379
|
this.writeBufferJSON('bridge-info', payload);
|
|
337
380
|
}
|
|
338
381
|
else if (topic.startsWith(this.mqttTopic + '/bridge/devices')) {
|
|
339
382
|
this.z2mDevices.splice(0, this.z2mDevices.length);
|
|
340
383
|
const devices = this.tryJsonParse(payload.toString());
|
|
341
384
|
const data = this.tryJsonParse(payload.toString());
|
|
342
|
-
if (this.log.logLevel === "debug")
|
|
385
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */)
|
|
343
386
|
this.writeBufferJSON('bridge-devices', payload);
|
|
344
387
|
this.emit('bridge-devices', data);
|
|
345
388
|
let index = 1;
|
|
@@ -394,10 +437,13 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
394
437
|
};
|
|
395
438
|
for (const expose of device.definition.exposes) {
|
|
396
439
|
if (!expose.property && !expose.name && expose.features && expose.type) {
|
|
440
|
+
// Specific expose https://www.zigbee2mqtt.io/guide/usage/exposes.html
|
|
397
441
|
if (z2m.category === '') {
|
|
442
|
+
// Only the first type: light, switch ...
|
|
398
443
|
z2m.category = expose.type;
|
|
399
444
|
}
|
|
400
445
|
for (const feature of expose.features) {
|
|
446
|
+
// Exposes nested inside features
|
|
401
447
|
feature.category = expose.type;
|
|
402
448
|
z2m.exposes.push(feature);
|
|
403
449
|
if (feature.endpoint) {
|
|
@@ -406,6 +452,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
406
452
|
}
|
|
407
453
|
}
|
|
408
454
|
else {
|
|
455
|
+
// Generic expose https://www.zigbee2mqtt.io/guide/usage/exposes.html
|
|
409
456
|
expose.category = '';
|
|
410
457
|
z2m.exposes.push(expose);
|
|
411
458
|
}
|
|
@@ -421,18 +468,20 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
421
468
|
endpoint: key,
|
|
422
469
|
};
|
|
423
470
|
z2m.endpoints.push(endpointWithKey);
|
|
471
|
+
// this.log.debug('classZigbee2MQTT=>Message bridge/devices endpoints=>', device.friendly_name, key, endpoint);
|
|
424
472
|
}
|
|
425
473
|
this.z2mDevices.push(z2m);
|
|
426
474
|
}
|
|
427
475
|
}
|
|
428
476
|
this.log.debug(`Received ${this.z2mDevices.length} devices`);
|
|
429
477
|
this.emit('devices');
|
|
478
|
+
// this.printDevices();
|
|
430
479
|
}
|
|
431
480
|
else if (topic.startsWith(this.mqttTopic + '/bridge/groups')) {
|
|
432
481
|
this.z2mGroups.splice(0, this.z2mGroups.length);
|
|
433
482
|
const groups = this.tryJsonParse(payload.toString());
|
|
434
483
|
const data = this.tryJsonParse(payload.toString());
|
|
435
|
-
if (this.log.logLevel === "debug")
|
|
484
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */)
|
|
436
485
|
this.writeBufferJSON('bridge-groups', payload);
|
|
437
486
|
this.emit('bridge-groups', data);
|
|
438
487
|
let index = 1;
|
|
@@ -458,6 +507,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
458
507
|
}
|
|
459
508
|
this.log.debug(`Received ${this.z2mGroups.length} groups`);
|
|
460
509
|
this.emit('groups');
|
|
510
|
+
// this.printGroups();
|
|
461
511
|
}
|
|
462
512
|
else if (topic.startsWith(this.mqttTopic + '/bridge/extensions')) {
|
|
463
513
|
const extensions = this.tryJsonParse(payload.toString());
|
|
@@ -515,28 +565,49 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
515
565
|
}
|
|
516
566
|
const data = this.tryJsonParse(payload.toString());
|
|
517
567
|
this.log.debug(`Message topic: ${topic} payload:${rs}`, data);
|
|
568
|
+
/*
|
|
569
|
+
[05/09/2023, 20:35:26] [z2m] classZigbee2MQTT=>Message bridge/response zigbee2mqtt/bridge/response/group/add {
|
|
570
|
+
data: { friendly_name: 'Guest', id: 1 },
|
|
571
|
+
status: 'ok',
|
|
572
|
+
transaction: '1nqux-2'
|
|
573
|
+
}
|
|
574
|
+
[11/09/2023, 15:13:54] [z2m] classZigbee2MQTT=>Message bridge/response zigbee2mqtt/bridge/response/group/members/add {
|
|
575
|
+
data: { device: '0x84fd27fffe83066f/1', group: 'Master Guest room' },
|
|
576
|
+
status: 'ok',
|
|
577
|
+
transaction: '2ww7l-5'
|
|
578
|
+
}
|
|
579
|
+
*/
|
|
518
580
|
}
|
|
519
581
|
else if (topic.startsWith(this.mqttTopic + '/bridge/logging')) {
|
|
582
|
+
// const data = JSON.parse(payload.toString());
|
|
583
|
+
// this.log.debug('classZigbee2MQTT=>Message bridge/logging', data);
|
|
520
584
|
}
|
|
521
585
|
else if (topic.startsWith(this.mqttTopic + '/bridge/config')) {
|
|
522
586
|
this.log.debug(`Message topic: ${topic}`);
|
|
587
|
+
// const data = JSON.parse(payload.toString());
|
|
588
|
+
// this.log.debug('classZigbee2MQTT=>Message bridge/config', data);
|
|
523
589
|
}
|
|
524
590
|
else if (topic.startsWith(this.mqttTopic + '/bridge/definitions')) {
|
|
525
591
|
this.log.debug(`Message topic: ${topic}`);
|
|
592
|
+
// const data = JSON.parse(payload.toString());
|
|
593
|
+
// this.log.debug('classZigbee2MQTT=>Message bridge/definitions', data);
|
|
526
594
|
}
|
|
527
595
|
else if (topic.startsWith(this.mqttTopic + '/bridge')) {
|
|
528
596
|
this.log.debug(`Message topic: ${topic}`);
|
|
597
|
+
// const data = JSON.parse(payload.toString());
|
|
598
|
+
// this.log.debug('classZigbee2MQTT=>Message bridge/definitions', data);
|
|
529
599
|
}
|
|
530
600
|
else {
|
|
531
601
|
let entity = topic.replace(this.mqttTopic + '/', '');
|
|
532
602
|
let service = '';
|
|
533
603
|
if (entity.search('/')) {
|
|
604
|
+
// set get availability or unknown TODO
|
|
534
605
|
const parts = entity.split('/');
|
|
535
606
|
entity = parts[0];
|
|
536
607
|
service = parts[1];
|
|
537
608
|
}
|
|
538
609
|
if (entity === 'Coordinator') {
|
|
539
|
-
const data = this.tryJsonParse(payload.toString());
|
|
610
|
+
const data = this.tryJsonParse(payload.toString()); // TODO crash on device rename
|
|
540
611
|
if (service === 'availability') {
|
|
541
612
|
if (data.state === 'online') {
|
|
542
613
|
this.log.debug(`Received ONLINE for ${id}Coordinator${rs}`, data);
|
|
@@ -547,7 +618,8 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
547
618
|
}
|
|
548
619
|
return;
|
|
549
620
|
}
|
|
550
|
-
|
|
621
|
+
// Log the first 1000 payloads
|
|
622
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ && this.loggedEntries < 1000) {
|
|
551
623
|
const logEntry = {
|
|
552
624
|
entity,
|
|
553
625
|
service,
|
|
@@ -584,7 +656,9 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
584
656
|
return this.z2mGroups.find((group) => group.friendly_name === name);
|
|
585
657
|
}
|
|
586
658
|
handleDeviceMessage(deviceIndex, entity, service, payload) {
|
|
659
|
+
// this.log.info(`classZigbee2MQTT=>handleDeviceMessage ${id}#${deviceIndex + 1}${rs} entity ${dn}${entity}${rs} service ${zb}${service}${rs} payload ${pl}${payload}${rs}`);
|
|
587
660
|
if (payload.length === 0 || payload === null) {
|
|
661
|
+
// this.log.warn(`handleDeviceMessage ${id}#${deviceIndex + 1}${rs} entity ${dn}${entity}${rs} service ${zb}${service}${rs} payload null`);
|
|
588
662
|
return;
|
|
589
663
|
}
|
|
590
664
|
const payloadString = payload.toString();
|
|
@@ -593,33 +667,41 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
593
667
|
data = this.tryJsonParse(payload.toString());
|
|
594
668
|
}
|
|
595
669
|
else {
|
|
596
|
-
data = { state: payloadString };
|
|
670
|
+
data = { state: payloadString }; // Only state for availability
|
|
597
671
|
}
|
|
598
672
|
if (service === 'availability') {
|
|
599
673
|
if (data.state === 'online') {
|
|
600
674
|
this.z2mDevices[deviceIndex].isAvailabilityEnabled = true;
|
|
601
675
|
this.z2mDevices[deviceIndex].isOnline = true;
|
|
676
|
+
// this.log.warn('handleDeviceMessage availability payload: ', data);
|
|
602
677
|
this.emit('availability', entity, true);
|
|
603
678
|
this.emit('ONLINE-' + entity);
|
|
604
679
|
}
|
|
605
680
|
else if (data.state === 'offline') {
|
|
606
681
|
this.z2mDevices[deviceIndex].isOnline = false;
|
|
682
|
+
// this.log.warn('handleDeviceMessage availability payload: ', data);
|
|
607
683
|
this.emit('availability', entity, false);
|
|
608
684
|
this.emit('OFFLINE-' + entity);
|
|
609
685
|
}
|
|
610
686
|
}
|
|
611
687
|
else if (service === 'get') {
|
|
688
|
+
// Do nothing
|
|
612
689
|
}
|
|
613
690
|
else if (service === 'set') {
|
|
691
|
+
// Do nothing
|
|
614
692
|
}
|
|
615
693
|
else if (service === undefined) {
|
|
694
|
+
// this.log.debug(`classZigbee2MQTT=>emitting message for device ${dn}${entity}${rs} payload ${pl}${payload}${rs}`);
|
|
616
695
|
this.emit('MESSAGE-' + entity, data);
|
|
617
696
|
}
|
|
618
697
|
else {
|
|
698
|
+
// MQTT output attribute type
|
|
619
699
|
}
|
|
620
700
|
}
|
|
621
701
|
handleGroupMessage(groupIndex, entity, service, payload) {
|
|
702
|
+
// this.log.info(`classZigbee2MQTT=>handleGroupMessage ${id}#${groupIndex + 1}${rs} entity ${gn}${entity}${rs} service ${zb}${service}${rs} payload ${pl}${payload}${rs}`);
|
|
622
703
|
if (payload.length === 0 || payload === null) {
|
|
704
|
+
// this.log.warn(`handleGroupMessage ${id}#${groupIndex + 1}${rs} entity ${gn}${entity}${rs} service ${zb}${service}${rs} payload null`);
|
|
623
705
|
return;
|
|
624
706
|
}
|
|
625
707
|
const payloadString = payload.toString();
|
|
@@ -628,7 +710,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
628
710
|
data = this.tryJsonParse(payload.toString());
|
|
629
711
|
}
|
|
630
712
|
else {
|
|
631
|
-
data = { state: payloadString };
|
|
713
|
+
data = { state: payloadString }; // Only state for availability
|
|
632
714
|
}
|
|
633
715
|
data['last_seen'] = new Date().toISOString();
|
|
634
716
|
if (service === 'availability') {
|
|
@@ -645,16 +727,29 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
645
727
|
}
|
|
646
728
|
}
|
|
647
729
|
else if (service === 'get') {
|
|
730
|
+
// Do nothing
|
|
648
731
|
}
|
|
649
732
|
else if (service === 'set') {
|
|
733
|
+
// Do nothing
|
|
650
734
|
}
|
|
651
735
|
else if (service === undefined) {
|
|
736
|
+
// this.log.debug(`classZigbee2MQTT=>emitting message for group ${gn}${entity}${rs} payload ${pl}${payload}${rs}`);
|
|
652
737
|
this.emit('MESSAGE-' + entity, data);
|
|
653
738
|
}
|
|
654
739
|
else {
|
|
740
|
+
// MQTT output attribute type
|
|
655
741
|
}
|
|
656
742
|
}
|
|
657
743
|
handleResponseNetworkmap(payload) {
|
|
744
|
+
/*
|
|
745
|
+
"routes": [
|
|
746
|
+
{
|
|
747
|
+
"destinationAddress": 31833,
|
|
748
|
+
"nextHop": 31833,
|
|
749
|
+
"status": "ACTIVE"
|
|
750
|
+
}
|
|
751
|
+
],
|
|
752
|
+
*/
|
|
658
753
|
const data = this.tryJsonParse(payload.toString());
|
|
659
754
|
const topology = data.data.value;
|
|
660
755
|
const lqi = (lqi) => {
|
|
@@ -704,7 +799,7 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
704
799
|
};
|
|
705
800
|
const timePassedSince = (lastSeen) => {
|
|
706
801
|
const now = Date.now();
|
|
707
|
-
const difference = now - lastSeen;
|
|
802
|
+
const difference = now - lastSeen; // difference in milliseconds
|
|
708
803
|
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
|
|
709
804
|
if (days > 0) {
|
|
710
805
|
return `${days} days ago`;
|
|
@@ -720,51 +815,116 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
720
815
|
const seconds = Math.floor((difference % (1000 * 60)) / 1000);
|
|
721
816
|
return `${seconds} seconds ago`;
|
|
722
817
|
};
|
|
723
|
-
if (this.log.logLevel === "debug")
|
|
818
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */)
|
|
724
819
|
this.writeBufferJSON('networkmap_' + data.data.type, payload);
|
|
725
820
|
if (data.data.type === 'graphviz') {
|
|
726
|
-
if (this.log.logLevel === "debug")
|
|
821
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */)
|
|
727
822
|
this.writeFile('networkmap_' + data.data.type + '.txt', data.data.value);
|
|
728
823
|
}
|
|
729
824
|
if (data.data.type === 'plantuml') {
|
|
730
|
-
if (this.log.logLevel === "debug")
|
|
825
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */)
|
|
731
826
|
this.writeFile('networkmap_' + data.data.type + '.txt', data.data.value);
|
|
732
827
|
}
|
|
733
828
|
if (data.data.type === 'raw') {
|
|
829
|
+
// Log nodes with links
|
|
734
830
|
this.log.warn('Network map nodes:');
|
|
735
831
|
topology.nodes.sort((a, b) => a.friendlyName.localeCompare(b.friendlyName));
|
|
736
832
|
topology.nodes.forEach((node, index) => {
|
|
737
833
|
this.log.debug(`Node [${index.toString().padStart(3, ' ')}] ${node.type === 'EndDevice' ? ign : node.type === 'Router' ? idn : '\x1b[48;5;1m\x1b[38;5;255m'}${node.friendlyName}${rs}${db} addr: ${node.ieeeAddr}-0x${node.networkAddress.toString(16)} type: ${node.type} lastseen: ${timePassedSince(node.lastSeen)}`);
|
|
738
|
-
|
|
739
|
-
sourceLinks.
|
|
834
|
+
// SourceAddr
|
|
835
|
+
const sourceLinks = topology.links.filter((link) => link.sourceIeeeAddr === node.ieeeAddr); // Filter
|
|
836
|
+
sourceLinks.sort((a, b) => a.lqi - b.lqi); // Sort by lqi
|
|
740
837
|
sourceLinks.forEach((link, index) => {
|
|
838
|
+
// const targetNode = topology.nodes.find((node) => node.ieeeAddr === link.target.ieeeAddr);
|
|
741
839
|
this.log.debug(` link [${index.toString().padStart(4, ' ')}] lqi: ${lqi(link.lqi)} depth: ${depth(link.depth)} relation: ${relationship(link.relationship)} > > > ${friendlyName(link.target.ieeeAddr)}`);
|
|
742
840
|
});
|
|
743
|
-
|
|
744
|
-
targetLinks.
|
|
841
|
+
// TargetAddr
|
|
842
|
+
const targetLinks = topology.links.filter((link) => link.targetIeeeAddr === node.ieeeAddr); // Filter
|
|
843
|
+
targetLinks.sort((a, b) => a.lqi - b.lqi); // Sort by lqi
|
|
745
844
|
targetLinks.forEach((link, index) => {
|
|
845
|
+
// const sourceNode = topology.nodes.find((node) => node.ieeeAddr === link.source.ieeeAddr);
|
|
746
846
|
this.log.debug(` link [${index.toString().padStart(4, ' ')}] lqi: ${lqi(link.lqi)} depth: ${depth(link.depth)} relation: ${relationship(link.relationship)} < < < ${friendlyName(link.source.ieeeAddr)}`);
|
|
747
847
|
});
|
|
748
848
|
});
|
|
849
|
+
// Log links
|
|
850
|
+
/*
|
|
851
|
+
this.log.warn('Network map links:');
|
|
852
|
+
map.links.sort((a, b) => a.sourceIeeeAddr.localeCompare(b.sourceIeeeAddr));
|
|
853
|
+
map.links.forEach( (link, index) => {
|
|
854
|
+
const sourceNode = map.nodes.find(node => node.ieeeAddr === link.source.ieeeAddr);
|
|
855
|
+
assert(sourceNode, `${wr}NwkAddr error node ${link.sourceIeeeAddr} not found${db}`);
|
|
856
|
+
const targetNode = map.nodes.find(node => node.ieeeAddr === link.target.ieeeAddr);
|
|
857
|
+
assert(targetNode, `${wr}NwkAddr error node ${link.targetIeeeAddr} not found${db}`);
|
|
858
|
+
this.log.debug(`- link[${index}]: ${link.source.ieeeAddr}-${link.source.networkAddress.toString(16)} (${sourceNode?.friendlyName})
|
|
859
|
+
Lqi: ${link.lqi} Depth: ${link.depth} Relation: ${link.relationship} => ${link.target.ieeeAddr}-${link.target.networkAddress.toString(16)} (${targetNode?.friendlyName})`);
|
|
860
|
+
} );
|
|
861
|
+
*/
|
|
749
862
|
}
|
|
750
863
|
}
|
|
751
864
|
handleResponseDeviceRename(payload) {
|
|
865
|
+
/*
|
|
866
|
+
{
|
|
867
|
+
data: {
|
|
868
|
+
from: '0xcc86ecfffe4e9d25',
|
|
869
|
+
homeassistant_rename: false,
|
|
870
|
+
to: 'Double switch'
|
|
871
|
+
},
|
|
872
|
+
status: 'ok',
|
|
873
|
+
transaction: 'smeo0-8'
|
|
874
|
+
}
|
|
875
|
+
*/
|
|
752
876
|
const json = this.tryJsonParse(payload.toString());
|
|
753
877
|
this.log.debug(`handleResponseDeviceRename from ${json.data.from} to ${json.data.to} status ${json.status}`);
|
|
754
878
|
const device = this.z2mDevices.find((device) => device.friendly_name === json.data.to);
|
|
755
879
|
this.emit('device_rename', device?.ieee_address, json.data.from, json.data.to);
|
|
756
880
|
}
|
|
757
881
|
handleResponseDeviceRemove(payload) {
|
|
882
|
+
/*
|
|
883
|
+
{
|
|
884
|
+
data: { block: false, force: false, id: 'Presence sensor' },
|
|
885
|
+
status: 'ok',
|
|
886
|
+
transaction: 'bet01-20'
|
|
887
|
+
}
|
|
888
|
+
*/
|
|
758
889
|
const json = this.tryJsonParse(payload.toString());
|
|
759
890
|
this.log.debug(`handleResponseDeviceRemove name ${json.data.id} status ${json.status} block ${json.data.block} force ${json.data.force}`);
|
|
760
891
|
this.emit('device_remove', json.data.id, json.status, json.data.block, json.data.force);
|
|
761
892
|
}
|
|
762
893
|
handleResponseDeviceOptions(payload) {
|
|
894
|
+
/*
|
|
895
|
+
{
|
|
896
|
+
data: {
|
|
897
|
+
from: {
|
|
898
|
+
color_sync: false,
|
|
899
|
+
legacy: false,
|
|
900
|
+
state_action: false,
|
|
901
|
+
transition: 0
|
|
902
|
+
},
|
|
903
|
+
id: '0xa4c1388ad0ebb0a6',
|
|
904
|
+
restart_required: false,
|
|
905
|
+
to: {
|
|
906
|
+
color_sync: false,
|
|
907
|
+
legacy: false,
|
|
908
|
+
state_action: false,
|
|
909
|
+
transition: 0
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
status: 'ok',
|
|
913
|
+
transaction: '8j6s7-3'
|
|
914
|
+
}
|
|
915
|
+
*/
|
|
763
916
|
const json = this.tryJsonParse(payload.toString());
|
|
764
917
|
this.log.debug(`handleResponseDeviceOptions ieee_address ${json.data.id} status ${json.status} from ${json.data.from} to ${json.data.to}`);
|
|
765
918
|
this.emit('device_options', json.data.id, json.status, json.data.from, json.data.to);
|
|
766
919
|
}
|
|
767
920
|
handleResponseGroupAdd(payload) {
|
|
921
|
+
/*
|
|
922
|
+
{
|
|
923
|
+
data: { friendly_name: 'Test', id: 7 },
|
|
924
|
+
status: 'ok',
|
|
925
|
+
transaction: '8j6s7-9'
|
|
926
|
+
}
|
|
927
|
+
*/
|
|
768
928
|
const json = this.tryJsonParse(payload.toString());
|
|
769
929
|
this.log.debug(`handleResponseGroupAdd() friendly_name ${json.data.friendly_name} id ${json.data.id} status ${json.status}`);
|
|
770
930
|
if (json.status === 'ok') {
|
|
@@ -772,6 +932,13 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
772
932
|
}
|
|
773
933
|
}
|
|
774
934
|
handleResponseGroupRemove(payload) {
|
|
935
|
+
/*
|
|
936
|
+
{
|
|
937
|
+
data: { force: false, id: 'Test' },
|
|
938
|
+
status: 'ok',
|
|
939
|
+
transaction: '8j6s7-10'
|
|
940
|
+
}
|
|
941
|
+
*/
|
|
775
942
|
const json = this.tryJsonParse(payload.toString());
|
|
776
943
|
this.log.debug(`handleResponseGroupRemove() friendly_name ${json.data.id} status ${json.status}`);
|
|
777
944
|
if (json.status === 'ok') {
|
|
@@ -779,6 +946,13 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
779
946
|
}
|
|
780
947
|
}
|
|
781
948
|
handleResponseGroupRename(payload) {
|
|
949
|
+
/*
|
|
950
|
+
{
|
|
951
|
+
data: { from: 'Test2', homeassistant_rename: false, to: 'Test' },
|
|
952
|
+
status: 'ok',
|
|
953
|
+
transaction: '0r51l-15'
|
|
954
|
+
}
|
|
955
|
+
*/
|
|
782
956
|
const json = this.tryJsonParse(payload.toString());
|
|
783
957
|
this.log.debug(`handleResponseGroupRename() from ${json.data.from} to ${json.data.to} status ${json.status}`);
|
|
784
958
|
if (json.status === 'ok') {
|
|
@@ -786,6 +960,13 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
786
960
|
}
|
|
787
961
|
}
|
|
788
962
|
handleResponseGroupAddMember(payload) {
|
|
963
|
+
/*
|
|
964
|
+
{
|
|
965
|
+
data: { device: '0xa4c1388ad0ebb0a6/1', group: 'Test2' },
|
|
966
|
+
status: 'ok',
|
|
967
|
+
transaction: '0r51l-7'
|
|
968
|
+
}
|
|
969
|
+
*/
|
|
789
970
|
const json = this.tryJsonParse(payload.toString());
|
|
790
971
|
this.log.debug(`handleResponseGroupAddMembers() add to group friendly_name ${json.data.group} device ieee_address ${json.data.device} status ${json.status}`);
|
|
791
972
|
if (json.status === 'ok' && json.data.device && json.data.device.includes('/')) {
|
|
@@ -793,6 +974,13 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
793
974
|
}
|
|
794
975
|
}
|
|
795
976
|
handleResponseGroupRemoveMember(payload) {
|
|
977
|
+
/*
|
|
978
|
+
{
|
|
979
|
+
data: { device: 'Gledopto RGBCTT light', group: 'Test2' },
|
|
980
|
+
status: 'ok',
|
|
981
|
+
transaction: '0r51l-10'
|
|
982
|
+
}
|
|
983
|
+
*/
|
|
796
984
|
const json = this.tryJsonParse(payload.toString());
|
|
797
985
|
this.log.debug(`handleResponseGroupRemoveMember() remove from group friendly_name ${json.data.group} device friendly_name ${json.data.device} status ${json.status}`);
|
|
798
986
|
if (json.status === 'ok') {
|
|
@@ -800,6 +988,13 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
800
988
|
}
|
|
801
989
|
}
|
|
802
990
|
handleResponsePermitJoin(payload) {
|
|
991
|
+
/*
|
|
992
|
+
{
|
|
993
|
+
data: { device?: 'Coordinator', time: 254, value: true },
|
|
994
|
+
status: 'ok',
|
|
995
|
+
transaction: 'adeis-5'
|
|
996
|
+
}
|
|
997
|
+
*/
|
|
803
998
|
const json = this.tryJsonParse(payload.toString());
|
|
804
999
|
this.log.debug(`handleResponsePermitJoin() device: ${json.data.device ? json.data.device : 'All'} time: ${json.data.time} value: ${json.data.value} status: ${json.status}`);
|
|
805
1000
|
if (json.status === 'ok') {
|
|
@@ -813,23 +1008,75 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
813
1008
|
this.log.error('handleEvent() undefined type', json);
|
|
814
1009
|
break;
|
|
815
1010
|
case 'device_leave':
|
|
1011
|
+
/*
|
|
1012
|
+
{
|
|
1013
|
+
data: { friendly_name: 'Light sensor', ieee_address: '0x54ef44100085c321' },
|
|
1014
|
+
type: 'device_leave'
|
|
1015
|
+
}
|
|
1016
|
+
*/
|
|
816
1017
|
this.log.debug(`handleEvent() type: device_leave name: ${json.data.friendly_name} address: ${json.data.ieee_address}`);
|
|
817
1018
|
this.emit('device_leave', json.data.friendly_name, json.data.ieee_address);
|
|
818
1019
|
break;
|
|
819
1020
|
case 'device_joined':
|
|
1021
|
+
/*
|
|
1022
|
+
{
|
|
1023
|
+
data: {
|
|
1024
|
+
friendly_name: 'Kitchen Dishwasher water leak sensor',
|
|
1025
|
+
ieee_address: '0x00158d0007c2b057'
|
|
1026
|
+
},
|
|
1027
|
+
type: 'device_joined'
|
|
1028
|
+
}
|
|
1029
|
+
*/
|
|
820
1030
|
this.log.debug(`handleEvent() type: device_joined name: ${json.data.friendly_name} address: ${json.data.ieee_address}`);
|
|
821
1031
|
this.emit('device_joined', json.data.friendly_name, json.data.ieee_address);
|
|
822
1032
|
break;
|
|
823
1033
|
case 'device_announce':
|
|
1034
|
+
/*
|
|
1035
|
+
{
|
|
1036
|
+
data: {
|
|
1037
|
+
friendly_name: 'Kitchen Sink water leak sensor',
|
|
1038
|
+
ieee_address: '0x00158d0008f1099b'
|
|
1039
|
+
},
|
|
1040
|
+
type: 'device_announce'
|
|
1041
|
+
}
|
|
1042
|
+
*/
|
|
824
1043
|
this.log.debug(`handleEvent() type: device_announce name: ${json.data.friendly_name} address: ${json.data.ieee_address}`);
|
|
825
1044
|
this.emit('device_announce', json.data.friendly_name, json.data.ieee_address);
|
|
826
1045
|
break;
|
|
827
1046
|
case 'device_interview':
|
|
1047
|
+
/*
|
|
1048
|
+
{
|
|
1049
|
+
data: {
|
|
1050
|
+
friendly_name: 'Kitchen Sink water leak sensor',
|
|
1051
|
+
ieee_address: '0x00158d0008f1099b',
|
|
1052
|
+
status: 'started'
|
|
1053
|
+
},
|
|
1054
|
+
type: 'device_interview'
|
|
1055
|
+
}
|
|
1056
|
+
{
|
|
1057
|
+
data: {
|
|
1058
|
+
definition: {
|
|
1059
|
+
description: 'Aqara water leak sensor',
|
|
1060
|
+
exposes: [Array],
|
|
1061
|
+
model: 'SJCGQ11LM',
|
|
1062
|
+
options: [Array],
|
|
1063
|
+
supports_ota: false,
|
|
1064
|
+
vendor: 'Xiaomi'
|
|
1065
|
+
},
|
|
1066
|
+
friendly_name: 'Kitchen Sink water leak sensor',
|
|
1067
|
+
ieee_address: '0x00158d0008f1099b',
|
|
1068
|
+
status: 'successful',
|
|
1069
|
+
supported: true
|
|
1070
|
+
},
|
|
1071
|
+
type: 'device_interview'
|
|
1072
|
+
}
|
|
1073
|
+
*/
|
|
828
1074
|
this.log.debug(`handleEvent() type: device_interview name: ${json.data.friendly_name} address: ${json.data.ieee_address} status: ${json.data.status} supported: ${json.data.supported}`);
|
|
829
1075
|
this.emit('device_interview', json.data.friendly_name, json.data.ieee_address, json.data.status, json.data.supported);
|
|
830
1076
|
break;
|
|
831
1077
|
}
|
|
832
1078
|
}
|
|
1079
|
+
// Function to read JSON config from a file
|
|
833
1080
|
readConfig(filename) {
|
|
834
1081
|
this.log.debug(`Reading config from ${filename}`);
|
|
835
1082
|
try {
|
|
@@ -842,6 +1089,8 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
842
1089
|
return null;
|
|
843
1090
|
}
|
|
844
1091
|
}
|
|
1092
|
+
// Function to write JSON config to a file
|
|
1093
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
845
1094
|
writeConfig(filename, data) {
|
|
846
1095
|
this.log.debug(`Writing config to ${filename}`);
|
|
847
1096
|
try {
|
|
@@ -969,3 +1218,4 @@ export class Zigbee2MQTT extends EventEmitter {
|
|
|
969
1218
|
});
|
|
970
1219
|
}
|
|
971
1220
|
}
|
|
1221
|
+
//# sourceMappingURL=zigbee2mqtt.js.map
|