iobroker.zigbee 3.0.5 → 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 +34 -0
- package/admin/admin.js +475 -230
- package/admin/i18n/de/translations.json +16 -16
- package/admin/index_m.html +84 -91
- package/admin/tab_m.html +38 -16
- package/docs/de/readme.md +1 -1
- package/docs/en/readme.md +4 -2
- package/io-package.json +35 -28
- package/lib/DeviceDebug.js +25 -2
- package/lib/binding.js +8 -8
- package/lib/commands.js +386 -326
- package/lib/developer.js +2 -2
- package/lib/devices.js +13 -9
- package/lib/exclude.js +1 -1
- package/lib/exposes.js +56 -24
- package/lib/groups.js +408 -73
- package/lib/localConfig.js +23 -12
- package/lib/networkmap.js +10 -2
- package/lib/states.js +32 -2
- package/lib/statescontroller.js +361 -209
- package/lib/utils.js +7 -5
- package/lib/zbDelayedAction.js +4 -4
- package/lib/zbDeviceAvailability.js +102 -46
- package/lib/zbDeviceConfigure.js +7 -0
- package/lib/zbDeviceEvent.js +40 -7
- package/lib/zigbeecontroller.js +552 -75
- package/main.js +168 -505
- package/package.json +8 -11
- package/lib/tools.js +0 -55
package/lib/zigbeecontroller.js
CHANGED
|
@@ -11,7 +11,6 @@ const DeviceAvailabilityExt = require('./zbDeviceAvailability');
|
|
|
11
11
|
const DeviceConfigureExt = require('./zbDeviceConfigure');
|
|
12
12
|
const DeviceEventExt = require('./zbDeviceEvent');
|
|
13
13
|
const DelayedActionExt = require('./zbDelayedAction');
|
|
14
|
-
const Groups = require('./groups');
|
|
15
14
|
const utils = require('./utils');
|
|
16
15
|
|
|
17
16
|
const { access, constants } =require('node:fs/promises');
|
|
@@ -67,7 +66,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
67
66
|
this.transmitPower = 0;
|
|
68
67
|
this.herdsmanStarted = false;
|
|
69
68
|
this.extensions = [
|
|
70
|
-
new DeviceAvailabilityExt(this, {}),
|
|
69
|
+
new DeviceAvailabilityExt(this, {}, adapter.config),
|
|
71
70
|
new DeviceConfigureExt(this, {}),
|
|
72
71
|
new DeviceEventExt(this, {}),
|
|
73
72
|
new DelayedActionExt(this, {}),
|
|
@@ -75,6 +74,10 @@ class ZigbeeController extends EventEmitter {
|
|
|
75
74
|
this.herdsmanTimeoutRegexp = new RegExp(/(\d+)ms/);
|
|
76
75
|
this.herdsmanLogSettings = {};
|
|
77
76
|
this.debugActive = true;
|
|
77
|
+
this.ListDevicesAtStart = adapter.config.listDevicesAtStart;
|
|
78
|
+
this.deviceQueryActive = [];
|
|
79
|
+
this.storedOptions = undefined;
|
|
80
|
+
this.isConfigured = false;
|
|
78
81
|
|
|
79
82
|
}
|
|
80
83
|
|
|
@@ -102,7 +105,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
configure(options) {
|
|
105
|
-
|
|
108
|
+
this.storedOptions = options;
|
|
106
109
|
if (options.transmitPower != undefined) {
|
|
107
110
|
this.transmitPower = options.transmitPower;
|
|
108
111
|
}
|
|
@@ -117,6 +120,8 @@ class ZigbeeController extends EventEmitter {
|
|
|
117
120
|
|
|
118
121
|
this.powerText = powerLevels[this.transmitPower] || 'normal';
|
|
119
122
|
}
|
|
123
|
+
this.readAtAnnounce = this.adapter.config.readAtAnnounce;
|
|
124
|
+
this.warnOnDeviceAnnouncement = this.adapter.config.warnOnDeviceAnnouncement;
|
|
120
125
|
|
|
121
126
|
//this.info(` --> transmitPower : ${this.powerText}`);
|
|
122
127
|
|
|
@@ -156,17 +161,18 @@ class ZigbeeController extends EventEmitter {
|
|
|
156
161
|
herdsmanSettings.transmitPower = options.transmitPower;
|
|
157
162
|
}
|
|
158
163
|
this.disableLed = options.disableLed;
|
|
159
|
-
this.warnOnDeviceAnnouncement = options.warnOnDeviceAnnouncement;
|
|
160
164
|
this.herdsmanLogSettings.panID = herdsmanSettings.network.panID;
|
|
161
165
|
this.herdsmanLogSettings.channel = herdsmanSettings.network.channelList[0];
|
|
162
166
|
this.herdsmanLogSettings.extendedPanID = utils.byteArrayToString(herdsmanSettings.network.extendedPanID);
|
|
163
167
|
this.herdsman = new ZigbeeHerdsman.Controller(herdsmanSettings, this.adapter.log);
|
|
164
168
|
this.callExtensionMethod('setOptions', [{
|
|
165
|
-
|
|
169
|
+
pingCluster: this.adapter.config.pingCluster,
|
|
170
|
+
startReadDelay: this.adapter.config.readAllAtStart ? this.adapter.config.startReadDelay : 0,
|
|
166
171
|
disableForcedPing: false,
|
|
167
172
|
pingTimeout: 300,
|
|
168
173
|
pingCount: 3
|
|
169
174
|
}]);
|
|
175
|
+
this.isConfigured = true;
|
|
170
176
|
}
|
|
171
177
|
|
|
172
178
|
async stopHerdsman() {
|
|
@@ -179,11 +185,15 @@ class ZigbeeController extends EventEmitter {
|
|
|
179
185
|
catch (error) {
|
|
180
186
|
this.emit('pairing', `error stopping zigbee-herdsman: ${error && error.message ? error.message : 'no reason given'}`);
|
|
181
187
|
}
|
|
188
|
+
this.isConfigured = false;
|
|
189
|
+
delete this.herdsman;
|
|
190
|
+
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
// Start controller
|
|
185
194
|
async start() {
|
|
186
195
|
try {
|
|
196
|
+
if (!this.isConfigured) this.configure(this.storedOptions);
|
|
187
197
|
this.emit('pairing',`Starting zigbee-herdsman...`);
|
|
188
198
|
this.powerText = '';
|
|
189
199
|
if (this.transmitPower !== '0') {
|
|
@@ -207,7 +217,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
207
217
|
this.herdsman.on('permitJoinChanged', this.handlePermitJoinChanged.bind(this));
|
|
208
218
|
|
|
209
219
|
this.info('Starting Zigbee-Herdsman');
|
|
210
|
-
await this.herdsman.start();
|
|
220
|
+
const result = await this.herdsman.start();
|
|
211
221
|
this.herdsmanStarted = true;
|
|
212
222
|
const cv = await this.herdsman.getCoordinatorVersion();
|
|
213
223
|
const MetaSt = `${cv.meta.transportrev ? cv.meta.transportrev : 'X'}-${cv.meta.product ? cv.meta.product : 'X'}.${cv.meta.majorrel ? cv.meta.majorrel : 'X'}.${cv.meta.minorrel ? cv.meta.minorrel : 'X'}.${cv.meta.maintrel ? cv.meta.maintrel : 'X'}`;
|
|
@@ -272,8 +282,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
272
282
|
// get the model description for the known devices
|
|
273
283
|
const entity = await this.resolveEntity(device);
|
|
274
284
|
if (!entity) {
|
|
275
|
-
this.
|
|
276
|
-
//this.emit('pairing','failed to resolve Entity for ' + device.ieeeAddr)
|
|
285
|
+
this.debug('failed to resolve Entity for ' + device.ieeeAddr);
|
|
277
286
|
continue;
|
|
278
287
|
}
|
|
279
288
|
//await this.adapter.stController.AddModelFromHerdsman(device, entity.mapped.model);
|
|
@@ -294,19 +303,20 @@ class ZigbeeController extends EventEmitter {
|
|
|
294
303
|
` (addr ${entity.device.networkAddress}): ` +
|
|
295
304
|
(entity.mapped ? `${entity.mapped.model} - ${entity.mapped.vendor} ${entity.mapped.description} ` : `Unsupported (model ${entity.device.modelID})`) +
|
|
296
305
|
`(${entity.device.type})`);
|
|
297
|
-
|
|
298
|
-
|
|
306
|
+
if (this.ListDevicesAtStart) this.info(msg);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const Groups = await this.getGroups();
|
|
310
|
+
for (const group of Groups) {
|
|
311
|
+
if (this.ListDevicesAtStart) this.info(`${group.stateName} (addr ${group.id}): Group with ${group.size} members.`);
|
|
299
312
|
}
|
|
300
313
|
|
|
301
314
|
// Log zigbee clients on startup
|
|
302
315
|
// const devices = await this.getClients();
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.info(`No devices are currently joined.`);
|
|
308
|
-
this.emit('pairing',`No devices are currently joined.`);
|
|
309
|
-
}
|
|
316
|
+
const gm = Groups.length > 0 ? `and ${Groups.length} group${Groups.length > 1?'s':''} ` : '';
|
|
317
|
+
const m = deviceCount > 0 ? `${deviceCount} devices ${gm}are part of the network` : `No devices ${gm}`;
|
|
318
|
+
this.info(m);
|
|
319
|
+
this.emit('pairing',m)
|
|
310
320
|
this.callExtensionMethod('onZigbeeStarted', []);
|
|
311
321
|
}
|
|
312
322
|
catch (error) {
|
|
@@ -395,6 +405,15 @@ class ZigbeeController extends EventEmitter {
|
|
|
395
405
|
}
|
|
396
406
|
}
|
|
397
407
|
|
|
408
|
+
getGroupIterator() {
|
|
409
|
+
if (this.herdsman.database) {
|
|
410
|
+
this.herdsman.getGroupsIterator()
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
return[].values();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
398
417
|
async getGroups() {
|
|
399
418
|
try {
|
|
400
419
|
if (this.herdsman) {
|
|
@@ -495,6 +514,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
495
514
|
ieee: device.ieeeAddr,
|
|
496
515
|
model: device.modelID,
|
|
497
516
|
epid,
|
|
517
|
+
deviceNetworkAddress: nwk,
|
|
498
518
|
ep: member
|
|
499
519
|
});
|
|
500
520
|
}
|
|
@@ -513,9 +533,10 @@ class ZigbeeController extends EventEmitter {
|
|
|
513
533
|
return members;
|
|
514
534
|
}
|
|
515
535
|
|
|
516
|
-
getDevice(key) {
|
|
536
|
+
async getDevice(key) {
|
|
517
537
|
try {
|
|
518
|
-
|
|
538
|
+
const dev = await this.herdsman.getDeviceByIeeeAddr(key);
|
|
539
|
+
return dev;
|
|
519
540
|
}
|
|
520
541
|
catch (error) {
|
|
521
542
|
this.error(`getDeviceByIeeeAddr: ${(error && error.message ? error.message : 'no error message')}`);
|
|
@@ -549,7 +570,9 @@ class ZigbeeController extends EventEmitter {
|
|
|
549
570
|
key: key,
|
|
550
571
|
message: 'success'
|
|
551
572
|
}
|
|
552
|
-
if (typeof key === 'object')
|
|
573
|
+
if (typeof key === 'object') {
|
|
574
|
+
return rv;
|
|
575
|
+
}
|
|
553
576
|
if (typeof key === 'number') {
|
|
554
577
|
rv.kind = 'group';
|
|
555
578
|
return rv;
|
|
@@ -579,8 +602,11 @@ class ZigbeeController extends EventEmitter {
|
|
|
579
602
|
return rv;
|
|
580
603
|
}
|
|
581
604
|
|
|
605
|
+
async getGroup(id) {
|
|
606
|
+
return await this.herdsman.getGroupByID(id);
|
|
607
|
+
}
|
|
608
|
+
|
|
582
609
|
async resolveEntity(key, ep) {
|
|
583
|
-
// this.warn('resolve entity with key of tyoe ' + typeof (key));
|
|
584
610
|
try {
|
|
585
611
|
const _key = await this.analyzeKey(key);
|
|
586
612
|
if (_key.message !== 'success') return undefined;
|
|
@@ -592,6 +618,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
592
618
|
device: coordinator,
|
|
593
619
|
endpoint: coordinator.getEndpoint(1),
|
|
594
620
|
name: 'Coordinator',
|
|
621
|
+
options:{}
|
|
595
622
|
};
|
|
596
623
|
}
|
|
597
624
|
if (_key.kind === 'group') {
|
|
@@ -602,16 +629,39 @@ class ZigbeeController extends EventEmitter {
|
|
|
602
629
|
return {
|
|
603
630
|
type: 'group',
|
|
604
631
|
mapped: group,
|
|
605
|
-
group,
|
|
632
|
+
device: group,
|
|
633
|
+
endpoint: group,
|
|
634
|
+
//group,
|
|
606
635
|
name: `Group ${_key.key}`,
|
|
636
|
+
options: {},
|
|
607
637
|
};
|
|
608
638
|
|
|
609
639
|
}
|
|
610
|
-
if (_key.kind === 'ieee')
|
|
611
|
-
const device = _key.key;
|
|
640
|
+
//if (_key.kind === 'ieee')
|
|
641
|
+
const device = (_key.kind === 'ieee' ? this.herdsman.getDeviceByIeeeAddr(_key.key) : key);
|
|
642
|
+
if (device && device.model === 'group') {
|
|
643
|
+
return {
|
|
644
|
+
type: 'group',
|
|
645
|
+
mapped: device,
|
|
646
|
+
device,
|
|
647
|
+
endpoint: device,
|
|
648
|
+
name: `Group ${device.groupID}`,
|
|
649
|
+
options:{}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
612
652
|
if (device) {
|
|
613
653
|
const t = Date.now();
|
|
614
|
-
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
|
654
|
+
const mapped = await zigbeeHerdsmanConverters.findByDevice(device, false);
|
|
655
|
+
if (!mapped) {
|
|
656
|
+
if (device.type === 'Coordinator')
|
|
657
|
+
return {
|
|
658
|
+
type: 'device',
|
|
659
|
+
device: device,
|
|
660
|
+
endpoint: device.getEndpoint(1),
|
|
661
|
+
name: 'Coordinator',
|
|
662
|
+
};
|
|
663
|
+
this.warn(`Resolve Entity did not manage to find a mapped device for ${device.ieeeAddr} of type ${device.modelID}`);
|
|
664
|
+
}
|
|
615
665
|
const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null;
|
|
616
666
|
let endpoint;
|
|
617
667
|
if (endpoints && ep != undefined && endpoints[ep]) {
|
|
@@ -626,6 +676,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
626
676
|
endpoint = device.endpoints[0];
|
|
627
677
|
}
|
|
628
678
|
}
|
|
679
|
+
const options = this.adapter.stController.localConfig.getOptions(device.ieeeAddr, mapped.model);
|
|
629
680
|
return {
|
|
630
681
|
type: 'device',
|
|
631
682
|
device,
|
|
@@ -633,8 +684,12 @@ class ZigbeeController extends EventEmitter {
|
|
|
633
684
|
endpoint,
|
|
634
685
|
endpoints: device.endpoints,
|
|
635
686
|
name: device._ieeeAddr,
|
|
687
|
+
options:options
|
|
636
688
|
};
|
|
637
689
|
}
|
|
690
|
+
else {
|
|
691
|
+
this.debug(`resolve_entity failed for ${JSON.stringify(_key)}`);
|
|
692
|
+
}
|
|
638
693
|
}
|
|
639
694
|
catch (error)
|
|
640
695
|
{
|
|
@@ -676,10 +731,13 @@ class ZigbeeController extends EventEmitter {
|
|
|
676
731
|
}
|
|
677
732
|
|
|
678
733
|
try {
|
|
679
|
-
if (this.HerdsmanStarted)
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
734
|
+
if (this.HerdsmanStarted) {
|
|
735
|
+
await this.permitJoin(0);
|
|
736
|
+
await this.herdsman.stop();
|
|
737
|
+
this.HerdsmanStarted = false;
|
|
738
|
+
this.info('zigbecontroller stopped successfully');
|
|
739
|
+
}
|
|
740
|
+
this.info('zigbecontroller stopped successfully - ZH was not running');
|
|
683
741
|
} catch (error) {
|
|
684
742
|
this.sendError(error);
|
|
685
743
|
if (this.herdsmanStarted) {
|
|
@@ -700,14 +758,18 @@ class ZigbeeController extends EventEmitter {
|
|
|
700
758
|
}
|
|
701
759
|
|
|
702
760
|
// Permit join
|
|
703
|
-
async permitJoin(permitTime, devid
|
|
761
|
+
async permitJoin(permitTime, devid) {
|
|
704
762
|
try {
|
|
705
763
|
this._permitJoinTime = permitTime;
|
|
706
764
|
await this.herdsman.permitJoin(permitTime);
|
|
765
|
+
this._permitJoinDevice = (permitTime > 0 && devid ? devid : '');
|
|
707
766
|
} catch (e) {
|
|
767
|
+
this._permitJoinTime = 0;
|
|
708
768
|
this.sendError(e);
|
|
709
769
|
this.error(`Failed to open the network: ${e.stack}`);
|
|
770
|
+
return false;
|
|
710
771
|
}
|
|
772
|
+
return true;
|
|
711
773
|
}
|
|
712
774
|
|
|
713
775
|
async handlePermitJoinChanged(data)
|
|
@@ -730,6 +792,7 @@ class ZigbeeController extends EventEmitter {
|
|
|
730
792
|
this._permitJoinInterval = null;
|
|
731
793
|
// this.emit('pairing', 'Pairing time left', 0);
|
|
732
794
|
this.emit('pairing', 'Closing network.',0);
|
|
795
|
+
this._permitJoinDevice = '';
|
|
733
796
|
}
|
|
734
797
|
}
|
|
735
798
|
catch (error) {
|
|
@@ -774,8 +837,16 @@ class ZigbeeController extends EventEmitter {
|
|
|
774
837
|
}
|
|
775
838
|
} catch (error) {
|
|
776
839
|
this.sendError(error);
|
|
777
|
-
|
|
778
|
-
|
|
840
|
+
let message = 'no reason given';
|
|
841
|
+
if (error && error.message) {
|
|
842
|
+
if (error.message.includes('AREQ - ZDO - mgmtLeaveRsp after'))
|
|
843
|
+
message = `No response to mgmtLeaveRequest from the device - device may be offline.`;
|
|
844
|
+
else
|
|
845
|
+
message = error.message;
|
|
846
|
+
|
|
847
|
+
}
|
|
848
|
+
this.warn(`Failed to remove ${deviceID ? 'device ' + deviceID : 'unspecified device'}: ${message}`);
|
|
849
|
+
callback && callback(`Failed to remove ${deviceID ? 'device ' + deviceID : 'unspecified device'}: ${message}`);
|
|
779
850
|
}
|
|
780
851
|
}
|
|
781
852
|
|
|
@@ -806,41 +877,52 @@ class ZigbeeController extends EventEmitter {
|
|
|
806
877
|
if (this.debugActive) this.debug('handleDeviceAnnounce', message);
|
|
807
878
|
const entity = await this.resolveEntity(message.device || message.ieeeAddr);
|
|
808
879
|
const friendlyName = entity.name;
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
this.emit('pairing', `Device '${friendlyName}' announced itself`);
|
|
809
883
|
if (this.adapter.stController.checkDebugDevice(friendlyName)) {
|
|
810
884
|
this.emit('device_debug', {ID: Date.now(), data: {flag:'da', states:[{id: '--', value:'--', payload:message}] , IO:true} ,message:`Device '${friendlyName}' announced itself`});
|
|
811
885
|
}
|
|
812
|
-
|
|
813
|
-
this.warn(`Device '${friendlyName}' announced itself`);
|
|
886
|
+
if (this.warnOnDeviceAnnouncement) {
|
|
887
|
+
this.warn(`Device '${friendlyName}' announced itself${this.readAtAnnounce ? ', trying to read its status' : ''}`);
|
|
814
888
|
} else {
|
|
815
|
-
this.info(`Device '${friendlyName}' announced itself`);
|
|
889
|
+
this.info(`Device '${friendlyName}' announced itself${this.readAtAnnounce ? ', trying to read its status' : ''}`);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (entity.device && entity.device.modelID && entity.device.interviewState != 'SUCCESSFUL') {
|
|
893
|
+
this.info(`ignoring device announcement for ${entity.device.modelID} due to interview state ${entity.device.interviewState}`);
|
|
894
|
+
this.emit('pairing', `device interview state is ${entity.device.interviewState}`)
|
|
895
|
+
return;
|
|
816
896
|
}
|
|
817
897
|
|
|
898
|
+
const networkOpen = this.herdsman.getPermitJoin();
|
|
899
|
+
/*
|
|
900
|
+
if (networkOpen && entity.device && entity.device.modelID && entity.device.interviewState != 'IN_PROGRESS')
|
|
901
|
+
{
|
|
902
|
+
//entity.device.modelID = entity.device._modelID;
|
|
903
|
+
//this.emit('new', entity);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
*/
|
|
818
907
|
try {
|
|
819
908
|
if (entity && entity.mapped) {
|
|
820
909
|
this.callExtensionMethod(
|
|
821
910
|
'onZigbeeEvent',
|
|
822
|
-
[{'device': message.device, 'type': 'deviceAnnounce'}, entity ? entity.mapped : null]);
|
|
911
|
+
[{'device': message.device, 'type': 'deviceAnnounce', options: entity.options || {}}, entity ? entity.mapped : null]);
|
|
912
|
+
this.callExtensionMethod('registerDevicePing', [message.device, entity]);
|
|
913
|
+
if (this.readAtAnnounce) await this.doDeviceQuery(message.device || message.ieeeAddr, Date.now(), false);
|
|
823
914
|
}
|
|
824
915
|
} catch (error) {
|
|
825
916
|
this.sendError(error);
|
|
826
|
-
this.error(`Failed to
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
this.emit('pairing', `Device '${friendlyName}' announced itself`);
|
|
830
|
-
if (!this.herdsman.getPermitJoin()) {
|
|
831
|
-
this.callExtensionMethod('registerDevicePing', [message.device, entity]);
|
|
832
|
-
}
|
|
833
|
-
// if has modelID so can create device
|
|
834
|
-
if (entity.device && entity.device._modelID) {
|
|
835
|
-
entity.device.modelID = entity.device._modelID;
|
|
836
|
-
this.emit('new', entity);
|
|
917
|
+
this.error(`Failed to handleDeviceAnnounce ${error.stack}`);
|
|
837
918
|
}
|
|
838
919
|
}
|
|
839
920
|
|
|
840
921
|
async handleDeviceJoined(message) {
|
|
841
922
|
if (this.debugActive) this.debug('handleDeviceJoined', message);
|
|
842
923
|
//const entity = await this.resolveEntity(message.device || message.ieeeAddr);
|
|
843
|
-
//this.emit('new', entity);
|
|
924
|
+
// this.emit('new', entity);
|
|
925
|
+
//if (entity && entity.mapped) this.callExtensionMethod([message, entity.mapped]);
|
|
844
926
|
}
|
|
845
927
|
|
|
846
928
|
async handleDeviceInterview(message) {
|
|
@@ -864,13 +946,13 @@ class ZigbeeController extends EventEmitter {
|
|
|
864
946
|
|
|
865
947
|
const log = {friendly_name: friendlyName, model, vendor, description, supported: true};
|
|
866
948
|
this.emit('pairing', 'Interview successful', JSON.stringify(log));
|
|
867
|
-
entity.device.modelID = entity.device._modelID;
|
|
868
|
-
this.emit('new', entity);
|
|
869
|
-
// send to extensions again (for configure)
|
|
949
|
+
//entity.device.modelID = entity.device._modelID;
|
|
870
950
|
this.callExtensionMethod(
|
|
871
951
|
'onZigbeeEvent',
|
|
872
|
-
[message, entity
|
|
952
|
+
[{...message,type:'deviceInterview', options: entity.options || {}}, entity.mapped],
|
|
873
953
|
);
|
|
954
|
+
this.emit('new', entity);
|
|
955
|
+
// send to extensions again (for configure)
|
|
874
956
|
} else {
|
|
875
957
|
if (this.debugActive) this.debug(
|
|
876
958
|
`Device '${friendlyName}' with Zigbee model '${message.device.modelID}' is NOT supported, ` +
|
|
@@ -878,15 +960,23 @@ class ZigbeeController extends EventEmitter {
|
|
|
878
960
|
);
|
|
879
961
|
const frName = {friendly_name: friendlyName, supported: false};
|
|
880
962
|
this.emit('pairing', 'Interview successful', JSON.stringify(frName));
|
|
881
|
-
entity.device.modelID = entity.device._modelID;
|
|
963
|
+
//entity.device.modelID = entity.device._modelID;
|
|
882
964
|
this.emit('new', entity);
|
|
883
965
|
}
|
|
884
966
|
} else if (message.status === 'failed') {
|
|
885
967
|
this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! `);
|
|
886
968
|
//this.error(`Failed to interview '${friendlyName}', device has not successfully been paired. Try again !!!!!!!!!! ${message.error}`);
|
|
887
969
|
this.emit('pairing', 'Interview failed', friendlyName);
|
|
970
|
+
this.callExtensionMethod(
|
|
971
|
+
'onZigbeeEvent',
|
|
972
|
+
[message, entity ? entity.mapped : null],
|
|
973
|
+
);
|
|
888
974
|
} else {
|
|
889
975
|
if (message.status === 'started') {
|
|
976
|
+
this.callExtensionMethod(
|
|
977
|
+
'onZigbeeEvent',
|
|
978
|
+
[message, entity ? entity.mapped : null],
|
|
979
|
+
);
|
|
890
980
|
this.info(`Starting interview of '${friendlyName}'`);
|
|
891
981
|
this.emit('pairing', 'Interview started', friendlyName);
|
|
892
982
|
}
|
|
@@ -899,6 +989,11 @@ class ZigbeeController extends EventEmitter {
|
|
|
899
989
|
|
|
900
990
|
async handleMessage(data) {
|
|
901
991
|
if (this.debugActive) this.debug(`handleMessage`, data);
|
|
992
|
+
|
|
993
|
+
const is = data.device.interviewState;
|
|
994
|
+
if (is != 'SUCCESSFUL' && is != 'FAILED') {
|
|
995
|
+
this.info(`message ${JSON.stringify(data)} received during interview.`)
|
|
996
|
+
}
|
|
902
997
|
const entity = await this.resolveEntity(data.device || data.ieeeAddr);
|
|
903
998
|
const name = (entity && entity._modelID) ? entity._modelID : data.device.ieeeAddr;
|
|
904
999
|
if (this.debugActive) this.debug(
|
|
@@ -907,11 +1002,10 @@ class ZigbeeController extends EventEmitter {
|
|
|
907
1002
|
(data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``)
|
|
908
1003
|
);
|
|
909
1004
|
this.event(data.type, entity, data);
|
|
910
|
-
|
|
911
1005
|
// Call extensions
|
|
912
1006
|
this.callExtensionMethod(
|
|
913
1007
|
'onZigbeeEvent',
|
|
914
|
-
[data, entity ? entity.mapped : null],
|
|
1008
|
+
[{...data, options:entity.options || {}}, entity ? entity.mapped : null],
|
|
915
1009
|
);
|
|
916
1010
|
}
|
|
917
1011
|
|
|
@@ -1000,7 +1094,14 @@ class ZigbeeController extends EventEmitter {
|
|
|
1000
1094
|
}
|
|
1001
1095
|
}
|
|
1002
1096
|
|
|
1097
|
+
processSyncStatesList(deviceId, model, syncStateList) {
|
|
1098
|
+
syncStateList.forEach((syncState) => {
|
|
1099
|
+
this.emit('acknowledge_state',deviceId, model, syncState.stateDesc, syncState.value);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1003
1102
|
|
|
1103
|
+
// publishing to the zigbee network
|
|
1104
|
+
// publish raw zigbee data
|
|
1004
1105
|
async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) {
|
|
1005
1106
|
const entity = await this.resolveEntity(deviceID, ep);
|
|
1006
1107
|
const device = entity.device;
|
|
@@ -1024,37 +1125,413 @@ class ZigbeeController extends EventEmitter {
|
|
|
1024
1125
|
cfg = {};
|
|
1025
1126
|
}
|
|
1026
1127
|
|
|
1128
|
+
// try { NO Try/Catach here, this is ONLY called from the developer tab and error handling is done there
|
|
1129
|
+
|
|
1130
|
+
if (type === 'foundation') {
|
|
1131
|
+
cfg.disableDefaultResponse = true;
|
|
1132
|
+
let result;
|
|
1133
|
+
if (cmd === 'configReport') {
|
|
1134
|
+
result = await endpoint.configureReporting(cid, zclData, cfg);
|
|
1135
|
+
} else {
|
|
1136
|
+
if (cmd === 'read' && !Array.isArray(zclData))
|
|
1137
|
+
result = await endpoint[cmd](cid, Object.keys(zclData), cfg);
|
|
1138
|
+
else
|
|
1139
|
+
result = await endpoint[cmd](cid, zclData, cfg);
|
|
1140
|
+
}
|
|
1141
|
+
callback && callback(undefined, result);
|
|
1142
|
+
} else if (type === 'functionalResp') {
|
|
1143
|
+
cfg.disableDefaultResponse = false;
|
|
1144
|
+
const result = await endpoint.commandResponse(cid, cmd, zclData, cfg, zclSeqNum);
|
|
1145
|
+
callback && callback(undefined, result);
|
|
1146
|
+
} else {
|
|
1147
|
+
cfg.disableDefaultResponse = false;
|
|
1148
|
+
const result = await endpoint.command(cid, cmd, zclData, cfg);
|
|
1149
|
+
callback && callback(undefined, result);
|
|
1150
|
+
}
|
|
1151
|
+
/* }
|
|
1152
|
+
catch (error)
|
|
1153
|
+
{
|
|
1154
|
+
//this.error(`error sending ${type} ${cmd} to endpoint: ${(error && error.message ? error.message : 'no error message')} ${(error && error.stack ? error.stack : 'no call stack')}`)
|
|
1155
|
+
}
|
|
1156
|
+
*/
|
|
1157
|
+
}
|
|
1158
|
+
// publish via converter
|
|
1159
|
+
//
|
|
1160
|
+
async publishFromState(deviceId, model, stateModel, stateList, options, debugID, has_elevated_debug) {
|
|
1161
|
+
let isGroup = false;
|
|
1162
|
+
//const has_elevated_debug = this.stController.checkDebugDevice(deviceId)
|
|
1163
|
+
|
|
1164
|
+
if (has_elevated_debug)
|
|
1165
|
+
{
|
|
1166
|
+
const stateNames = stateList.map((state) => state.stateDesc.id);
|
|
1167
|
+
const message = `Publishing to ${deviceId} of model ${model} with ${stateNames.join(', ')}`;
|
|
1168
|
+
this.emit('device_debug', { ID:debugID, data: { ID: deviceId, flag: '03', IO:false }, message: message});
|
|
1169
|
+
}
|
|
1170
|
+
else
|
|
1171
|
+
if (this.debugActive) this.debug(`main publishFromState : ${deviceId} ${model} ${safeJsonStringify(stateList)}`);
|
|
1172
|
+
if (model === 'group') {
|
|
1173
|
+
isGroup = true;
|
|
1174
|
+
deviceId = parseInt(deviceId);
|
|
1175
|
+
}
|
|
1027
1176
|
try {
|
|
1177
|
+
const entity = await this.resolveEntity(deviceId);
|
|
1178
|
+
if (this.debugActive) this.debug(`entity: ${deviceId} ${model} ${safeJsonStringify(entity)}`);
|
|
1179
|
+
const mappedModel = entity ? entity.mapped : undefined;
|
|
1180
|
+
|
|
1181
|
+
if (!mappedModel) {
|
|
1182
|
+
if (this.debugActive) this.debug(`No mapped model for ${model}`);
|
|
1183
|
+
if (has_elevated_debug) {
|
|
1184
|
+
const message=`No mapped model ${deviceId} (model ${model})`;
|
|
1185
|
+
this.emit('device_debug', { ID:debugID, data: { error: 'NOMODEL' , IO:false }, message: message});
|
|
1186
|
+
}
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1028
1189
|
|
|
1029
|
-
if (
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1190
|
+
if (!mappedModel.toZigbee)
|
|
1191
|
+
{
|
|
1192
|
+
this.error(`No toZigbee in mapped model for ${model}`);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
stateList.forEach(async changedState => {
|
|
1197
|
+
const stateDesc = changedState.stateDesc;
|
|
1198
|
+
const value = changedState.value;
|
|
1199
|
+
|
|
1200
|
+
let converter = undefined;
|
|
1201
|
+
let msg_counter = 0;
|
|
1202
|
+
if (stateDesc.isOption) {
|
|
1203
|
+
if (has_elevated_debug) {
|
|
1204
|
+
const message = `No converter needed on option state for ${deviceId} of type ${model}`;
|
|
1205
|
+
this.emit('device_debug', { ID:debugID, data: { flag: `SUCCESS` , IO:false }, message:message});
|
|
1206
|
+
}
|
|
1207
|
+
else
|
|
1208
|
+
if (this.debugActive) this.debug(`No converter needed on option state for ${deviceId} of type ${model}`);
|
|
1209
|
+
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
for (const c of mappedModel.toZigbee) {
|
|
1213
|
+
|
|
1214
|
+
if (!c.hasOwnProperty('convertSet')) continue;
|
|
1215
|
+
if (this.debugActive) this.debug(`Type of toZigbee is '${typeof c}', Contains key ${(c.hasOwnProperty('key')?JSON.stringify(c.key):'false ')}`)
|
|
1216
|
+
if (!c.hasOwnProperty('key'))
|
|
1217
|
+
{
|
|
1218
|
+
if (converter === undefined)
|
|
1219
|
+
{
|
|
1220
|
+
converter = c;
|
|
1221
|
+
if (has_elevated_debug) {
|
|
1222
|
+
const message = `Setting converter to keyless converter for ${deviceId} of type ${model}`;
|
|
1223
|
+
this.emit('device_debug', { ID:debugID, data: { flag: `s4.${msg_counter}` , IO:false }, message:message});
|
|
1224
|
+
}
|
|
1225
|
+
else
|
|
1226
|
+
if (this.debugActive) this.debug(`Setting converter to keyless converter for ${deviceId} of type ${model}`);
|
|
1227
|
+
msg_counter++;
|
|
1228
|
+
}
|
|
1229
|
+
else
|
|
1230
|
+
{
|
|
1231
|
+
if (has_elevated_debug)
|
|
1232
|
+
{
|
|
1233
|
+
const message = `ignoring keyless converter for ${deviceId} of type ${model}`;
|
|
1234
|
+
this.emit('device_debug', { ID:debugID, data: { flag: `i4.${msg_counter}` , IO:false} , message:message});
|
|
1235
|
+
}
|
|
1236
|
+
else
|
|
1237
|
+
if (this.debugActive) this.debug(`ignoring keyless converter for ${deviceId} of type ${model}`);
|
|
1238
|
+
msg_counter++;
|
|
1239
|
+
}
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
if (c.key.includes(stateDesc.prop) || c.key.includes(stateDesc.setattr) || c.key.includes(stateDesc.id))
|
|
1243
|
+
{
|
|
1244
|
+
const message = `${(converter===undefined?'Setting':'Overriding')}' converter to converter with key(s)'${JSON.stringify(c.key)}}`;
|
|
1245
|
+
if (has_elevated_debug) {
|
|
1246
|
+
this.emit('device_debugug', { ID:debugID, data: { flag: `${converter===undefined ? 's' : 'o'}4.${msg_counter}` , IO:false }, message:message});
|
|
1247
|
+
|
|
1248
|
+
}
|
|
1249
|
+
else
|
|
1250
|
+
if (this.debugActive) this.debug(message);
|
|
1251
|
+
converter = c;
|
|
1252
|
+
msg_counter++;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
if (converter === undefined) {
|
|
1256
|
+
const message = `No converter available for '${model}' with key '${stateDesc.id}' `;
|
|
1257
|
+
if (has_elevated_debug) {
|
|
1258
|
+
this.emit('device_debug', { ID:debugID, data: { error: 'NOCONV',states:[{id:stateDesc.id, value:value, payload:'no converter'}] , IO:false }, message:message});
|
|
1259
|
+
}
|
|
1260
|
+
else {
|
|
1261
|
+
this.info(message);
|
|
1262
|
+
}
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const preparedValue = (stateDesc.setter) ? stateDesc.setter(value, options) : value;
|
|
1267
|
+
const preparedOptions = (stateDesc.setterOpt) ? stateDesc.setterOpt(value, options) : {};
|
|
1268
|
+
|
|
1269
|
+
let syncStateList = [];
|
|
1270
|
+
if (stateModel && stateModel.syncStates) {
|
|
1271
|
+
stateModel.syncStates.forEach(syncFunct => {
|
|
1272
|
+
const res = syncFunct(stateDesc, value, options);
|
|
1273
|
+
if (res) {
|
|
1274
|
+
syncStateList = syncStateList.concat(res);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const epName = stateDesc.epname !== undefined ? stateDesc.epname : (stateDesc.prop || stateDesc.id);
|
|
1280
|
+
const key = stateDesc.setattr || stateDesc.prop || stateDesc.id;
|
|
1281
|
+
const message = `convert ${key} with value ${safeJsonStringify(preparedValue)} and options ${safeJsonStringify(preparedOptions)} for device ${deviceId} with Endpoint ${epName}`;
|
|
1282
|
+
if (has_elevated_debug) {
|
|
1283
|
+
this.emit('device_debug', { ID:debugID, data: { flag: '04', payload: {key:key, ep: stateDesc.epname, value:preparedValue, options:preparedOptions}, IO:false }, message:message});
|
|
1284
|
+
}
|
|
1285
|
+
else
|
|
1286
|
+
if (this.debugActive) this.debug(message);
|
|
1287
|
+
|
|
1288
|
+
let target;
|
|
1289
|
+
if (model === 'group') {
|
|
1290
|
+
target = entity.mapped;
|
|
1034
1291
|
} else {
|
|
1035
|
-
|
|
1036
|
-
|
|
1292
|
+
target = await this.resolveEntity(deviceId, epName);
|
|
1293
|
+
target = target.endpoint;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (this.debugActive) this.debug(`target: ${safeJsonStringify(target)}`);
|
|
1297
|
+
|
|
1298
|
+
const meta = {
|
|
1299
|
+
endpoint_name: epName,
|
|
1300
|
+
options: preparedOptions,
|
|
1301
|
+
device: entity.device,
|
|
1302
|
+
mapped: model === 'group' ? [] : mappedModel,
|
|
1303
|
+
message: {[key]: preparedValue},
|
|
1304
|
+
logger: this,
|
|
1305
|
+
state: {},
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
// new toZigbee
|
|
1309
|
+
if (preparedValue !== undefined && Object.keys(meta.message).filter(p => p.startsWith('state')).length > 0) {
|
|
1310
|
+
if (typeof preparedValue === 'number') {
|
|
1311
|
+
meta.message.state = preparedValue > 0 ? 'ON' : 'OFF';
|
|
1312
|
+
} else {
|
|
1313
|
+
meta.message.state = preparedValue;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (has_elevated_debug) {
|
|
1317
|
+
this.emit('device_debug', { ID:debugID, data: { states:[{id:stateDesc.id, value:value, payload:preparedValue, ep:stateDesc.epname}] , IO:false }});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
if (preparedOptions !== undefined) {
|
|
1321
|
+
if (preparedOptions.hasOwnProperty('state')) {
|
|
1322
|
+
meta.state = preparedOptions.state;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
try {
|
|
1327
|
+
const result = await converter.convertSet(target, key, preparedValue, meta);
|
|
1328
|
+
const message = `convert result ${safeJsonStringify(result)} for device ${deviceId}`;
|
|
1329
|
+
if (isGroup)
|
|
1330
|
+
this.emit('published', deviceId, model, stateModel, stateList, options, debugID, has_elevated_debug );
|
|
1331
|
+
if (has_elevated_debug) {
|
|
1332
|
+
this.emit('device_debug', { ID:debugID, data: { flag: 'SUCCESS' , IO:false }, message:message});
|
|
1333
|
+
}
|
|
1037
1334
|
else
|
|
1038
|
-
|
|
1335
|
+
if (this.debugActive) this.debug(message);
|
|
1336
|
+
if (result !== undefined) {
|
|
1337
|
+
if (stateModel && !isGroup && !stateDesc.noack) {
|
|
1338
|
+
this.emit('acknowledge_state', deviceId, model, stateDesc, value );
|
|
1339
|
+
}
|
|
1340
|
+
// process sync state list
|
|
1341
|
+
this.processSyncStatesList(deviceId, model, syncStateList);
|
|
1342
|
+
}
|
|
1343
|
+
else {
|
|
1344
|
+
if (has_elevated_debug) {
|
|
1345
|
+
const message = `Convert does not return a result result for ${key} with ${safeJsonStringify(preparedValue)} on device ${deviceId}.`;
|
|
1346
|
+
this.emit('device_debug', { ID:debugID, data: { flag: '06' , IO:false }, message:message});
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
if (has_elevated_debug) {
|
|
1351
|
+
const message = `caught error ${error && error.message ? error.message : 'no reason given'} when setting value for device ${deviceId}.`;
|
|
1352
|
+
this.emit('device_debug', { ID:debugID, data: { error: 'EXSET' , IO:false },message:message});
|
|
1353
|
+
}
|
|
1354
|
+
this.adapter.filterError(`Error ${error.code} on send command to ${deviceId}.` +
|
|
1355
|
+
` Error: ${error.stack}`, `Send command to ${deviceId} failed with`, error);
|
|
1039
1356
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1357
|
+
});
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
const message = `No entity for ${deviceId} : ${err && err.message ? err.message : 'no error message'}`;
|
|
1360
|
+
this.emit('device_debug', { ID:debugID, data: { error: 'EXPUB' , IO:false }, message:message});
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
extractEP(key, endpoints) {
|
|
1365
|
+
try {
|
|
1366
|
+
if (endpoints) for (const ep of Object.keys(endpoints)) {
|
|
1367
|
+
if (key.endsWith('_'+ep)) return { setattr: key.replace('_'+ep, ''), epname:ep }
|
|
1049
1368
|
}
|
|
1050
1369
|
}
|
|
1051
|
-
catch
|
|
1052
|
-
|
|
1053
|
-
|
|
1370
|
+
catch {
|
|
1371
|
+
return {};
|
|
1372
|
+
}
|
|
1373
|
+
return {};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// publish via payload
|
|
1377
|
+
//
|
|
1378
|
+
// This function is introduced to explicitly allow user level scripts to send Commands
|
|
1379
|
+
// directly to the zigbee device. It utilizes the zigbee-herdsman-converters to generate
|
|
1380
|
+
// the exact zigbee message to be sent and can be used to set device options which are
|
|
1381
|
+
// not exposed as states. It serves as a wrapper function for "publishFromState" with
|
|
1382
|
+
// extended parameter checking
|
|
1383
|
+
//
|
|
1384
|
+
// The payload can either be a JSON object or the string representation of a JSON object
|
|
1385
|
+
// The following keys are supported in the object:
|
|
1386
|
+
// device: name of the device. For a device zigbee.0.0011223344556677 this would be 0011223344556677
|
|
1387
|
+
// payload: The data to send to the device as JSON object (key/Value pairs)
|
|
1388
|
+
// endpoint: optional: the endpoint to send the data to, if supported.
|
|
1389
|
+
//
|
|
1390
|
+
async publishPayload(payload, debugID, has_elevated_debug) {
|
|
1391
|
+
let payloadObj = {};
|
|
1392
|
+
if (typeof payload === 'string') {
|
|
1393
|
+
try {
|
|
1394
|
+
payloadObj = JSON.parse(payload);
|
|
1395
|
+
} catch (e) {
|
|
1396
|
+
this.log.error(`Unable to parse ${safeJsonStringify(payload)}: ${safeJsonStringify(e)}`);
|
|
1397
|
+
return {
|
|
1398
|
+
success: false,
|
|
1399
|
+
error: `Unable to parse ${safeJsonStringify(payload)}: ${safeJsonStringify(e)}`
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
} else if (typeof payload === 'object') {
|
|
1403
|
+
payloadObj = payload;
|
|
1404
|
+
} else return { success: false, error: 'illegal type of payload: ' + typeof payload};
|
|
1405
|
+
|
|
1406
|
+
if (payloadObj.hasOwnProperty('device') && payloadObj.hasOwnProperty('payload')) {
|
|
1407
|
+
try {
|
|
1408
|
+
const isDevice = !payload.device.includes('group_');
|
|
1409
|
+
const stateList = [];
|
|
1410
|
+
const devID = isDevice ? `0x${payload.device}` : parseInt(payload.device.replace('group_', ''));
|
|
1411
|
+
|
|
1412
|
+
const entity = await this.resolveEntity(devID);
|
|
1413
|
+
if (!entity) {
|
|
1414
|
+
this.log.error(`Device ${safeJsonStringify(payloadObj.device)} not found`);
|
|
1415
|
+
return {success: false, error: `Device ${safeJsonStringify(payloadObj.device)} not found`};
|
|
1416
|
+
}
|
|
1417
|
+
const mappedModel = entity.mapped;
|
|
1418
|
+
if (!mappedModel) {
|
|
1419
|
+
this.log.error(`No Model for Device ${safeJsonStringify(payloadObj.device)}`);
|
|
1420
|
+
return {success: false, error: `No Model for Device ${safeJsonStringify(payloadObj.device)}`};
|
|
1421
|
+
}
|
|
1422
|
+
if (typeof payloadObj.payload !== 'object') {
|
|
1423
|
+
this.log.error(`Illegal payload type for ${safeJsonStringify(payloadObj.device)}`);
|
|
1424
|
+
return {success: false, error: `Illegal payload type for ${safeJsonStringify(payloadObj.device)}`};
|
|
1425
|
+
}
|
|
1426
|
+
const endpoints = mappedModel && mappedModel.endpoint ? mappedModel.endpoint(entity.device) : null;
|
|
1427
|
+
for (const key in payloadObj.payload) {
|
|
1428
|
+
if (payloadObj.payload[key] != undefined) {
|
|
1429
|
+
const datatype = typeof payloadObj.payload[key];
|
|
1430
|
+
const epobj = this.extractEP(key, endpoints);
|
|
1431
|
+
if (payloadObj.endpoint) {
|
|
1432
|
+
epobj.epname = payloadObj.endpoint;
|
|
1433
|
+
delete epobj.setattr;
|
|
1434
|
+
}
|
|
1435
|
+
stateList.push({
|
|
1436
|
+
stateDesc: {
|
|
1437
|
+
id: key,
|
|
1438
|
+
prop: key,
|
|
1439
|
+
role: 'state',
|
|
1440
|
+
type: datatype,
|
|
1441
|
+
noack:true,
|
|
1442
|
+
epname: epobj.epname,
|
|
1443
|
+
setattr: epobj.setattr,
|
|
1444
|
+
},
|
|
1445
|
+
value: payloadObj.payload[key],
|
|
1446
|
+
index: 0,
|
|
1447
|
+
timeout: 0,
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
try {
|
|
1452
|
+
await this.publishFromState(`0x${payload.device}`, payload.model, payload.stateModel, stateList, payload.options, debugID, has_elevated_debug);
|
|
1453
|
+
return {success: true};
|
|
1454
|
+
} catch (error) {
|
|
1455
|
+
this.log.error(`Error ${error.code} on send command to ${payload.device}.` + ` Error: ${error.stack} ` + `Send command to ${payload.device} failed with ` + error);
|
|
1456
|
+
this.adapter.filterError(`Error ${error.code} on send command to ${payload.device}.` + ` Error: ${error.stack}`, `Send command to ${payload.device} failed with`, error);
|
|
1457
|
+
return {success: false, error};
|
|
1458
|
+
}
|
|
1459
|
+
} catch (e) {
|
|
1460
|
+
return {success: false, error: e};
|
|
1461
|
+
}
|
|
1054
1462
|
}
|
|
1055
1463
|
|
|
1464
|
+
return {success: false, error: `missing parameter device or payload in message ${JSON.stringify(payload)}`};
|
|
1056
1465
|
}
|
|
1057
1466
|
|
|
1467
|
+
async doDeviceQuery(deviceId, debugID, elevated) {
|
|
1468
|
+
const entity = await this.resolveEntity(deviceId);
|
|
1469
|
+
if (this.debugActive) this.debug(`doDeviceQuery: resolveEntity for entity: ${deviceId} is ${safeJsonStringify(entity)}`);
|
|
1470
|
+
const mappedModel = entity ? entity.mapped : undefined;
|
|
1471
|
+
if (mappedModel) {
|
|
1472
|
+
const epmap = mappedModel.endpoint ? mappedModel.endpoint() : [];
|
|
1473
|
+
if (elevated) {
|
|
1474
|
+
const message = `Device query for '${entity.device.ieeeAddr}' triggered`;
|
|
1475
|
+
this.emit('device_debug', { ID:debugID, data: { flag: 'qs' ,states:[{id:'device_query', value:true, payload:'device_query'}], IO:false }, message:message});
|
|
1476
|
+
}
|
|
1477
|
+
else
|
|
1478
|
+
if (this.debugActive) this.debug(`Device query for '${entity.device.ieeeAddr}' started`);
|
|
1479
|
+
else this.info(`Device query for '${entity.device.ieeeAddr}' started`);
|
|
1480
|
+
|
|
1481
|
+
for (const converter of mappedModel.toZigbee) {
|
|
1482
|
+
if (converter.hasOwnProperty('convertGet')) {
|
|
1483
|
+
const sources = [];
|
|
1484
|
+
if (converter.endpoints && epmap) {
|
|
1485
|
+
for (const epname of converter.endpoints) {
|
|
1486
|
+
const source = entity.device.endpoints.find((id) => id.ID == epmap[epname]);
|
|
1487
|
+
if (source) sources.push(source);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
if (sources.length == 0) sources.push(entity.device.endpoints[0]);
|
|
1491
|
+
for (const source of sources) {
|
|
1492
|
+
try {
|
|
1493
|
+
await converter.convertGet(source, '', {device:entity.device});
|
|
1494
|
+
this.debug(`read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${entity.device.ieeeAddr}/${source.ID}' after device query`);
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
if (elevated) {
|
|
1497
|
+
const message = `Failed to read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${source.ID}' from query with '${error && error.message ? error.message : 'no error message'}`;
|
|
1498
|
+
this.warn(`ELEVATED OE02.1 ${message}`);
|
|
1499
|
+
this.emit('device_debug', { ID:debugID, data: { error: 'NOTREAD' , IO:false }, message:message });
|
|
1500
|
+
}
|
|
1501
|
+
else
|
|
1502
|
+
this.debug(`failed to read for state${converter.key.length ? '' : 's'} '${converter.key.join(',')}' of '${source.ID}'after device query`);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
if (elevated) {
|
|
1508
|
+
const message = `ELEVATED O07: Device query for '${entity.device.ieeeAddr}}' complete`;
|
|
1509
|
+
this.emit('device_debug', { ID:debugID, data: { flag: 'qe' , IO:false }, message:message});
|
|
1510
|
+
}
|
|
1511
|
+
else
|
|
1512
|
+
this.info(`Device query for '${entity.device.ieeeAddr}' complete`);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
async deviceQuery(deviceId, debugID, elevated, callback) {
|
|
1517
|
+
if (this.deviceQueryActive.includes (deviceId)) {
|
|
1518
|
+
this.info(`Device query for ${deviceId} is still active.`);
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
this.deviceQueryActive.push(deviceId);
|
|
1522
|
+
try {
|
|
1523
|
+
await this.doDeviceQuery(deviceId, debugID, elevated);
|
|
1524
|
+
}
|
|
1525
|
+
catch (e) {
|
|
1526
|
+
this.warn('error in doDeviceQuery')
|
|
1527
|
+
}
|
|
1528
|
+
const idx = this.deviceQueryActive.indexOf(deviceId)
|
|
1529
|
+
if (idx > -1)
|
|
1530
|
+
this.deviceQueryActive.splice(idx, 1);
|
|
1531
|
+
if (callback) callback(deviceId);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
|
|
1058
1535
|
async addDevToGroup(devId, groupId, epid) {
|
|
1059
1536
|
try {
|
|
1060
1537
|
if (this.debugActive) this.debug(`called addDevToGroup with ${devId}, ${groupId}, ${epid}`);
|