iobroker.zigbee 3.1.2 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/admin/admin.js +369 -191
- package/admin/index_m.html +30 -6
- package/admin/tab_m.html +31 -11
- package/io-package.json +42 -39
- package/lib/DeviceDebug.js +25 -2
- package/lib/binding.js +7 -7
- package/lib/commands.js +279 -249
- package/lib/developer.js +1 -1
- package/lib/devices.js +2 -2
- package/lib/exclude.js +1 -1
- package/lib/exposes.js +54 -24
- package/lib/groups.js +26 -28
- package/lib/localConfig.js +8 -8
- package/lib/networkmap.js +10 -2
- package/lib/statescontroller.js +134 -90
- package/lib/zbDelayedAction.js +4 -4
- package/lib/zbDeviceAvailability.js +32 -33
- package/lib/zbDeviceConfigure.js +7 -0
- package/lib/zbDeviceEvent.js +39 -6
- package/lib/zigbeecontroller.js +91 -43
- package/main.js +31 -38
- package/package.json +5 -8
- package/lib/tools.js +0 -55
package/lib/exposes.js
CHANGED
|
@@ -7,7 +7,7 @@ const utils = require('./utils');
|
|
|
7
7
|
const colors = require('./colors');
|
|
8
8
|
const ea = require('zigbee-herdsman-converters/lib/exposes').access;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const LocalData = { options:{} };
|
|
11
11
|
const __logger = undefined;
|
|
12
12
|
|
|
13
13
|
function genState(expose, role, name, desc) {
|
|
@@ -819,9 +819,28 @@ function createFromExposes(model, def, device, log) {
|
|
|
819
819
|
break;
|
|
820
820
|
|
|
821
821
|
case 'composite':
|
|
822
|
+
{
|
|
823
|
+
const stname = (expose.property);
|
|
824
|
+
const stateId = stname.replace(/\*/g, '');
|
|
825
|
+
const stateName = (expose.description || expose.name);
|
|
826
|
+
const channelID = LocalData.options.newCompositeMethod ? `${stateId}.` : '';
|
|
827
|
+
if (LocalData.options.newCompositeMethod) {
|
|
828
|
+
|
|
829
|
+
if (typeof stname !== 'string') break;
|
|
830
|
+
pushToStates({
|
|
831
|
+
id: stateId,
|
|
832
|
+
name: stateName,
|
|
833
|
+
icon: undefined,
|
|
834
|
+
role: 'state',
|
|
835
|
+
write: expose.access & ea.SET,
|
|
836
|
+
read: expose.access & ea.GET,
|
|
837
|
+
type: 'string',
|
|
838
|
+
}, expose.access)
|
|
839
|
+
|
|
840
|
+
}
|
|
822
841
|
for (const prop of expose.features) {
|
|
823
842
|
if (prop.type == 'numeric') {
|
|
824
|
-
const st = genState(prop);
|
|
843
|
+
const st = genState(prop, 'state', `${channelID}${prop.name}`);
|
|
825
844
|
st.prop = expose.property;
|
|
826
845
|
st.inOptions = true;
|
|
827
846
|
// I'm not fully sure, as it really needed, but
|
|
@@ -831,27 +850,34 @@ function createFromExposes(model, def, device, log) {
|
|
|
831
850
|
result[expose.property] = options;
|
|
832
851
|
return result;
|
|
833
852
|
};
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
st.
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
853
|
+
if (LocalData.options.newCompositeMethod) {
|
|
854
|
+
st.compositeKey = stateId;
|
|
855
|
+
st.compositeTimeout = 250;
|
|
856
|
+
st.compositeState = stateId;
|
|
857
|
+
} else
|
|
858
|
+
{
|
|
859
|
+
// if we have a composite expose, the value have to be an object {expose.property : {prop.property: value}}
|
|
860
|
+
if (prop.access & ea.SET) {
|
|
861
|
+
st.setter = (value, options) => {
|
|
862
|
+
const result = {};
|
|
863
|
+
options[prop.property] = value;
|
|
864
|
+
result[expose.property] = options;
|
|
865
|
+
return result;
|
|
866
|
+
};
|
|
867
|
+
st.setattr = expose.property;
|
|
868
|
+
}
|
|
869
|
+
// if we have a composite expose, the payload will be an object {expose.property : {prop.property: value}}
|
|
870
|
+
if (prop.access & ea.STATE) {
|
|
871
|
+
st.getter = payload => {
|
|
872
|
+
if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
|
|
873
|
+
return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined;
|
|
874
|
+
} else {
|
|
875
|
+
return undefined;
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
} else {
|
|
879
|
+
st.getter = payload => undefined;
|
|
880
|
+
}
|
|
855
881
|
}
|
|
856
882
|
pushToStates(st, prop.access);
|
|
857
883
|
}
|
|
@@ -894,6 +920,7 @@ function createFromExposes(model, def, device, log) {
|
|
|
894
920
|
}
|
|
895
921
|
}
|
|
896
922
|
break;
|
|
923
|
+
}
|
|
897
924
|
case 'list':
|
|
898
925
|
// is not mapped
|
|
899
926
|
// for (const prop of expose) {
|
|
@@ -909,7 +936,10 @@ function createFromExposes(model, def, device, log) {
|
|
|
909
936
|
|
|
910
937
|
}
|
|
911
938
|
|
|
912
|
-
async function applyExposeForDevice(mappedDevices, byModel, device) {
|
|
939
|
+
async function applyExposeForDevice(mappedDevices, byModel, device, options) {
|
|
940
|
+
if (options && typeof options == 'object') {
|
|
941
|
+
LocalData.options = options;
|
|
942
|
+
}
|
|
913
943
|
const deviceDef = await zigbeeHerdsmanConverters.findByDevice(device);
|
|
914
944
|
if (!deviceDef) {
|
|
915
945
|
return undefined;
|
package/lib/groups.js
CHANGED
|
@@ -216,7 +216,6 @@ class Groups {
|
|
|
216
216
|
break;
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
|
|
220
219
|
}
|
|
221
220
|
}
|
|
222
221
|
}
|
|
@@ -240,12 +239,9 @@ class Groups {
|
|
|
240
239
|
}
|
|
241
240
|
}
|
|
242
241
|
}
|
|
243
|
-
|
|
244
242
|
}
|
|
245
243
|
}
|
|
246
244
|
|
|
247
|
-
|
|
248
|
-
|
|
249
245
|
static readables = {
|
|
250
246
|
state: { cluster:6, attributes:['onOff']},
|
|
251
247
|
brightness: { cluster:8, attributes:['currentLevel']},
|
|
@@ -260,16 +256,21 @@ class Groups {
|
|
|
260
256
|
const endpoint = entity.endpoint;
|
|
261
257
|
const mappedModel = entity.mapped;
|
|
262
258
|
if (!mappedModel) return;
|
|
259
|
+
const obj = await this.adapter.getObjectAsync((member.ieee.includes('x') ? member.ieee.split('x')[1] : member.ieee));
|
|
260
|
+
if (obj && obj.common.deactivated) {
|
|
261
|
+
this.debug(`omitting reading member state for deactivated device ${member.ieee}`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
263
264
|
const converter = mappedModel.toZigbee.find(c => c && (c.key.includes(item.state)));
|
|
264
265
|
const canReadViaConverter = converter && converter.hasOwnProperty('convertGet');
|
|
265
266
|
if (canReadViaConverter) {
|
|
266
267
|
try {
|
|
267
268
|
await converter.convertGet(endpoint, item.state, {device:entity.device});
|
|
268
269
|
} catch (error) {
|
|
269
|
-
this.
|
|
270
|
+
this.debug(`reading ${item.state} from ${member.id}${member.ieee ? '/' + member.epid : ''} via convertGet failed with ${error && error.message ? error.message : 'no reason given'}`);
|
|
270
271
|
return {unread:member.device};
|
|
271
272
|
}
|
|
272
|
-
this.
|
|
273
|
+
this.debug(`reading ${item.state} from ${member.ieee}${member.ep ? '/' + member.epid : ''} via convertGet succeeded` );
|
|
273
274
|
return {read:member.device};
|
|
274
275
|
}
|
|
275
276
|
if (toRead.cluster) {
|
|
@@ -277,25 +278,23 @@ class Groups {
|
|
|
277
278
|
if (entity.endpoint.inputClusters.includes(toRead.cluster)) {
|
|
278
279
|
try {
|
|
279
280
|
const result = await endpoint.read(toRead.cluster, toRead.attributes,{disableDefaultResponse : true });
|
|
280
|
-
this.
|
|
281
|
+
this.debug(`readGroupMemberStatus for ${item.state} from ${member.ieee}${member.ep ? '/' + member.epid : ''} resulted in ${JSON.stringify(result)}`);
|
|
281
282
|
}
|
|
282
283
|
catch (error) {
|
|
283
|
-
this.
|
|
284
|
+
this.debug(`reading ${item.state} from ${member.ieee}${member.ep ? '/' + member.epid : ''} via endpoint.read with ${toRead.cluster}, ${JSON.stringify(toRead.attributes)} resulted in ${error && error.message ? error.message : 'an unspecified error'}`);
|
|
284
285
|
}
|
|
285
|
-
this.
|
|
286
|
+
this.debug(`reading ${item.state} from ${member.ieee}${member.ep ? '/' + member.epid : ''} via endpoint.read with ${toRead.cluster}, ${JSON.stringify(toRead.attributes)} succeeded`);
|
|
286
287
|
}
|
|
287
|
-
else this.
|
|
288
|
+
else this.debug(`omitting cluster ${toRead.cluster} - not supported`);
|
|
288
289
|
}
|
|
289
290
|
else {
|
|
290
|
-
this.
|
|
291
|
+
this.debug(`unable to read ${item.state} from ${member.id}${member.ep ? '/' + member.epid : ''} - unable to resolve the entity`);
|
|
291
292
|
}
|
|
292
293
|
return;
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
async readGroupMemberStatus(obj, item) {
|
|
297
|
-
obj.warn(`rgms with ${JSON.stringify(item)}`);
|
|
298
|
-
|
|
299
298
|
const toRead = Groups.readables[item.state]
|
|
300
299
|
if (toRead) {
|
|
301
300
|
const members = await obj.zbController.getGroupMembersFromController(item.id);
|
|
@@ -465,11 +464,10 @@ class Groups {
|
|
|
465
464
|
|
|
466
465
|
async renameGroup(from, command, message) {
|
|
467
466
|
this.debug(`rename group called with ${from}, ${command}, ${JSON.stringify(message)}`);
|
|
468
|
-
// const groupsEntry = await this.adapter.getStateAsync('info.groups');
|
|
469
|
-
// const objGroups = (groupsEntry && groupsEntry.val ? JSON.parse(groupsEntry.val) : {});
|
|
470
|
-
const name = message.name;
|
|
471
467
|
const id = `group_${message.id}`;
|
|
472
|
-
|
|
468
|
+
|
|
469
|
+
this.stController.localConfig.updateDeviceName(id, message.name);
|
|
470
|
+
|
|
473
471
|
try {
|
|
474
472
|
const group = await this.zbController.verifyGroupExists(message.id);
|
|
475
473
|
if (message.remove) {
|
|
@@ -478,16 +476,13 @@ class Groups {
|
|
|
478
476
|
this.debug('trying to remove ' + member.id + (member.ep ? '.'+member.ep : '') + ' ' + ' from group ' + message.id + ' response is '+JSON.stringify(response));
|
|
479
477
|
}
|
|
480
478
|
}
|
|
481
|
-
if (icon.match(/img\/group_\d+\.png/g)) {
|
|
482
|
-
icon = await this.zbController.rebuildGroupIcon(group);
|
|
483
|
-
}
|
|
484
479
|
} catch (e) {
|
|
485
480
|
this.warn('renameGroup caught error ' + (e && e.message ? e.message : 'no message'));
|
|
486
481
|
if (e && e.hasOwnProperty('code')) {
|
|
487
482
|
this.warn(`renameGroup caught error ${JSON.stringify(e.code)}`);
|
|
488
483
|
}
|
|
489
484
|
}
|
|
490
|
-
this.debug(`rename group name ${name}, id ${id},
|
|
485
|
+
this.debug(`rename group name ${message.name}, id ${id}, remove ${JSON.stringify(message.removeMembers)}`);
|
|
491
486
|
this.syncGroups([parseInt(message.id)]);
|
|
492
487
|
}
|
|
493
488
|
|
|
@@ -601,10 +596,13 @@ class Groups {
|
|
|
601
596
|
if (!this.GroupData.states[id]) return undefined;
|
|
602
597
|
const val = this.GroupData.states[id].val;
|
|
603
598
|
if (val == null || val == undefined) {
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
599
|
+
const ai = getAdId(this.adapter, id);
|
|
600
|
+
const obj = await this.adapter.getObjectAsync(ai);
|
|
601
|
+
if (obj && obj.common.deactivated) return undefined;
|
|
602
|
+
const state = await this.adapter.getStateAsync(id);
|
|
603
|
+
if (state) {
|
|
604
|
+
this.GroupData.states[id].val = state.val;
|
|
605
|
+
return state.val;
|
|
608
606
|
}
|
|
609
607
|
return undefined;
|
|
610
608
|
}
|
|
@@ -617,7 +615,7 @@ class Groups {
|
|
|
617
615
|
// get all group id's from the database and the respective names from the local overrides (if present)
|
|
618
616
|
const groups = await this.getGroups();
|
|
619
617
|
const chain = [];
|
|
620
|
-
const usedGroupsIds = Object.keys(groups);
|
|
618
|
+
const usedGroupsIds = numericGroupIdArray.length > 0 ? Object.keys(groups) : [];
|
|
621
619
|
for (const j in groups) {
|
|
622
620
|
if (numericGroupIdArray.length > 0 && !numericGroupIdArray.includes(Number(j))) continue; // skip groups we didnt ask for
|
|
623
621
|
this.debug(`Analysing group_${JSON.stringify(j)}`);
|
|
@@ -659,7 +657,7 @@ class Groups {
|
|
|
659
657
|
this.rebuildGroupMemberStateList(j, memberInfo);
|
|
660
658
|
}
|
|
661
659
|
|
|
662
|
-
const name = groups[j];
|
|
660
|
+
const name = this.stController.localConfig.NameForId(id, 'group', groups[j]);
|
|
663
661
|
const icon = this.stController.localConfig.IconForId(id, 'group', await this.stController.getDefaultGroupIcon(id));
|
|
664
662
|
chain.push(new Promise(resolve => {
|
|
665
663
|
const isActive = false;
|
|
@@ -668,7 +666,7 @@ class Groups {
|
|
|
668
666
|
common: {name: name, type: 'group', icon: icon },
|
|
669
667
|
native: {id: j}
|
|
670
668
|
}, () => {
|
|
671
|
-
this.adapter.extendObject(id, {common: {type: 'group', icon: icon}});
|
|
669
|
+
this.adapter.extendObject(id, {common: {name: name, type: 'group', icon: icon}});
|
|
672
670
|
// create writable states for groups from their devices
|
|
673
671
|
for (const stateInd in statesMapping.groupStates) {
|
|
674
672
|
if (!statesMapping.groupStates.hasOwnProperty(stateInd)) {
|
package/lib/localConfig.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/*eslint no-unused-vars: ['off']*/
|
|
4
3
|
const fs = require('fs');
|
|
5
4
|
const path = require('path');
|
|
6
5
|
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
|
|
7
|
-
// const { src } = require('gulp');
|
|
8
6
|
|
|
9
7
|
const EventEmitter = require('events').EventEmitter;
|
|
10
8
|
|
|
@@ -20,7 +18,6 @@ class localConfig extends EventEmitter {
|
|
|
20
18
|
this.adapter.on('ready', () => this.onReady());
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
|
|
24
21
|
async onReady()
|
|
25
22
|
{
|
|
26
23
|
}
|
|
@@ -116,9 +113,12 @@ class localConfig extends EventEmitter {
|
|
|
116
113
|
this.error(`update called with illegal id data:${JSON.stringify(target)}:${JSON.stringify(key)}`)
|
|
117
114
|
return false;
|
|
118
115
|
}
|
|
119
|
-
const base = global ? this.localData.by_model[target] || {} : this.localData.by_id[target] || {};
|
|
120
116
|
const rv = {};
|
|
121
|
-
if (
|
|
117
|
+
if ((this.localData.byModel[target] || {}).hasOwnProperty(key))
|
|
118
|
+
rv[key] = this.localData.byModel[target] || {}[key];
|
|
119
|
+
if (global) return rv;
|
|
120
|
+
if ((this.localData.by_id[_target] || {}).hasOwnProperty(key))
|
|
121
|
+
rv[key] = this.localData.by_id[_target] || {}[key];
|
|
122
122
|
return rv;
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -285,7 +285,7 @@ class localConfig extends EventEmitter {
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
async retainData() {
|
|
288
|
-
|
|
288
|
+
this.debug('retaining local config: ' + JSON.stringify(this.localData));
|
|
289
289
|
try {
|
|
290
290
|
fs.writeFileSync(this.filename, JSON.stringify(this.localData, null, 2))
|
|
291
291
|
this.info('Saved local configuration data');
|
|
@@ -304,7 +304,6 @@ class localConfig extends EventEmitter {
|
|
|
304
304
|
const fn = path.join(item.parentPath, item.name);
|
|
305
305
|
rv.push({file: fn, name: item.name, data: fs.readFileSync(path.join(item.parentPath, item.name), 'base64'), isBase64:true});
|
|
306
306
|
});
|
|
307
|
-
//this.warn('enumerateImages for ' + _path + ' is ' + JSON.stringify(rv));
|
|
308
307
|
}
|
|
309
308
|
catch (error) {
|
|
310
309
|
this.error(`error in enumerateImages : ${error.message ? error.message : 'undefined error'}`)
|
|
@@ -312,13 +311,14 @@ class localConfig extends EventEmitter {
|
|
|
312
311
|
return rv;
|
|
313
312
|
}
|
|
314
313
|
|
|
315
|
-
getOptions(
|
|
314
|
+
getOptions(_dev_id, model_id) {
|
|
316
315
|
function extractOptions(target, options) {
|
|
317
316
|
if (typeof target != 'object') target = {};
|
|
318
317
|
if (typeof options != 'object') return target;
|
|
319
318
|
Object.keys(options).forEach((option) => target[option] = options[option]);
|
|
320
319
|
return target;
|
|
321
320
|
}
|
|
321
|
+
const dev_id = _dev_id.startsWith('0x') ? _dev_id.substring(2) : _dev_id
|
|
322
322
|
|
|
323
323
|
const ld = this.localData.by_id[dev_id];
|
|
324
324
|
const gd = this.localData.by_model[model_id];
|
package/lib/networkmap.js
CHANGED
|
@@ -4,6 +4,7 @@ class NetworkMap {
|
|
|
4
4
|
constructor(adapter) {
|
|
5
5
|
this.adapter = adapter;
|
|
6
6
|
this.adapter.on('message', this.onMessage.bind(this));
|
|
7
|
+
this.mapdata = undefined;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
start(zbController, stController) {
|
|
@@ -37,16 +38,23 @@ class NetworkMap {
|
|
|
37
38
|
switch (obj.command) {
|
|
38
39
|
case 'getMap':
|
|
39
40
|
if (obj && obj.message && typeof obj.message === 'object') {
|
|
40
|
-
this.getMap(obj.from, obj.command, obj.callback);
|
|
41
|
+
this.getMap(obj.from, obj.command, obj.message, obj.callback);
|
|
41
42
|
}
|
|
42
43
|
break;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
getMap(from, command, callback) {
|
|
48
|
+
getMap(from, command, message, callback) {
|
|
49
|
+
if (message.forcebuild) this.mapdata = undefined;
|
|
50
|
+
if (this.mapdata) {
|
|
51
|
+
this.mapdata.errors = {};
|
|
52
|
+
this.adapter.sendTo(from, command, this.mapdata, callback)
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
48
55
|
if (this.zbController) {
|
|
49
56
|
this.zbController.getMap(networkmap => {
|
|
57
|
+
this.mapdata = networkmap;
|
|
50
58
|
this.adapter.log.debug(`getMap result: ${JSON.stringify(networkmap)}`);
|
|
51
59
|
this.adapter.sendTo(from, command, networkmap, callback);
|
|
52
60
|
});
|