iobroker.zigbee 1.8.0 → 1.8.1
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 +1 -1
- package/io-package.json +2 -2
- package/lib/binding.js +9 -13
- package/lib/colors.js +148 -145
- package/lib/commands.js +15 -13
- package/lib/developer.js +3 -5
- package/lib/devices.js +29 -30
- package/lib/exposes.js +60 -31
- package/lib/groups.js +32 -33
- package/lib/json.js +2 -2
- package/lib/networkmap.js +0 -1
- package/lib/ota.js +11 -3
- package/lib/rgb.js +72 -33
- package/lib/seriallist.js +9 -9
- package/lib/states.js +48 -49
- package/lib/statescontroller.js +44 -27
- package/lib/utils.js +3 -3
- package/lib/zbDeviceAvailability.js +26 -25
- package/lib/zbDeviceConfigure.js +2 -2
- package/lib/zigbeecontroller.js +22 -8
- package/main.js +17 -17
- package/package.json +1 -1
package/lib/statescontroller.js
CHANGED
|
@@ -68,7 +68,7 @@ class StatesController extends EventEmitter {
|
|
|
68
68
|
this.debugDevices = [];
|
|
69
69
|
this.adapter.getState(`${this.adapter.namespace}.info.debugmessages`, (err, state) => {
|
|
70
70
|
if (state) {
|
|
71
|
-
if (typeof
|
|
71
|
+
if (typeof state.val === 'string' && state.val.length > 2) {
|
|
72
72
|
this.debugDevices = state.val.split(';');
|
|
73
73
|
}
|
|
74
74
|
this.info(`debug devices set to ${JSON.stringify(this.debugDevices)}`);
|
|
@@ -100,7 +100,7 @@ class StatesController extends EventEmitter {
|
|
|
100
100
|
this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
onStateChange(id, state){
|
|
103
|
+
onStateChange(id, state) {
|
|
104
104
|
if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
@@ -112,7 +112,7 @@ class StatesController extends EventEmitter {
|
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
if (id.endsWith('debugmessages')) {
|
|
115
|
-
if
|
|
115
|
+
if (typeof state.val === 'string' && state.val.length > 2) {
|
|
116
116
|
this.debugDevices = state.val.split(';');
|
|
117
117
|
|
|
118
118
|
} else {
|
|
@@ -121,7 +121,7 @@ class StatesController extends EventEmitter {
|
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
for (const addressPart of this.debugDevices) {
|
|
124
|
-
if (typeof
|
|
124
|
+
if (typeof id === 'string' && id.includes(addressPart)) {
|
|
125
125
|
this.warn(`ELEVATED: User stateChange ${id} ${JSON.stringify(state)}`);
|
|
126
126
|
break;
|
|
127
127
|
}
|
|
@@ -159,7 +159,7 @@ class StatesController extends EventEmitter {
|
|
|
159
159
|
async collectOptions(devId, model, callback) {
|
|
160
160
|
const result = {};
|
|
161
161
|
// find model states for options and get it values
|
|
162
|
-
const devStates = await this.getDevStates('0x'+devId, model);
|
|
162
|
+
const devStates = await this.getDevStates('0x' + devId, model);
|
|
163
163
|
if (!devStates) {
|
|
164
164
|
callback(result);
|
|
165
165
|
return;
|
|
@@ -202,11 +202,9 @@ class StatesController extends EventEmitter {
|
|
|
202
202
|
} else {
|
|
203
203
|
stateModel = statesMapping.findModel(model);
|
|
204
204
|
if (!stateModel) {
|
|
205
|
-
if (knownUndefinedDevices[deviceId])
|
|
206
|
-
{
|
|
205
|
+
if (knownUndefinedDevices[deviceId]) {
|
|
207
206
|
knownUndefinedDevices[deviceId]++;
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
207
|
+
} else {
|
|
210
208
|
knownUndefinedDevices[deviceId] = 1;
|
|
211
209
|
this.error(`Device ${deviceId} "${model}" not described in statesMapping.`);
|
|
212
210
|
}
|
|
@@ -233,7 +231,7 @@ class StatesController extends EventEmitter {
|
|
|
233
231
|
if (this.debugDevices === undefined) this.getDebugDevices();
|
|
234
232
|
this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
235
233
|
for (const addressPart of this.debugDevices) {
|
|
236
|
-
if (typeof
|
|
234
|
+
if (typeof deviceId === 'string' && deviceId.includes(addressPart)) {
|
|
237
235
|
this.warn(`ELEVATED Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
238
236
|
break;
|
|
239
237
|
}
|
|
@@ -263,8 +261,7 @@ class StatesController extends EventEmitter {
|
|
|
263
261
|
if (res) {
|
|
264
262
|
stateList = stateList.concat(res);
|
|
265
263
|
}
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
264
|
+
} else {
|
|
268
265
|
this.warn(`publish from State - LinkedState is not a function ${JSON.stringify(linkedFunct)}`);
|
|
269
266
|
}
|
|
270
267
|
} catch (e) {
|
|
@@ -293,7 +290,7 @@ class StatesController extends EventEmitter {
|
|
|
293
290
|
}
|
|
294
291
|
|
|
295
292
|
setDeviceActivated(id, active) {
|
|
296
|
-
this.adapter.extendObject(id, {common: {deactivated: active
|
|
293
|
+
this.adapter.extendObject(id, {common: {deactivated: active}});
|
|
297
294
|
}
|
|
298
295
|
|
|
299
296
|
storeDeviceName(id, name) {
|
|
@@ -344,7 +341,9 @@ class StatesController extends EventEmitter {
|
|
|
344
341
|
if (arr[1] === undefined) {
|
|
345
342
|
this.warn(`unable to extract id from state ${statename}`);
|
|
346
343
|
const idx = statename.lastIndexOf('.');
|
|
347
|
-
if (idx > -1)
|
|
344
|
+
if (idx > -1) {
|
|
345
|
+
statename = statename.slice(idx + 1);
|
|
346
|
+
}
|
|
348
347
|
} else {
|
|
349
348
|
statename = arr[1];
|
|
350
349
|
}
|
|
@@ -439,23 +438,23 @@ class StatesController extends EventEmitter {
|
|
|
439
438
|
|
|
440
439
|
// only change object when any common property has changed
|
|
441
440
|
if (hasChanges) {
|
|
442
|
-
this.adapter.extendObject(id, {type: 'state', common: new_common, native: {}
|
|
441
|
+
this.adapter.extendObject(id, {type: 'state', common: new_common, native: {}}, () =>
|
|
443
442
|
value !== undefined && this.setState_typed(id, value, true, stobj ? stobj.common.type : new_common.type));
|
|
444
443
|
} else if (value !== undefined) {
|
|
445
444
|
this.setState_typed(id, value, true, stobj.common.type);
|
|
446
445
|
}
|
|
447
446
|
|
|
448
447
|
});
|
|
448
|
+
} else {
|
|
449
|
+
this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
449
450
|
}
|
|
450
|
-
else this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
451
451
|
} else {
|
|
452
452
|
this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
|
|
453
453
|
}
|
|
454
454
|
});
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
-
setState_typed(id, value, ack, type, callback)
|
|
458
|
-
{
|
|
457
|
+
setState_typed(id, value, ack, type, callback) {
|
|
459
458
|
// never set a null or undefined value
|
|
460
459
|
if (value === null || value === undefined) return;
|
|
461
460
|
if (!type) {
|
|
@@ -475,10 +474,14 @@ class StatesController extends EventEmitter {
|
|
|
475
474
|
switch (type) {
|
|
476
475
|
case 'number':
|
|
477
476
|
value = parseFloat(value);
|
|
478
|
-
if (isNaN
|
|
477
|
+
if (isNaN(value)) {
|
|
478
|
+
value = 0;
|
|
479
|
+
}
|
|
479
480
|
break;
|
|
480
481
|
case 'string':
|
|
481
|
-
case 'text':
|
|
482
|
+
case 'text':
|
|
483
|
+
value = JSON.stringify(value);
|
|
484
|
+
break;
|
|
482
485
|
case 'boolean': {
|
|
483
486
|
if (typeof value == 'number') {
|
|
484
487
|
value = value !== 0;
|
|
@@ -522,11 +525,24 @@ class StatesController extends EventEmitter {
|
|
|
522
525
|
this.adapter.setObjectNotExists(id, {
|
|
523
526
|
type: 'device',
|
|
524
527
|
// actually this is an error, so device.common has no attribute type. It must be in native part
|
|
525
|
-
common: {
|
|
528
|
+
common: {
|
|
529
|
+
name: __dev_name,
|
|
530
|
+
type: model,
|
|
531
|
+
icon,
|
|
532
|
+
color: null,
|
|
533
|
+
statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
|
|
534
|
+
},
|
|
526
535
|
native: {id: dev_id}
|
|
527
536
|
}, () => {
|
|
528
537
|
// update type and icon
|
|
529
|
-
this.adapter.extendObject(id, {
|
|
538
|
+
this.adapter.extendObject(id, {
|
|
539
|
+
common: {
|
|
540
|
+
type: model,
|
|
541
|
+
icon,
|
|
542
|
+
color: null,
|
|
543
|
+
statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
|
|
544
|
+
}
|
|
545
|
+
}, callback);
|
|
530
546
|
});
|
|
531
547
|
}
|
|
532
548
|
|
|
@@ -599,10 +615,10 @@ class StatesController extends EventEmitter {
|
|
|
599
615
|
|
|
600
616
|
async publishToState(devId, model, payload) {
|
|
601
617
|
const devStates = await this.getDevStates(`0x${devId}`, model);
|
|
602
|
-
let has_debug=false;
|
|
618
|
+
let has_debug = false;
|
|
603
619
|
if (this.debugDevices === undefined) this.getDebugDevices();
|
|
604
620
|
for (const addressPart of this.debugDevices) {
|
|
605
|
-
if (typeof
|
|
621
|
+
if (typeof devId === 'string' && devId.includes(addressPart)) {
|
|
606
622
|
if (payload.hasOwnProperty('msg_from_zigbee')) break;
|
|
607
623
|
this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
|
|
608
624
|
has_debug = true;
|
|
@@ -646,16 +662,17 @@ class StatesController extends EventEmitter {
|
|
|
646
662
|
min: statedesc.min,
|
|
647
663
|
max: statedesc.max,
|
|
648
664
|
};
|
|
665
|
+
|
|
649
666
|
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
650
667
|
stateID = `${stateID}.${value.stateid}`;
|
|
651
668
|
if (value.hasOwnProperty('unit')) {
|
|
652
669
|
common.unit = value.unit;
|
|
653
670
|
}
|
|
654
|
-
common.name =
|
|
655
|
-
common.role =
|
|
671
|
+
common.name = value.name ? value.name : value.stateid;
|
|
672
|
+
common.role = value.role ? `value.${value.role}` : 'number';
|
|
656
673
|
value = value.value;
|
|
657
|
-
|
|
658
674
|
}
|
|
675
|
+
|
|
659
676
|
// if needs to return value to back after timeout
|
|
660
677
|
if (statedesc.isEvent) {
|
|
661
678
|
this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, !value);
|
package/lib/utils.js
CHANGED
|
@@ -62,7 +62,7 @@ function bytesArrayToWordArray(ba) {
|
|
|
62
62
|
// If smaller, it is assumed to be mired.
|
|
63
63
|
function toMired(t) {
|
|
64
64
|
let miredValue = t;
|
|
65
|
-
if (t > 1000){
|
|
65
|
+
if (t > 1000) {
|
|
66
66
|
miredValue = miredKelvinConversion(t);
|
|
67
67
|
}
|
|
68
68
|
return miredValue;
|
|
@@ -80,7 +80,7 @@ function miredKelvinConversion(t) {
|
|
|
80
80
|
*/
|
|
81
81
|
function decimalToHex(decimal, padding) {
|
|
82
82
|
let hex = Number(decimal).toString(16);
|
|
83
|
-
padding = typeof
|
|
83
|
+
padding = typeof padding === 'undefined' || padding === null ? 2 : padding;
|
|
84
84
|
|
|
85
85
|
while (hex.length < padding) {
|
|
86
86
|
hex = '0' + hex;
|
|
@@ -92,7 +92,7 @@ function decimalToHex(decimal, padding) {
|
|
|
92
92
|
function getZbId(adapterDevId) {
|
|
93
93
|
const idx = adapterDevId.indexOf('group');
|
|
94
94
|
if (idx > 0) {
|
|
95
|
-
return adapterDevId.substr(idx+6);
|
|
95
|
+
return adapterDevId.substr(idx + 6);
|
|
96
96
|
}
|
|
97
97
|
return `0x${adapterDevId.split('.')[2]}`;
|
|
98
98
|
}
|
|
@@ -20,7 +20,7 @@ const forcedPingable = [
|
|
|
20
20
|
// will result in warnings "illegal state x,y" or "illegal state h,s" for color
|
|
21
21
|
// and possibly sudden changes in value due to the support for color_temp
|
|
22
22
|
// in mired and Kelvin.
|
|
23
|
-
const toZigbeeCandidates = ['local_temperature','state', 'brightness']; //, 'color', 'color_temp'];
|
|
23
|
+
const toZigbeeCandidates = ['local_temperature', 'state', 'brightness']; //, 'color', 'color_temp'];
|
|
24
24
|
const Hours25 = 1000 * 60 * 60 * 25;
|
|
25
25
|
const MinAvailabilityTimeout = 300; // ping every 5 minutes with few devices
|
|
26
26
|
const MaxAvailabilityTimeout = 1800; // ping every 30 minutes with many devices;
|
|
@@ -65,7 +65,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
65
65
|
this.forced_ping = false;
|
|
66
66
|
}
|
|
67
67
|
if (typeof options.pingTimeout === 'number') {
|
|
68
|
-
this.availability_timeout = Math.min(60,options.pingTimeout);
|
|
68
|
+
this.availability_timeout = Math.min(60, options.pingTimeout);
|
|
69
69
|
}
|
|
70
70
|
if (typeof options.pingCount === 'number') {
|
|
71
71
|
this.max_ping = Math.min(2, options.pingCount);
|
|
@@ -74,8 +74,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
isPingable(device) {
|
|
77
|
-
if (this.active_ping)
|
|
78
|
-
{
|
|
77
|
+
if (this.active_ping) {
|
|
79
78
|
if (this.forced_ping && forcedPingable.find(d => d && d.hasOwnProperty('zigbeeModel') && d.zigbeeModel.includes(device.modelID))) {
|
|
80
79
|
return true;
|
|
81
80
|
}
|
|
@@ -105,10 +104,10 @@ class DeviceAvailability extends BaseExtension {
|
|
|
105
104
|
}
|
|
106
105
|
});
|
|
107
106
|
this.number_of_registered_devices++;
|
|
108
|
-
this.availability_timeout = Math.max(Math.min(this.number_of_registered_devices * AverageTimeBetweenPings,MaxAvailabilityTimeout
|
|
107
|
+
this.availability_timeout = Math.max(Math.min(this.number_of_registered_devices * AverageTimeBetweenPings, MaxAvailabilityTimeout), MinAvailabilityTimeout);
|
|
109
108
|
this.startDevicePingQueue.push({device, entity});
|
|
110
109
|
if (this.startDevicePingTimeout == null) {
|
|
111
|
-
this.startDevicePingTimeout = setTimeout(async() =>
|
|
110
|
+
this.startDevicePingTimeout = setTimeout(async () =>
|
|
112
111
|
await this.startDevicePing(), this.startDevicePingDelay);
|
|
113
112
|
}
|
|
114
113
|
}
|
|
@@ -125,14 +124,15 @@ class DeviceAvailability extends BaseExtension {
|
|
|
125
124
|
// this.warn(JSON.stringify(this));
|
|
126
125
|
this.startDevicePingTimeout = null;
|
|
127
126
|
const item = this.startDevicePingQueue.shift();
|
|
128
|
-
if (this.startDevicePingQueue.length >0) {
|
|
129
|
-
this.startDevicePingTimeout = setTimeout(async() =>
|
|
127
|
+
if (this.startDevicePingQueue.length > 0) {
|
|
128
|
+
this.startDevicePingTimeout = setTimeout(async () =>
|
|
130
129
|
await this.startDevicePing(), this.startDevicePingDelay);
|
|
131
130
|
}
|
|
132
131
|
if (item && item.hasOwnProperty('device')) {
|
|
133
132
|
this.handleIntervalPingable(item.device, item.entity);
|
|
134
133
|
}
|
|
135
134
|
}
|
|
135
|
+
|
|
136
136
|
async onZigbeeStarted() {
|
|
137
137
|
// As some devices are not checked for availability (e.g. battery powered devices)
|
|
138
138
|
// we mark these device as online by default.
|
|
@@ -155,7 +155,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
155
155
|
|
|
156
156
|
async handleIntervalPingable(device, entity) {
|
|
157
157
|
const ieeeAddr = device.ieeeAddr;
|
|
158
|
-
const resolvedEntity =
|
|
158
|
+
const resolvedEntity = entity ? entity : await this.zigbee.resolveEntity(ieeeAddr);
|
|
159
159
|
if (!resolvedEntity) {
|
|
160
160
|
this.debug(`Stop pinging '${ieeeAddr}' ${device.modelID}, device is not known anymore`);
|
|
161
161
|
return;
|
|
@@ -179,8 +179,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
182
|
+
} catch (error) {
|
|
184
183
|
this.sendError(error);
|
|
185
184
|
this.debug(`Exception in readState of '${device.ieeeAddr}' - error : '${error}'`);
|
|
186
185
|
// intentionally empty: Just present to ensure we cause no harm
|
|
@@ -202,7 +201,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
202
201
|
this.debug(`Failed to ping ${ieeeAddr} ${device.modelID} on ${pingCount} consecutive attempts`);
|
|
203
202
|
}
|
|
204
203
|
this.setTimerPingable(device, pingCount.failed);
|
|
205
|
-
this.ping_counters[device.ieeeAddr]= pingCount;
|
|
204
|
+
this.ping_counters[device.ieeeAddr] = pingCount;
|
|
206
205
|
} else {
|
|
207
206
|
this.warn(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
|
|
208
207
|
}
|
|
@@ -225,11 +224,13 @@ class DeviceAvailability extends BaseExtension {
|
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
setTimerPingable(device, factor) {
|
|
228
|
-
if (factor === undefined || factor < 1)
|
|
227
|
+
if (factor === undefined || factor < 1) {
|
|
228
|
+
factor = 1;
|
|
229
|
+
}
|
|
229
230
|
if (this.timers[device.ieeeAddr]) {
|
|
230
231
|
clearTimeout(this.timers[device.ieeeAddr]);
|
|
231
232
|
}
|
|
232
|
-
this.timers[device.ieeeAddr] = setTimeout(async() =>
|
|
233
|
+
this.timers[device.ieeeAddr] = setTimeout(async () =>
|
|
233
234
|
await this.handleIntervalPingable(device), utils.secondsToMilliseconds(this.availability_timeout * factor));
|
|
234
235
|
}
|
|
235
236
|
|
|
@@ -273,8 +274,8 @@ class DeviceAvailability extends BaseExtension {
|
|
|
273
274
|
const payload = {available: available};
|
|
274
275
|
this.debug(`Publish available for ${ieeeAddr} = ${available}`);
|
|
275
276
|
this.zigbee.emit('publish', ieeeAddr.substr(2), entity.mapped.model, payload);
|
|
276
|
-
this.debug(`Publish LQ for ${ieeeAddr} = ${(available ? 10: 0)}`);
|
|
277
|
-
this.zigbee.emit('publish', ieeeAddr.substr(2), entity.mapped.model, {
|
|
277
|
+
this.debug(`Publish LQ for ${ieeeAddr} = ${(available ? 10 : 0)}`);
|
|
278
|
+
this.zigbee.emit('publish', ieeeAddr.substr(2), entity.mapped.model, {linkquality: (available ? 10 : 0)});
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
281
|
}
|
|
@@ -293,7 +294,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
293
294
|
this.setTimerPingable(device, 1);
|
|
294
295
|
const pc = this.ping_counters[device.ieeeAddr];
|
|
295
296
|
if (pc == undefined) {
|
|
296
|
-
this.ping_counters[device.ieeeAddr] = {
|
|
297
|
+
this.ping_counters[device.ieeeAddr] = {failed: 0, reported: 0};
|
|
297
298
|
} else {
|
|
298
299
|
this.ping_counters[device.ieeeAddr].failed++;
|
|
299
300
|
}
|
|
@@ -301,14 +302,14 @@ class DeviceAvailability extends BaseExtension {
|
|
|
301
302
|
const online = this.state.hasOwnProperty(device.ieeeAddr) && this.state[device.ieeeAddr];
|
|
302
303
|
if (online && data.type === 'deviceAnnounce' && !utils.isIkeaTradfriDevice(device)) {
|
|
303
304
|
/**
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
305
|
+
* In case the device is powered off AND on within the availability timeout,
|
|
306
|
+
* zigbee2qmtt does not detect the device as offline (device is still marked online).
|
|
307
|
+
* When a device is turned on again the state could be out of sync.
|
|
308
|
+
* https://github.com/Koenkk/zigbee2mqtt/issues/1383#issuecomment-489412168
|
|
309
|
+
* endDeviceAnnce is typically send when a device comes online.
|
|
310
|
+
*
|
|
311
|
+
* This isn't needed for TRADFRI devices as they already send the state themself.
|
|
312
|
+
*/
|
|
312
313
|
this.onReconnect(device);
|
|
313
314
|
}
|
|
314
315
|
}
|
package/lib/zbDeviceConfigure.js
CHANGED
|
@@ -74,7 +74,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
onDeviceRemove(device){
|
|
77
|
+
onDeviceRemove(device) {
|
|
78
78
|
try {
|
|
79
79
|
if (this.configuring.has(device.ieeeAddr)) {
|
|
80
80
|
this.configuring.delete(device.ieeeAddr);
|
|
@@ -89,7 +89,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
onDeviceLeave(data, entity){
|
|
92
|
+
onDeviceLeave(data, entity) {
|
|
93
93
|
if (entity) {
|
|
94
94
|
this.onDeviceRemove(entity.device);
|
|
95
95
|
} else {
|
package/lib/zigbeecontroller.js
CHANGED
|
@@ -85,7 +85,12 @@ class ZigbeeController extends EventEmitter {
|
|
|
85
85
|
|
|
86
86
|
this.debug(`Using zigbee-herdsman with settings: ${JSON.stringify(herdsmanSettings)}`);
|
|
87
87
|
this.herdsman = new ZigbeeHerdsman.Controller(herdsmanSettings, this.adapter.log);
|
|
88
|
-
this.callExtensionMethod('setOptions', [{
|
|
88
|
+
this.callExtensionMethod('setOptions', [{
|
|
89
|
+
disableActivePing: options.disablePing,
|
|
90
|
+
disableForcedPing: false,
|
|
91
|
+
pingTimeout: 300,
|
|
92
|
+
pingCount: 3
|
|
93
|
+
}]);
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
// Start controller
|
|
@@ -108,11 +113,11 @@ class ZigbeeController extends EventEmitter {
|
|
|
108
113
|
|
|
109
114
|
// debug info from herdsman getNetworkParameters
|
|
110
115
|
const debNetworkParam = JSON.parse(JSON.stringify(await this.herdsman.getNetworkParameters()));
|
|
111
|
-
const extendedPanIDDebug =
|
|
116
|
+
const extendedPanIDDebug = typeof debNetworkParam.extendedPanID === 'string' ? debNetworkParam.extendedPanID.replace('0x', '') : debNetworkParam.extendedPanID;
|
|
112
117
|
|
|
113
118
|
let extPanIDDebug = '';
|
|
114
119
|
for (let i = extendedPanIDDebug.length - 1; i >= 0; i--) {
|
|
115
|
-
extPanIDDebug += extendedPanIDDebug[i-1];
|
|
120
|
+
extPanIDDebug += extendedPanIDDebug[i - 1];
|
|
116
121
|
extPanIDDebug += extendedPanIDDebug[i];
|
|
117
122
|
i--;
|
|
118
123
|
}
|
|
@@ -288,7 +293,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
288
293
|
}
|
|
289
294
|
|
|
290
295
|
async verifyGroupExists(id) {
|
|
291
|
-
const nid =
|
|
296
|
+
const nid = typeof id === 'number' ? id : parseInt(id);
|
|
292
297
|
let group = await this.herdsman.getGroupByID(nid);
|
|
293
298
|
if (!group) {
|
|
294
299
|
group = await this.herdsman.createGroup(nid);
|
|
@@ -308,10 +313,17 @@ class ZigbeeController extends EventEmitter {
|
|
|
308
313
|
if (group) {
|
|
309
314
|
const groupmembers = group.members;
|
|
310
315
|
for (const member of groupmembers) {
|
|
311
|
-
const epid =
|
|
316
|
+
const epid = member.ID ? member.ID : -1;
|
|
312
317
|
const nwk = member.deviceNetworkAddress;
|
|
313
318
|
const device = this.getDeviceByNetworkAddress(nwk);
|
|
314
|
-
if (device && device.ieeeAddr)
|
|
319
|
+
if (device && device.ieeeAddr) {
|
|
320
|
+
members.push({
|
|
321
|
+
ieee: device.ieeeAddr,
|
|
322
|
+
model: device.modelID,
|
|
323
|
+
epid,
|
|
324
|
+
ep: member
|
|
325
|
+
});
|
|
326
|
+
}
|
|
315
327
|
}
|
|
316
328
|
} else {
|
|
317
329
|
return undefined;
|
|
@@ -560,7 +572,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
560
572
|
if (entity && entity.mapped) {
|
|
561
573
|
this.callExtensionMethod(
|
|
562
574
|
'onZigbeeEvent',
|
|
563
|
-
[
|
|
575
|
+
[{'device': message.device, 'type': 'deviceAnnounce'}, entity ? entity.mapped : null]);
|
|
564
576
|
}
|
|
565
577
|
} catch (error) {
|
|
566
578
|
this.sendError(error);
|
|
@@ -568,7 +580,9 @@ class ZigbeeController extends EventEmitter {
|
|
|
568
580
|
}
|
|
569
581
|
|
|
570
582
|
this.emit('pairing', `Device '${friendlyName}' announced itself`);
|
|
571
|
-
if (!this.herdsman.getPermitJoin())
|
|
583
|
+
if (!this.herdsman.getPermitJoin()) {
|
|
584
|
+
this.callExtensionMethod('registerDevicePing', [message.device, entity]);
|
|
585
|
+
}
|
|
572
586
|
// if has modelID so can create device
|
|
573
587
|
if (entity.device && entity.device._modelID) {
|
|
574
588
|
entity.device.modelID = entity.device._modelID;
|
package/main.js
CHANGED
|
@@ -138,7 +138,7 @@ class Zigbee extends utils.Adapter {
|
|
|
138
138
|
if (error.code === undefined) {
|
|
139
139
|
let em = error.stack.match(/failed \((.+?)\) at/);
|
|
140
140
|
em = em || error.stack.match(/failed \((.+?)\)/);
|
|
141
|
-
this.log.error(`${message} no error code (${(em ? em[1]:'undefined')})`);
|
|
141
|
+
this.log.error(`${message} no error code (${(em ? em[1] : 'undefined')})`);
|
|
142
142
|
this.sendError(error, `${message} no error code`);
|
|
143
143
|
this.log.debug(`Stack trace for ${em}: ${error.stack}`);
|
|
144
144
|
return;
|
|
@@ -172,7 +172,7 @@ class Zigbee extends utils.Adapter {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
debugLog
|
|
175
|
+
debugLog(data, ...args) {
|
|
176
176
|
const message = (args) ? util.format(data, ...args) : data;
|
|
177
177
|
this.log.debug(message.slice(message.indexOf('zigbee-herdsman')));
|
|
178
178
|
}
|
|
@@ -250,8 +250,7 @@ class Zigbee extends utils.Adapter {
|
|
|
250
250
|
try {
|
|
251
251
|
const DebugIdentify = require('./debugidentify');
|
|
252
252
|
debugversion = DebugIdentify.ReportIdentifier();
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
253
|
+
} catch {
|
|
255
254
|
debugversion = ' npm ...';
|
|
256
255
|
}
|
|
257
256
|
|
|
@@ -297,7 +296,7 @@ class Zigbee extends utils.Adapter {
|
|
|
297
296
|
}
|
|
298
297
|
|
|
299
298
|
tryToReconnect() {
|
|
300
|
-
this.reconnectTimer = setTimeout(()=>{
|
|
299
|
+
this.reconnectTimer = setTimeout(() => {
|
|
301
300
|
if (this.config.port.includes('tcp://')) {
|
|
302
301
|
// Controller connect though Wi-Fi.
|
|
303
302
|
// Unlikely USB dongle, connection broken may only cause user unplugged the dongle,
|
|
@@ -414,9 +413,7 @@ class Zigbee extends utils.Adapter {
|
|
|
414
413
|
});
|
|
415
414
|
}
|
|
416
415
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
async onZigbeeEvent(type, entity, message){
|
|
416
|
+
async onZigbeeEvent(type, entity, message) {
|
|
420
417
|
this.log.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
|
|
421
418
|
const device = entity.device;
|
|
422
419
|
const mappedModel = entity.mapped;
|
|
@@ -499,7 +496,7 @@ class Zigbee extends utils.Adapter {
|
|
|
499
496
|
});
|
|
500
497
|
}
|
|
501
498
|
|
|
502
|
-
async publishFromState(deviceId, model, stateModel, stateList, options){
|
|
499
|
+
async publishFromState(deviceId, model, stateModel, stateList, options) {
|
|
503
500
|
let isGroup = false;
|
|
504
501
|
if (model === 'group') {
|
|
505
502
|
isGroup = true;
|
|
@@ -522,7 +519,7 @@ class Zigbee extends utils.Adapter {
|
|
|
522
519
|
try {
|
|
523
520
|
const json_value = JSON.parse(value);
|
|
524
521
|
const payload = {device: deviceId.replace('0x', ''), payload: json_value};
|
|
525
|
-
const result = await
|
|
522
|
+
const result = await this.sendPayload(payload);
|
|
526
523
|
if (result.hasOwnProperty('success') && result.success) {
|
|
527
524
|
this.acknowledgeState(deviceId, model, stateDesc, value);
|
|
528
525
|
}
|
|
@@ -635,8 +632,8 @@ class Zigbee extends utils.Adapter {
|
|
|
635
632
|
}
|
|
636
633
|
}
|
|
637
634
|
} catch (error) {
|
|
638
|
-
this.filterError(`Error ${error.code} on send command to ${deviceId}
|
|
639
|
-
|
|
635
|
+
this.filterError(`Error ${error.code} on send command to ${deviceId}.` +
|
|
636
|
+
` Error: ${error.stack}`, `Send command to ${deviceId} failed with`, error);
|
|
640
637
|
}
|
|
641
638
|
});
|
|
642
639
|
}
|
|
@@ -662,7 +659,10 @@ class Zigbee extends utils.Adapter {
|
|
|
662
659
|
} catch (e) {
|
|
663
660
|
this.log.error(`Unable to parse ${safeJsonStringify(payload)}: ${safeJsonStringify(e)}`);
|
|
664
661
|
this.sendError(e, `Unable to parse ${safeJsonStringify(payload)}: ${safeJsonStringify(e)}`);
|
|
665
|
-
return {
|
|
662
|
+
return {
|
|
663
|
+
success: false,
|
|
664
|
+
error: `Unable to parse ${safeJsonStringify(payload)}: ${safeJsonStringify(e)}`
|
|
665
|
+
};
|
|
666
666
|
}
|
|
667
667
|
} else if (typeof payload === 'object') {
|
|
668
668
|
payloadObj = payload;
|
|
@@ -672,7 +672,7 @@ class Zigbee extends utils.Adapter {
|
|
|
672
672
|
try {
|
|
673
673
|
const isDevice = !payload.device.includes('group_');
|
|
674
674
|
const stateList = [];
|
|
675
|
-
const devID = isDevice ? `0x${payload.device}
|
|
675
|
+
const devID = isDevice ? `0x${payload.device}` : parseInt(payload.device.replace('group_', ''));
|
|
676
676
|
|
|
677
677
|
const entity = await this.zbController.resolveEntity(devID);
|
|
678
678
|
if (!entity) {
|
|
@@ -713,8 +713,8 @@ class Zigbee extends utils.Adapter {
|
|
|
713
713
|
await this.publishFromState(`0x${payload.device}`, '', undefined, stateList, payload.options);
|
|
714
714
|
return {success: true};
|
|
715
715
|
} catch (error) {
|
|
716
|
-
this.filterError(`Error ${error.code} on send command to ${payload.device}
|
|
717
|
-
|
|
716
|
+
this.filterError(`Error ${error.code} on send command to ${payload.device}.` +
|
|
717
|
+
` Error: ${error.stack}`, `Send command to ${payload.device} failed with`, error);
|
|
718
718
|
return {success: false, error};
|
|
719
719
|
}
|
|
720
720
|
} catch (e) {
|
|
@@ -857,7 +857,7 @@ class Zigbee extends utils.Adapter {
|
|
|
857
857
|
}
|
|
858
858
|
if (data === 0) {
|
|
859
859
|
// set pairing mode off
|
|
860
|
-
this.setState('info.pairingMode', false,true);
|
|
860
|
+
this.setState('info.pairingMode', false, true);
|
|
861
861
|
_pairingMode = false;
|
|
862
862
|
}
|
|
863
863
|
if (data) {
|