iobroker.zigbee 1.6.0 → 1.6.12
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 +16 -2
- package/admin/adapter-settings.js +39 -3
- package/admin/admin.js +179 -27
- package/admin/img/14153905L.png +0 -0
- package/admin/img/81855.png +0 -0
- package/admin/img/HG06338.png +0 -0
- package/admin/img/R7060.png +0 -0
- package/admin/img/TI0001-cover.png +0 -0
- package/admin/img/WHD02.png +0 -0
- package/admin/img/ikea_E1812.png +0 -0
- package/admin/img/tuya_rb280.png +0 -0
- package/admin/index_m.html +141 -24
- package/admin/tab_m.html +170 -216
- package/admin/words.js +31 -30
- package/io-package.json +24 -1
- package/lib/commands.js +35 -4
- package/lib/developer.js +6 -2
- package/lib/devices.js +46 -1
- package/lib/exposes.js +7 -4
- package/lib/groups.js +28 -18
- package/lib/rgb.js +30 -0
- package/lib/states.js +64 -39
- package/lib/statescontroller.js +84 -22
- package/lib/utils.js +19 -0
- package/lib/zbBaseExtension.js +4 -3
- package/lib/zbDelayedAction.js +1 -0
- package/lib/zbDeviceAvailability.js +2 -1
- package/lib/zbDeviceConfigure.js +11 -7
- package/lib/zbDeviceEvent.js +6 -0
- package/lib/zigbeecontroller.js +95 -16
- package/main.js +47 -32
- package/package.json +4 -4
package/lib/statescontroller.js
CHANGED
|
@@ -4,6 +4,7 @@ const EventEmitter = require('events').EventEmitter;
|
|
|
4
4
|
const statesMapping = require('./devices');
|
|
5
5
|
const getAdId = require('./utils').getAdId;
|
|
6
6
|
const getZbId = require('./utils').getZbId;
|
|
7
|
+
var knownUndefinedDevices = {};
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class StatesController extends EventEmitter {
|
|
@@ -55,8 +56,20 @@ class StatesController extends EventEmitter {
|
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
});
|
|
58
|
-
|
|
59
|
+
this.adapter.setObject('info.undefinedDevices', {
|
|
60
|
+
'type': 'state',
|
|
61
|
+
'common': {
|
|
62
|
+
'name': 'Recorded undefined devices',
|
|
63
|
+
'role': '',
|
|
64
|
+
'type': 'string',
|
|
65
|
+
'read': true,
|
|
66
|
+
'write': false,
|
|
67
|
+
},
|
|
68
|
+
'native': {},
|
|
69
|
+
});
|
|
70
|
+
this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
|
|
59
71
|
}
|
|
72
|
+
|
|
60
73
|
onStateChange(id, state){
|
|
61
74
|
if (!this.adapter.zbController || !this.adapter.zbController.connected()) return;
|
|
62
75
|
if (this.debugDevices === undefined) this.getDebugDevices();
|
|
@@ -116,20 +129,25 @@ class StatesController extends EventEmitter {
|
|
|
116
129
|
return;
|
|
117
130
|
}
|
|
118
131
|
let cnt = 0;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
try {
|
|
133
|
+
const len = states.length;
|
|
134
|
+
states.forEach(statedesc => {
|
|
135
|
+
const id = this.adapter.namespace + '.' + devId + '.' + statedesc.id;
|
|
136
|
+
this.adapter.getState(id, (err, state) => {
|
|
137
|
+
cnt = cnt + 1;
|
|
138
|
+
if (!err && state) {
|
|
139
|
+
result[statedesc.id] = state.val;
|
|
140
|
+
}
|
|
141
|
+
if (cnt === len) {
|
|
142
|
+
callback(result);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
130
145
|
});
|
|
131
|
-
|
|
132
|
-
|
|
146
|
+
if (!len) callback(result);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
this.sendError(error);
|
|
149
|
+
this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
|
|
150
|
+
}
|
|
133
151
|
}
|
|
134
152
|
|
|
135
153
|
async getDevStates(deviceId, model) {
|
|
@@ -141,7 +159,15 @@ class StatesController extends EventEmitter {
|
|
|
141
159
|
} else {
|
|
142
160
|
stateModel = statesMapping.findModel(model);
|
|
143
161
|
if (!stateModel) {
|
|
144
|
-
|
|
162
|
+
if (knownUndefinedDevices[deviceId])
|
|
163
|
+
{
|
|
164
|
+
knownUndefinedDevices[deviceId]++;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
knownUndefinedDevices[deviceId] = 1;
|
|
168
|
+
this.error('Device ' + deviceId + ' "' + model + '" not described in statesMapping.');
|
|
169
|
+
}
|
|
170
|
+
this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
|
|
145
171
|
states = statesMapping.commonStates;
|
|
146
172
|
} else {
|
|
147
173
|
states = stateModel.states;
|
|
@@ -343,13 +369,10 @@ class StatesController extends EventEmitter {
|
|
|
343
369
|
// only change object when any common property has changed
|
|
344
370
|
if (hasChanges) {
|
|
345
371
|
this.adapter.extendObject(id, {type: 'state', common: new_common, native: {} }, () => {
|
|
346
|
-
value !== undefined && this.
|
|
372
|
+
value !== undefined && this.setState_typed(id, value, true, (stobj) ? stobj.common.type : new_common.type);
|
|
347
373
|
});
|
|
348
374
|
} else if (value !== undefined) {
|
|
349
|
-
|
|
350
|
-
this.adapter.setState(id, value, true);
|
|
351
|
-
} else this.warn('set state with object for id :' + JSON.stringify(id) + ' '+ JSON.stringify(value));
|
|
352
|
-
|
|
375
|
+
this.setState_typed(id, value, true, stobj.common.type);
|
|
353
376
|
}
|
|
354
377
|
|
|
355
378
|
});
|
|
@@ -359,6 +382,44 @@ class StatesController extends EventEmitter {
|
|
|
359
382
|
});
|
|
360
383
|
}
|
|
361
384
|
|
|
385
|
+
setState_typed(id, value, ack, type, callback)
|
|
386
|
+
{
|
|
387
|
+
// never set a null or undefined value
|
|
388
|
+
if (value === null || value === undefined) return;
|
|
389
|
+
if (!type) {
|
|
390
|
+
this.debug("SetState_typed called without type");
|
|
391
|
+
// identify datatype, recursively call this function with set datatype
|
|
392
|
+
this.adapter.getObject(id, (err, obj) => {
|
|
393
|
+
if (obj && obj.common)
|
|
394
|
+
this.setState_typed(id, value, ack, obj.common.type, callback);
|
|
395
|
+
else {
|
|
396
|
+
this.setState_typed(id, value, ack, 'noobj', callback);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (typeof value != type) {
|
|
402
|
+
this.debug("SetState_typed : converting " + JSON.stringify(value) + " for " + id + " from " + typeof value + " to " + type);
|
|
403
|
+
switch (type) {
|
|
404
|
+
case 'number':
|
|
405
|
+
value = parseFloat(value);
|
|
406
|
+
if (isNaN (value)) value = 0;
|
|
407
|
+
break;
|
|
408
|
+
case 'string':
|
|
409
|
+
case 'text': value = JSON.stringify(value); break;
|
|
410
|
+
case 'boolean':
|
|
411
|
+
if (typeof value == 'number') {
|
|
412
|
+
value = (value != 0);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
const sval = JSON.stringify(value).toLowerCase().trim();
|
|
416
|
+
value = (sval == 'true' || sval == 'yes' || sval == 'on');
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
this.adapter.setState(id, value, ack, callback);
|
|
421
|
+
}
|
|
422
|
+
|
|
362
423
|
updateDev(dev_id, dev_name, model, callback) {
|
|
363
424
|
const id = '' + dev_id;
|
|
364
425
|
const modelDesc = statesMapping.findModel(model);
|
|
@@ -432,6 +493,7 @@ class StatesController extends EventEmitter {
|
|
|
432
493
|
for (const addressPart of this.debugDevices) {
|
|
433
494
|
if (typeof(devId) == 'string' && devId.indexOf(addressPart) > -1)
|
|
434
495
|
{
|
|
496
|
+
if (payload.hasOwnProperty('msg_from_zigbee')) break;
|
|
435
497
|
this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
|
|
436
498
|
has_debug = true;
|
|
437
499
|
break;
|
|
@@ -454,10 +516,10 @@ class StatesController extends EventEmitter {
|
|
|
454
516
|
value = payload[statedesc.prop || statedesc.id];
|
|
455
517
|
}
|
|
456
518
|
// checking value
|
|
457
|
-
if (value === undefined) continue;
|
|
519
|
+
if (value === undefined || value === null) continue;
|
|
458
520
|
let stateID = statedesc.id;
|
|
459
521
|
|
|
460
|
-
if (has_debug) {
|
|
522
|
+
if (has_debug && statedesc.id != 'msg_from_zigbee') {
|
|
461
523
|
this.warn(`ELEVATED publishToState: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
|
|
462
524
|
}
|
|
463
525
|
|
package/lib/utils.js
CHANGED
|
@@ -114,6 +114,24 @@ const forceEndDevice = flatten(
|
|
|
114
114
|
const xiaomiManufacturerID = [4151, 4447];
|
|
115
115
|
const ikeaTradfriManufacturerID = [4476];
|
|
116
116
|
|
|
117
|
+
function sanitizeImageParameter(parameter) {
|
|
118
|
+
const replaceByDash = [/\?/g, /&/g, /[^a-z\d\-_./:]/gi, /[/]/gi];
|
|
119
|
+
let sanitized = parameter;
|
|
120
|
+
replaceByDash.forEach((r) => sanitized = sanitized.replace(r, '-'));
|
|
121
|
+
return sanitized;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getDeviceIcon(definition) {
|
|
125
|
+
let icon = definition.icon;
|
|
126
|
+
if (icon) {
|
|
127
|
+
icon = icon.replace('${model}', sanitizeImageParameter(definition.model));
|
|
128
|
+
}
|
|
129
|
+
if (!icon) {
|
|
130
|
+
icon = `https://www.zigbee2mqtt.io/images/devices/${sanitizeImageParameter(definition.model)}.jpg`;
|
|
131
|
+
}
|
|
132
|
+
return icon;
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
exports.secondsToMilliseconds = (seconds) => seconds * 1000;
|
|
118
136
|
exports.bulbLevelToAdapterLevel = bulbLevelToAdapterLevel;
|
|
119
137
|
exports.adapterLevelToBulbLevel = adapterLevelToBulbLevel;
|
|
@@ -130,3 +148,4 @@ exports.isXiaomiDevice = (device) => {
|
|
|
130
148
|
(!device.manufacturerName || !device.manufacturerName.startsWith('Trust'));
|
|
131
149
|
};
|
|
132
150
|
exports.isIkeaTradfriDevice = (device) => ikeaTradfriManufacturerID.includes(device.manufacturerID);
|
|
151
|
+
exports.getDeviceIcon = getDeviceIcon;
|
package/lib/zbBaseExtension.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
class BaseExtension {
|
|
6
6
|
constructor(zigbee, options) {
|
|
7
7
|
this.zigbee = zigbee;
|
|
8
|
+
this.name = 'BaseExtension';
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
info(message, data) {
|
|
@@ -12,15 +13,15 @@ class BaseExtension {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
error(message, data) {
|
|
15
|
-
this.zigbee.error(message, data);
|
|
16
|
+
this.zigbee.error(this.name + ':' + message, data);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
warn(message, data) {
|
|
19
|
-
this.zigbee.warn(message, data);
|
|
20
|
+
this.zigbee.warn(this.name + ':' + message, data);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
debug(message, data) {
|
|
23
|
-
this.zigbee.debug(message, data);
|
|
24
|
+
this.zigbee.debug(this.name + ':' + message, data);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
sendError(error, message) {
|
package/lib/zbDelayedAction.js
CHANGED
|
@@ -50,6 +50,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
50
50
|
this.startDevicePingQueue = []; // simple fifo array for starting device pings
|
|
51
51
|
this.startDevicePingTimeout = null; // handle for the timeout which empties the queue
|
|
52
52
|
this.startDevicePingDelay = 200; // 200 ms delay between starting the ping timeout
|
|
53
|
+
this.name = "DeviceAvailability";
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
setOptions(options) {
|
|
@@ -265,7 +266,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
265
266
|
// }
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
|
-
|
|
269
|
+
|
|
269
270
|
onZigbeeEvent(data) {
|
|
270
271
|
const device = data.device;
|
|
271
272
|
if (!device) {
|
package/lib/zbDeviceConfigure.js
CHANGED
|
@@ -18,6 +18,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
18
18
|
|
|
19
19
|
this.configuring = new Set();
|
|
20
20
|
this.attempts = {};
|
|
21
|
+
this.name = "DeviceConfigure";
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
setOptions(options) {
|
|
@@ -119,13 +120,8 @@ class DeviceConfigure extends BaseExtension {
|
|
|
119
120
|
if (!this.attempts.hasOwnProperty(device.ieeeAddr)) {
|
|
120
121
|
this.attempts[device.ieeeAddr] = 0;
|
|
121
122
|
}
|
|
122
|
-
|
|
123
|
-
this.info(`Configuring ${device.ieeeAddr} ${device.modelID}`);
|
|
124
123
|
try {
|
|
125
|
-
await
|
|
126
|
-
this.info(`DeviceConfigure successful ${device.ieeeAddr} ${device.modelID}`);
|
|
127
|
-
device.meta.configured = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
|
|
128
|
-
device.save();
|
|
124
|
+
await this.doConfigure(device, mappedDevice);
|
|
129
125
|
} catch (error) {
|
|
130
126
|
this.sendError(error);
|
|
131
127
|
this.warn(
|
|
@@ -134,7 +130,6 @@ class DeviceConfigure extends BaseExtension {
|
|
|
134
130
|
);
|
|
135
131
|
this.attempts[device.ieeeAddr]++;
|
|
136
132
|
}
|
|
137
|
-
|
|
138
133
|
this.configuring.delete(device.ieeeAddr);
|
|
139
134
|
} catch (error) {
|
|
140
135
|
this.sendError(error);
|
|
@@ -143,6 +138,15 @@ class DeviceConfigure extends BaseExtension {
|
|
|
143
138
|
);
|
|
144
139
|
}
|
|
145
140
|
}
|
|
141
|
+
|
|
142
|
+
async doConfigure(device, mappedDevice) {
|
|
143
|
+
this.info(`Configuring ${device.ieeeAddr} ${device.modelID}`);
|
|
144
|
+
const coordinatorEndpoint = await this.zigbee.getDevicesByType('Coordinator')[0].endpoints[0];
|
|
145
|
+
await mappedDevice.configure(device, coordinatorEndpoint);
|
|
146
|
+
device.meta.configured = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
|
|
147
|
+
device.save();
|
|
148
|
+
this.info(`DeviceConfigure successful ${device.ieeeAddr} ${device.modelID}`);
|
|
149
|
+
}
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
module.exports = DeviceConfigure;
|
package/lib/zbDeviceEvent.js
CHANGED
|
@@ -4,6 +4,12 @@ const BaseExtension = require('./zbBaseExtension');
|
|
|
4
4
|
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
|
|
5
5
|
|
|
6
6
|
class DeviceEvent extends BaseExtension {
|
|
7
|
+
constructor(zigbee, options) {
|
|
8
|
+
super(zigbee, options);
|
|
9
|
+
this.name = "DeviceEvent";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
async onZigbeeStarted() {
|
|
8
14
|
for (const device of await this.zigbee.getClients()) {
|
|
9
15
|
this.callOnEvent(device, 'start', {});
|
package/lib/zigbeecontroller.js
CHANGED
|
@@ -105,11 +105,11 @@ class ZigbeeController extends EventEmitter {
|
|
|
105
105
|
this.debug('zigbee-herdsman started');
|
|
106
106
|
this.herdsman_started = true;
|
|
107
107
|
this.info(`Coordinator firmware version: ${JSON.stringify(await this.herdsman.getCoordinatorVersion())}`);
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
// debug info from herdsman getNetworkParameters
|
|
110
110
|
const debNetworkParam = JSON.parse(JSON.stringify(await this.herdsman.getNetworkParameters()));
|
|
111
111
|
const extendedPanIDDebug = (typeof debNetworkParam.extendedPanID == 'string') ? debNetworkParam.extendedPanID.replace('0x','') : debNetworkParam.extendedPanID;
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
let extPanIDDebug = '';
|
|
114
114
|
for (let i = extendedPanIDDebug.length - 1; i >= 0; i--) {
|
|
115
115
|
extPanIDDebug += extendedPanIDDebug[i-1];
|
|
@@ -117,8 +117,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
117
117
|
i--;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
this.debug(`Zigbee network parameters: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
|
|
121
|
-
|
|
120
|
+
this.debug(`Zigbee network parameters: panID=${debNetworkParam.panID} channel=${debNetworkParam.channel} extendedPanID=${extPanIDDebug}`);
|
|
121
|
+
|
|
122
122
|
} catch (e) {
|
|
123
123
|
this.sendError(e);
|
|
124
124
|
this.error('Starting zigbee-herdsman problem : ' + JSON.stringify(e.message));
|
|
@@ -148,6 +148,9 @@ class ZigbeeController extends EventEmitter {
|
|
|
148
148
|
case '19':
|
|
149
149
|
powerText = 'high';
|
|
150
150
|
break;
|
|
151
|
+
case '20':
|
|
152
|
+
powerText = 'high+';
|
|
153
|
+
break;
|
|
151
154
|
default:
|
|
152
155
|
powerText = 'normal';
|
|
153
156
|
}
|
|
@@ -217,13 +220,14 @@ class ZigbeeController extends EventEmitter {
|
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
callExtensionMethod(method, parameters) {
|
|
223
|
+
const result = [];
|
|
220
224
|
for (const extension of this.extensions) {
|
|
221
225
|
if (extension[method]) {
|
|
222
226
|
try {
|
|
223
227
|
if (parameters !== undefined) {
|
|
224
|
-
extension[method](...parameters);
|
|
228
|
+
result.push(extension[method](...parameters));
|
|
225
229
|
} else {
|
|
226
|
-
extension[method]();
|
|
230
|
+
result.push(extension[method]());
|
|
227
231
|
}
|
|
228
232
|
} catch (error) {
|
|
229
233
|
this.sendError(error);
|
|
@@ -231,6 +235,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
231
235
|
}
|
|
232
236
|
}
|
|
233
237
|
}
|
|
238
|
+
return Promise.all(result);
|
|
234
239
|
}
|
|
235
240
|
|
|
236
241
|
async getClients(all) {
|
|
@@ -248,9 +253,11 @@ class ZigbeeController extends EventEmitter {
|
|
|
248
253
|
|
|
249
254
|
async getGroups() {
|
|
250
255
|
try {
|
|
251
|
-
|
|
256
|
+
const rv = await this.herdsman.getGroups();
|
|
257
|
+
return rv;
|
|
252
258
|
} catch (error) {
|
|
253
259
|
this.sendError(error);
|
|
260
|
+
this.error(JSON.stringify(error));
|
|
254
261
|
return undefined;
|
|
255
262
|
}
|
|
256
263
|
}
|
|
@@ -280,11 +287,11 @@ class ZigbeeController extends EventEmitter {
|
|
|
280
287
|
const group = await this.getGroupByID(id);
|
|
281
288
|
if (group) {
|
|
282
289
|
const groupmembers = group.members;
|
|
283
|
-
|
|
284
290
|
for (const member of groupmembers) {
|
|
291
|
+
const epid = (member.ID ? member.ID:-1);
|
|
285
292
|
const nwk = member.deviceNetworkAddress;
|
|
286
293
|
const device = this.getDeviceByNetworkAddress(nwk);
|
|
287
|
-
if (device && device.ieeeAddr) members.push( {
|
|
294
|
+
if (device && device.ieeeAddr) members.push( { ieee:device.ieeeAddr, model:device.modelID, epid:epid, ep:member } );
|
|
288
295
|
}
|
|
289
296
|
}
|
|
290
297
|
else {
|
|
@@ -525,6 +532,13 @@ class ZigbeeController extends EventEmitter {
|
|
|
525
532
|
const entity = await this.resolveEntity(message.device || message.ieeeAddr);
|
|
526
533
|
const friendlyName = entity.name;
|
|
527
534
|
this.warn(`Device '${friendlyName}' announced itself`);
|
|
535
|
+
|
|
536
|
+
if (entity && entity.mapped) {
|
|
537
|
+
this.callExtensionMethod(
|
|
538
|
+
'onZigbeeEvent',
|
|
539
|
+
[ {'device': message.device, 'type': 'deviceAnnounce'}, (entity ? entity.mapped : null)]);
|
|
540
|
+
}
|
|
541
|
+
|
|
528
542
|
this.emit('pairing', `Device '${friendlyName}' announced itself`);
|
|
529
543
|
if (!this.herdsman.getPermitJoin()) this.callExtensionMethod('registerDevicePing', [message.device, entity]);
|
|
530
544
|
// if has modelID so can create device
|
|
@@ -534,6 +548,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
534
548
|
}
|
|
535
549
|
}
|
|
536
550
|
|
|
551
|
+
|
|
537
552
|
async handleDeviceJoined(message) {
|
|
538
553
|
this.debug('handleDeviceJoined', message);
|
|
539
554
|
//const entity = await this.resolveEntity(message.device || message.ieeeAddr);
|
|
@@ -686,7 +701,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
686
701
|
}
|
|
687
702
|
}
|
|
688
703
|
|
|
689
|
-
async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback) {
|
|
704
|
+
async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) {
|
|
690
705
|
const entity = await this.resolveEntity(deviceID, ep);
|
|
691
706
|
const device = entity.device;
|
|
692
707
|
const endpoint = entity.endpoint;
|
|
@@ -727,20 +742,57 @@ class ZigbeeController extends EventEmitter {
|
|
|
727
742
|
result = await endpoint[cmd](cid, zclData, cfg);
|
|
728
743
|
}
|
|
729
744
|
if (callback) callback(undefined, result);
|
|
730
|
-
}
|
|
745
|
+
}
|
|
746
|
+
else if(type === 'functionalResp'){
|
|
747
|
+
cfg.disableDefaultResponse = false;
|
|
748
|
+
const result = await endpoint.commandResponse(cid, cmd, zclData, cfg, zclSeqNum);
|
|
749
|
+
if (callback) callback(undefined, result);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
731
752
|
cfg.disableDefaultResponse = false;
|
|
732
753
|
const result = await endpoint.command(cid, cmd, zclData, cfg);
|
|
733
754
|
if (callback) callback(undefined, result);
|
|
734
755
|
}
|
|
735
756
|
}
|
|
736
757
|
|
|
737
|
-
async addDevToGroup(devId, groupId) {
|
|
758
|
+
async addDevToGroup(devId, groupId, epid) {
|
|
738
759
|
try {
|
|
739
760
|
const entity = await this.resolveEntity(devId);
|
|
740
761
|
const group = await this.resolveEntity(groupId);
|
|
741
|
-
this.debug(`entity: ${safeJsonStringify(entity)}`);
|
|
742
|
-
this.debug(`group: ${safeJsonStringify(group)}`);
|
|
743
|
-
|
|
762
|
+
this.debug(`addDevFromGroup - entity: ${safeJsonStringify(entity)}`);
|
|
763
|
+
this.debug(`addDevFromGroup - group: ${safeJsonStringify(group)}`);
|
|
764
|
+
if (epid != undefined) {
|
|
765
|
+
for (const ep of entity.endpoints) {
|
|
766
|
+
if (ep.ID == epid && (ep.inputClusters.includes(4) || ep.outputClusters.includes(4)))
|
|
767
|
+
{
|
|
768
|
+
this.debug(`adding endpoint ${ep.ID} (${epid}) to group ${groupId}`)
|
|
769
|
+
await(ep.addToGroup(group.mapped));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
}
|
|
774
|
+
else
|
|
775
|
+
{
|
|
776
|
+
if (entity.endpoint.inputClusters.includes(4))
|
|
777
|
+
{
|
|
778
|
+
this.debug(`adding endpoint ${entity.endpoint.ID} to group`)
|
|
779
|
+
await entity.endpoint.addToGroup(group.mapped);
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
let added = false;
|
|
783
|
+
for (const ep of entity.endpoints)
|
|
784
|
+
{
|
|
785
|
+
if (ep.inputClusters.includes(4))
|
|
786
|
+
{
|
|
787
|
+
this.debug(`adding endpoint ${ep.ID} to group`)
|
|
788
|
+
await ep.addToGroup(group.mapped);
|
|
789
|
+
added = true;
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (!added) throw ('cluster genGroups not supported');
|
|
794
|
+
}
|
|
795
|
+
}
|
|
744
796
|
} catch (error) {
|
|
745
797
|
this.sendError(error);
|
|
746
798
|
this.error(`Exception when trying to Add ${devId} to group ${groupId}`, error);
|
|
@@ -749,11 +801,38 @@ class ZigbeeController extends EventEmitter {
|
|
|
749
801
|
return {};
|
|
750
802
|
}
|
|
751
803
|
|
|
804
|
+
async removeDevFromGroup(devId, groupId, epid) {
|
|
805
|
+
try {
|
|
806
|
+
const entity = await this.resolveEntity(devId);
|
|
807
|
+
const group = await this.resolveEntity(groupId);
|
|
808
|
+
this.debug(`removeDevFromGroup - entity: ${safeJsonStringify(entity)}`);
|
|
809
|
+
this.debug(`removeDevFromGroup - group: ${safeJsonStringify(group)}`);
|
|
810
|
+
if (epid != undefined) {
|
|
811
|
+
for (const ep of entity.endpoints) {
|
|
812
|
+
if (ep.ID == epid && (ep.inputClusters.includes(4) || ep.outputClusters.includes(4)))
|
|
813
|
+
{
|
|
814
|
+
this.debug(`removing endpoint ${ep.ID} (${epid}) group ${groupId}`)
|
|
815
|
+
await(ep.removeFromGroup(group.mapped))
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
} else await entity.endpoint.removeFromGroup(group.mapped);
|
|
819
|
+
} catch (error) {
|
|
820
|
+
this.sendError(error);
|
|
821
|
+
this.error(`Exception when trying remove ${devId} (ep ${epid?epid:entity.endpoint.ID}) from group ${devId}`, error);
|
|
822
|
+
return { error: `Failed to remove dev ${devId} (ep ${epid?epid:entity.endpoint.ID}) from group ${devId}`};
|
|
823
|
+
}
|
|
824
|
+
return {};
|
|
825
|
+
}
|
|
826
|
+
|
|
752
827
|
async removeDevFromAllGroups(devId) {
|
|
753
828
|
try {
|
|
754
829
|
const entity = await this.resolveEntity(devId);
|
|
755
830
|
this.debug(`entity: ${safeJsonStringify(entity)}`);
|
|
756
|
-
|
|
831
|
+
for (const ep of entity.endpoints) {
|
|
832
|
+
if (ep.inputClusters.includes(4) || ep.outputClusters.includes(4))
|
|
833
|
+
await ep.removefromAllGroups();
|
|
834
|
+
}
|
|
835
|
+
//await entity.endpoint.removeFromAllGroups();
|
|
757
836
|
} catch (error) {
|
|
758
837
|
this.sendError(error);
|
|
759
838
|
this.error(`Exception when trying remove ${devId} from all groups`, error);
|
package/main.js
CHANGED
|
@@ -30,6 +30,7 @@ const StatesController = require('./lib/statescontroller');
|
|
|
30
30
|
const ExcludePlugin = require('./lib/exclude');
|
|
31
31
|
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
|
|
32
32
|
const vm = require('vm');
|
|
33
|
+
const util = require('util');
|
|
33
34
|
|
|
34
35
|
const createByteArray = function (hexString) {
|
|
35
36
|
const bytes = [];
|
|
@@ -158,14 +159,15 @@ class Zigbee extends utils.Adapter {
|
|
|
158
159
|
this.log.error(`${message}: Code ${error.code} (${ecode.message})`);
|
|
159
160
|
this.sendError(error, `${message}: Code ${error.code} (${ecode.message})`);
|
|
160
161
|
break;
|
|
161
|
-
default:
|
|
162
|
+
default:
|
|
162
163
|
this.log.error(`${message}: Code ${error.code} (malformed error)`);
|
|
163
164
|
this.sendError(error, `${message}: Code ${error.code} (malformed error)`);
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
debugLog (data) {
|
|
168
|
-
|
|
168
|
+
debugLog (data, ...args) {
|
|
169
|
+
const message = (args) ? util.format(data, ...args) : data;
|
|
170
|
+
this.log.debug(message.slice(message.indexOf('zigbee-herdsman')));
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
async onReady() {
|
|
@@ -304,6 +306,7 @@ class Zigbee extends utils.Adapter {
|
|
|
304
306
|
|
|
305
307
|
async onZigbeeAdapterReady() {
|
|
306
308
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
309
|
+
this.log.warn("config :" + JSON.stringify(this.config));
|
|
307
310
|
this.log.info(`Zigbee started`);
|
|
308
311
|
// https://github.com/ioBroker/ioBroker.zigbee/issues/668
|
|
309
312
|
const extPanIdFix = this.config.extPanIdFix ? this.config.extPanIdFix : false;
|
|
@@ -315,25 +318,31 @@ class Zigbee extends utils.Adapter {
|
|
|
315
318
|
const adapterType = this.config.adapterType || 'zstack';
|
|
316
319
|
if (adapterType === 'zstack') {
|
|
317
320
|
if (configExtPanId != networkExtPanId) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
321
|
+
try {
|
|
322
|
+
// try to read from nvram
|
|
323
|
+
const result = await this.zbController.herdsman.adapter.znp.request(
|
|
324
|
+
1, // Subsystem.SYS
|
|
325
|
+
'osalNvRead',
|
|
326
|
+
{
|
|
327
|
+
id: 45, // EXTENDED_PAN_ID
|
|
328
|
+
len: 0x08,
|
|
329
|
+
offset: 0x00,
|
|
330
|
+
},
|
|
331
|
+
null, [
|
|
332
|
+
0, // ZnpCommandStatus.SUCCESS
|
|
333
|
+
2, // ZnpCommandStatus.INVALID_PARAM
|
|
334
|
+
]
|
|
335
|
+
);
|
|
336
|
+
const nwExtPanId = '0x'+result.payload.value.reverse().toString('hex');
|
|
337
|
+
this.log.debug(`Config value ${configExtPanId} : nw value ${nwExtPanId}`);
|
|
338
|
+
if (configExtPanId != nwExtPanId) {
|
|
339
|
+
networkExtPanId = nwExtPanId;
|
|
340
|
+
needChange = true;
|
|
341
|
+
}
|
|
342
|
+
} catch (e) {
|
|
343
|
+
this.log.error(`Unable to apply ExtPanID changes: ${e}`);
|
|
344
|
+
this.sendError(e, `Unable to apply ExtPanID changes`);
|
|
345
|
+
needChange = false;
|
|
337
346
|
}
|
|
338
347
|
} else {
|
|
339
348
|
needChange = true;
|
|
@@ -423,7 +432,7 @@ class Zigbee extends utils.Adapter {
|
|
|
423
432
|
delete msgForState['endpoint'];
|
|
424
433
|
msgForState['endpoint_id'] = message.endpoint.ID;
|
|
425
434
|
this.publishToState(devId, model, {msg_from_zigbee: safeJsonStringify(msgForState)});
|
|
426
|
-
|
|
435
|
+
|
|
427
436
|
if (!entity.mapped) {
|
|
428
437
|
return;
|
|
429
438
|
}
|
|
@@ -489,6 +498,10 @@ class Zigbee extends utils.Adapter {
|
|
|
489
498
|
const entity = await this.zbController.resolveEntity(deviceId);
|
|
490
499
|
this.log.debug(`entity: ${safeJsonStringify(entity)}`);
|
|
491
500
|
const mappedModel = entity.mapped;
|
|
501
|
+
if (!mappedModel) {
|
|
502
|
+
this.log.debug(`No mapped model for ${model}`);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
492
505
|
this.log.debug('Mapped Model: ' + JSON.stringify(mappedModel));
|
|
493
506
|
|
|
494
507
|
stateList.forEach(async(changedState) => {
|
|
@@ -579,13 +592,15 @@ class Zigbee extends utils.Adapter {
|
|
|
579
592
|
try {
|
|
580
593
|
const result = await converter.convertSet(target, key, preparedValue, meta);
|
|
581
594
|
this.log.debug(`convert result ${safeJsonStringify(result)}`);
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
595
|
+
if (result !== undefined) {
|
|
596
|
+
if (stateModel && !isGroup)
|
|
597
|
+
this.acknowledgeState(deviceId, model, stateDesc, value);
|
|
598
|
+
// process sync state list
|
|
599
|
+
this.processSyncStatesList(deviceId, model, syncStateList);
|
|
600
|
+
if (isGroup) {
|
|
601
|
+
await this.callPluginMethod('queryGroupMemberState', [deviceId, stateDesc]);
|
|
602
|
+
this.acknowledgeState(deviceId, model, stateDesc, value);
|
|
603
|
+
}
|
|
589
604
|
}
|
|
590
605
|
} catch(error) {
|
|
591
606
|
this.filterError(`Error ${error.code} on send command to ${deviceId}.`+
|
|
@@ -757,8 +772,8 @@ class Zigbee extends utils.Adapter {
|
|
|
757
772
|
getZigbeeOptions() {
|
|
758
773
|
// file path for db
|
|
759
774
|
let dbDir = path.join(utils.getAbsoluteInstanceDataDir(this), '');
|
|
760
|
-
dbDir = dbDir.replace('.', '_');
|
|
761
|
-
|
|
775
|
+
dbDir = dbDir.replace('.', '_');
|
|
776
|
+
|
|
762
777
|
if (this.systemConfig && !fs.existsSync(dbDir)) {
|
|
763
778
|
try {
|
|
764
779
|
fs.mkdirSync(dbDir);
|