iobroker.zigbee 2.0.2 → 2.0.3
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 +64 -35
- package/admin/admin.js +211 -19
- package/admin/img/philips_hue_lom001.png +0 -0
- package/admin/tab_m.html +13 -8
- package/docs/tutorial/groups-1.png +0 -0
- package/docs/tutorial/groups-2.png +0 -0
- package/docs/tutorial/tab-dev-1.png +0 -0
- package/io-package.json +15 -49
- package/lib/DeviceDebug.js +80 -0
- package/lib/commands.js +16 -1
- package/lib/developer.js +0 -0
- package/lib/exposes.js +1 -1
- package/lib/groups.js +6 -8
- package/lib/localConfig.js +1 -2
- package/lib/ota.js +6 -6
- package/lib/statescontroller.js +270 -97
- package/lib/zbDeviceAvailability.js +2 -2
- package/lib/zbDeviceConfigure.js +22 -15
- package/lib/zigbeecontroller.js +13 -6
- package/main.js +132 -194
- package/package.json +2 -2
package/lib/statescontroller.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const safeJsonStringify = require('./json');
|
|
4
|
+
const { EventEmitter } = require('events');
|
|
4
5
|
const statesMapping = require('./devices');
|
|
5
|
-
const getAdId = require('./utils')
|
|
6
|
-
const getZbId = require('./utils').getZbId;
|
|
6
|
+
const { getAdId, getZbId } = require('./utils');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const axios = require('axios');
|
|
9
9
|
const localConfig = require('./localConfig');
|
|
@@ -14,7 +14,8 @@ const localConfig = require('./localConfig');
|
|
|
14
14
|
const { exec } = require('child_process');
|
|
15
15
|
const { tmpdir } = require('os');
|
|
16
16
|
const path = require('path');
|
|
17
|
-
const {
|
|
17
|
+
const { throwDeprecation } = require('process');
|
|
18
|
+
const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class StatesController extends EventEmitter {
|
|
@@ -32,6 +33,7 @@ class StatesController extends EventEmitter {
|
|
|
32
33
|
this.ImagesToDownload = [];
|
|
33
34
|
this.stashedErrors = {};
|
|
34
35
|
this.stashedUnknownModels = [];
|
|
36
|
+
this.debugMessages = { nodevice:{ in:[], out: []} };
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
info(message, data) {
|
|
@@ -85,6 +87,10 @@ class StatesController extends EventEmitter {
|
|
|
85
87
|
return rv;
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
debugMessagesById() {
|
|
91
|
+
return this.debugMessages;
|
|
92
|
+
}
|
|
93
|
+
|
|
88
94
|
async AddModelFromHerdsman(device, model) {
|
|
89
95
|
// this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
|
|
90
96
|
if (this.localConfig.getOverrideWithKey(model, 'legacy', true)) {
|
|
@@ -152,6 +158,7 @@ class StatesController extends EventEmitter {
|
|
|
152
158
|
if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
|
|
153
159
|
return;
|
|
154
160
|
}
|
|
161
|
+
const debugId = Date.now();
|
|
155
162
|
if (state && !state.ack) {
|
|
156
163
|
if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
|
|
157
164
|
return;
|
|
@@ -166,16 +173,18 @@ class StatesController extends EventEmitter {
|
|
|
166
173
|
return;
|
|
167
174
|
}
|
|
168
175
|
|
|
169
|
-
if (this.checkDebugDevice(id))
|
|
170
|
-
this.warn(`ELEVATED O01: User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`);
|
|
171
|
-
|
|
172
|
-
this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
|
|
173
176
|
const devId = getAdId(this.adapter, id); // iobroker device id
|
|
174
177
|
let deviceId = getZbId(id); // zigbee device id
|
|
178
|
+
|
|
179
|
+
if (this.checkDebugDevice(id)) {
|
|
180
|
+
const message = `User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`;
|
|
181
|
+
this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
|
|
182
|
+
} else
|
|
183
|
+
this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
|
|
175
184
|
// const stateKey = id.split('.')[3];
|
|
176
185
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
|
|
177
186
|
if (arr[1] === undefined) {
|
|
178
|
-
this.warn(`unable to extract id from state ${id}`);
|
|
187
|
+
//this.warn(`unable to extract id from state ${id}`);
|
|
179
188
|
return;
|
|
180
189
|
}
|
|
181
190
|
const stateKey = arr[1];
|
|
@@ -199,7 +208,7 @@ class StatesController extends EventEmitter {
|
|
|
199
208
|
|
|
200
209
|
}
|
|
201
210
|
this.collectOptions(id.split('.')[2], model, options =>
|
|
202
|
-
this.publishFromState(deviceId, model, stateKey, state, options));
|
|
211
|
+
this.publishFromState(deviceId, model, stateKey, state, options, debugId));
|
|
203
212
|
}
|
|
204
213
|
});
|
|
205
214
|
}
|
|
@@ -305,29 +314,38 @@ class StatesController extends EventEmitter {
|
|
|
305
314
|
}, (stateDesc.compositeTimeout ? stateDesc.compositeTimeout : 100) * factor);
|
|
306
315
|
}
|
|
307
316
|
|
|
308
|
-
async publishFromState(deviceId, model, stateKey, state, options) {
|
|
317
|
+
async publishFromState(deviceId, model, stateKey, state, options, debugId) {
|
|
309
318
|
this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
310
319
|
const elevated = this.checkDebugDevice(deviceId);
|
|
311
320
|
|
|
312
|
-
if (elevated)
|
|
321
|
+
if (elevated) {
|
|
322
|
+
const message = (`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
323
|
+
this.emit('device_debug', { ID:debugId, data: { ID: deviceId, model: model, flag:'02', IO:false }, message:message});
|
|
324
|
+
}
|
|
313
325
|
|
|
314
326
|
const devStates = await this.getDevStates(deviceId, model);
|
|
315
327
|
if (!devStates) {
|
|
316
|
-
if (elevated)
|
|
328
|
+
if (elevated) {
|
|
329
|
+
const message = (`no device states for device ${deviceId} type '${model}'`);
|
|
330
|
+
this.emit('device_debug', { ID:debugId, data: { error: 'NOSTATES' , IO:false }, message:message});
|
|
331
|
+
}
|
|
317
332
|
return;
|
|
318
333
|
}
|
|
319
334
|
const commonStates = statesMapping.commonStates.find(statedesc => stateKey === statedesc.id);
|
|
320
335
|
const stateDesc = (commonStates === undefined ? devStates.states.find(statedesc => stateKey === statedesc.id) : commonStates);
|
|
321
336
|
const stateModel = devStates.stateModel;
|
|
322
337
|
if (!stateDesc) {
|
|
323
|
-
|
|
338
|
+
const message = (`No state available for '${model}' with key '${stateKey}'`);
|
|
339
|
+
if (elevated) this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:'unknown', payload:'unknown'}], error: 'NOSTKEY' , IO:false }, message:message});
|
|
324
340
|
return;
|
|
325
341
|
}
|
|
326
342
|
|
|
327
343
|
const value = state.val;
|
|
328
344
|
if (value === undefined || value === '') {
|
|
329
|
-
if (elevated)
|
|
330
|
-
|
|
345
|
+
if (elevated) {
|
|
346
|
+
const message = (`no value for device ${deviceId} type '${model}'`);
|
|
347
|
+
this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:'--', payload:'error', ep:stateDesc.epname}],error: 'NOVAL' , IO:false }, message:message});
|
|
348
|
+
}
|
|
331
349
|
return;
|
|
332
350
|
}
|
|
333
351
|
let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0, source:state.from}];
|
|
@@ -344,7 +362,8 @@ class StatesController extends EventEmitter {
|
|
|
344
362
|
}
|
|
345
363
|
} catch (e) {
|
|
346
364
|
this.sendError(e);
|
|
347
|
-
this.
|
|
365
|
+
if (elevated) this.emit('device_debug', { ID:debugId, data: { states:[{id:state.ID, value:state.val, payload:'unknown'}], error: 'EXLINK' , IO:false }});
|
|
366
|
+
this.error('Exception caught in publishfromstate: ' + (e && e.message ? e.message : 'no error message given'));
|
|
348
367
|
}
|
|
349
368
|
|
|
350
369
|
});
|
|
@@ -359,7 +378,7 @@ class StatesController extends EventEmitter {
|
|
|
359
378
|
readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
|
|
360
379
|
}
|
|
361
380
|
|
|
362
|
-
this.emit('changed', deviceId, model, stateModel, stateList, options);
|
|
381
|
+
this.emit('changed', deviceId, model, stateModel, stateList, options, debugId);
|
|
363
382
|
}
|
|
364
383
|
|
|
365
384
|
async renameDevice(id, newName) {
|
|
@@ -413,7 +432,7 @@ class StatesController extends EventEmitter {
|
|
|
413
432
|
let statename = state._id;
|
|
414
433
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
|
|
415
434
|
if (arr[1] === undefined) {
|
|
416
|
-
this.
|
|
435
|
+
this.debug(`unable to extract id from state ${statename}`);
|
|
417
436
|
const idx = statename.lastIndexOf('.');
|
|
418
437
|
if (idx > -1) {
|
|
419
438
|
statename = statename.slice(idx + 1);
|
|
@@ -755,7 +774,7 @@ class StatesController extends EventEmitter {
|
|
|
755
774
|
this.adapter.fileExists(namespace, target, async (err,result) => {
|
|
756
775
|
if (result) return;
|
|
757
776
|
const src = `${tmpdir()}/${path.basename(target)}`;
|
|
758
|
-
const msg = `downloading ${url} to ${src}`;
|
|
777
|
+
//const msg = `downloading ${url} to ${src}`;
|
|
759
778
|
if (this.ImagesToDownload.indexOf(url) ==-1) {
|
|
760
779
|
await this.downloadIcon(url, src)
|
|
761
780
|
try {
|
|
@@ -773,7 +792,6 @@ class StatesController extends EventEmitter {
|
|
|
773
792
|
this.info(`copied ${src} to ${target}.`)
|
|
774
793
|
fs.rm(src, (err) => {
|
|
775
794
|
if (err) this.warn(`error removing ${src} : ${JSON.stringify(err)}`);
|
|
776
|
-
else this.info(`removed ${src}`)
|
|
777
795
|
});
|
|
778
796
|
})
|
|
779
797
|
}
|
|
@@ -861,102 +879,257 @@ class StatesController extends EventEmitter {
|
|
|
861
879
|
this.emit('debugmessage', {id: id, message:message});
|
|
862
880
|
}
|
|
863
881
|
|
|
864
|
-
async publishToState(devId, model, payload) {
|
|
865
|
-
|
|
882
|
+
async publishToState(devId, model, payload, debugId) {
|
|
883
|
+
try {
|
|
884
|
+
if (!debugId) debugId = Date.now();
|
|
885
|
+
const devStates = await this.getDevStates(`0x${devId}`, model);
|
|
886
|
+
|
|
887
|
+
const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
|
|
888
|
+
|
|
889
|
+
const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
|
|
890
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'01', IO:true }, message:message});
|
|
891
|
+
else this.debug(message);
|
|
892
|
+
if (!devStates) {
|
|
893
|
+
const message = `no device states for device ${devId} type '${model}'`;
|
|
894
|
+
if (has_elevated_debug)this.emit('device_debug', { ID:debugId, data: { error:'NOSTATE',states:[{ id:'--', value:'--', payload:payload}], IO:true }, message:message});
|
|
895
|
+
else this.debug(message);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
// find states for payload
|
|
899
|
+
let has_published = false;
|
|
900
|
+
if (devStates.states !== undefined) {
|
|
901
|
+
try {
|
|
902
|
+
const states = statesMapping.commonStates.concat(
|
|
903
|
+
devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id))
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
for (const stateInd in states) {
|
|
907
|
+
const statedesc = states[stateInd];
|
|
908
|
+
let value;
|
|
909
|
+
if (statedesc.getter) {
|
|
910
|
+
value = statedesc.getter(payload);
|
|
911
|
+
} else {
|
|
912
|
+
value = payload[statedesc.prop || statedesc.id];
|
|
913
|
+
}
|
|
914
|
+
// checking value
|
|
915
|
+
if (value === undefined || value === null) {
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
866
918
|
|
|
867
|
-
|
|
919
|
+
let stateID = statedesc.id;
|
|
920
|
+
|
|
921
|
+
const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
|
|
922
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'02', IO:true }, message});
|
|
923
|
+
else this.debug(message);
|
|
924
|
+
|
|
925
|
+
const common = {
|
|
926
|
+
name: statedesc.name,
|
|
927
|
+
type: statedesc.type,
|
|
928
|
+
unit: statedesc.unit,
|
|
929
|
+
read: statedesc.read,
|
|
930
|
+
write: statedesc.write,
|
|
931
|
+
icon: statedesc.icon,
|
|
932
|
+
role: statedesc.role,
|
|
933
|
+
min: statedesc.min,
|
|
934
|
+
max: statedesc.max,
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
938
|
+
stateID = `${stateID}.${value.stateid}`;
|
|
939
|
+
if (value.hasOwnProperty('unit')) {
|
|
940
|
+
common.unit = value.unit;
|
|
941
|
+
}
|
|
942
|
+
common.name = value.name ? value.name : value.stateid;
|
|
943
|
+
common.role = value.role ? `value.${value.role}` : 'number';
|
|
944
|
+
value = value.value;
|
|
945
|
+
}
|
|
868
946
|
|
|
947
|
+
// if needs to return value to back after timeout
|
|
948
|
+
if (statedesc.isEvent) {
|
|
949
|
+
this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, (typeof value == typeof (!value) ? !value : ''));
|
|
950
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
869
951
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
952
|
+
} else {
|
|
953
|
+
if (statedesc.prepublish) {
|
|
954
|
+
this.collectOptions(devId, model, options =>
|
|
955
|
+
statedesc.prepublish(devId, value, newvalue => {
|
|
956
|
+
this.updateState(devId, stateID, newvalue, common) }, options)
|
|
957
|
+
);
|
|
958
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
959
|
+
} else {
|
|
960
|
+
this.updateState(devId, stateID, value, common, debugId);
|
|
961
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { flag:'SUCCESS', IO:true }});
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
has_published = true;
|
|
965
|
+
}
|
|
966
|
+
} catch (e) {
|
|
967
|
+
const message = `unable to enumerate states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`;
|
|
968
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'ESTATE', IO:true }, message:message});
|
|
969
|
+
else this.debug(message);
|
|
970
|
+
}
|
|
971
|
+
const message = `No value published for device ${devId}`;
|
|
972
|
+
if (!has_published && has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOVAL', IO:true }, message:message});
|
|
973
|
+
else this.debug(message);
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
const message = `ELEVATED IE05 - NOSTATE: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`;
|
|
977
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data:{ error:'NOSTATE', IO:true }, message});
|
|
978
|
+
else this.debug(message);
|
|
979
|
+
}
|
|
873
980
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
this.elevatedMessage(devId, `ELEVATED IE02: no device states for device ${devId} type '${model}'`, true)
|
|
877
|
-
return;
|
|
981
|
+
catch (error) {
|
|
982
|
+
this.error('Something went horribly wrong: ' + (error && error.message ? error.message : 'no reason given'));
|
|
878
983
|
}
|
|
879
|
-
|
|
880
|
-
let has_published = false;
|
|
984
|
+
}
|
|
881
985
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const statedesc = states[stateInd];
|
|
890
|
-
let value;
|
|
891
|
-
if (statedesc.getter) {
|
|
892
|
-
value = statedesc.getter(payload);
|
|
893
|
-
} else {
|
|
894
|
-
value = payload[statedesc.prop || statedesc.id];
|
|
895
|
-
}
|
|
896
|
-
// checking value
|
|
897
|
-
if (value === undefined || value === null) {
|
|
898
|
-
continue;
|
|
899
|
-
}
|
|
986
|
+
async processConverters(converters, devId, model, mappedModel, message, meta, debugId) {
|
|
987
|
+
for (const converter of converters) {
|
|
988
|
+
const publish = (payload, dID) => {
|
|
989
|
+
if (typeof payload === 'object') {
|
|
990
|
+
this.publishToState(devId, model, payload,dID);
|
|
991
|
+
}
|
|
992
|
+
};
|
|
900
993
|
|
|
901
|
-
|
|
994
|
+
const options = await new Promise((resolve, reject) => {
|
|
995
|
+
this.collectOptions(devId, model, (options) => {
|
|
996
|
+
resolve(options);
|
|
997
|
+
});
|
|
998
|
+
});
|
|
902
999
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1000
|
+
const payload = await new Promise((resolve, reject) => {
|
|
1001
|
+
const payloadConv = converter.convert(mappedModel, message, publish, options, meta);
|
|
1002
|
+
if (typeof payloadConv === 'object') {
|
|
1003
|
+
resolve(payloadConv);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
906
1006
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
unit: statedesc.unit,
|
|
911
|
-
read: statedesc.read,
|
|
912
|
-
write: statedesc.write,
|
|
913
|
-
icon: statedesc.icon,
|
|
914
|
-
role: statedesc.role,
|
|
915
|
-
min: statedesc.min,
|
|
916
|
-
max: statedesc.max,
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
920
|
-
stateID = `${stateID}.${value.stateid}`;
|
|
921
|
-
if (value.hasOwnProperty('unit')) {
|
|
922
|
-
common.unit = value.unit;
|
|
923
|
-
}
|
|
924
|
-
common.name = value.name ? value.name : value.stateid;
|
|
925
|
-
common.role = value.role ? `value.${value.role}` : 'number';
|
|
926
|
-
value = value.value;
|
|
927
|
-
}
|
|
1007
|
+
publish(payload, debugId);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
928
1010
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1011
|
+
|
|
1012
|
+
async onZigbeeEvent(type, entity, message) {
|
|
1013
|
+
this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
|
|
1014
|
+
|
|
1015
|
+
const device = entity.device;
|
|
1016
|
+
const mappedModel = entity.mapped;
|
|
1017
|
+
const model = entity.mapped ? entity.mapped.model : entity.device.modelID;
|
|
1018
|
+
const cluster = message.cluster;
|
|
1019
|
+
const devId = device.ieeeAddr.substr(2);
|
|
1020
|
+
const meta = {device};
|
|
1021
|
+
|
|
1022
|
+
const has_elevated_debug = this.checkDebugDevice(devId);
|
|
1023
|
+
const debugId = Date.now();
|
|
1024
|
+
|
|
1025
|
+
// raw message data for logging and msg_from_zigbee
|
|
1026
|
+
const msgForState = Object.assign({}, message);
|
|
1027
|
+
delete msgForState['device'];
|
|
1028
|
+
delete msgForState['endpoint'];
|
|
1029
|
+
msgForState['endpoint_id'] = message.endpoint.ID;
|
|
1030
|
+
|
|
1031
|
+
if (has_elevated_debug) {
|
|
1032
|
+
const message = `Zigbee Event of Type ${type} from device ${device.ieeeAddr}, incoming event: ${safeJsonStringify(msgForState)}`;
|
|
1033
|
+
this.emit('device_debug', { ID:debugId, data: { ID: device.ieeeAddr, payload:safeJsonStringify(msgForState), flag:'01', IO:true }, message:message});
|
|
1034
|
+
|
|
1035
|
+
}
|
|
1036
|
+
// this assigment give possibility to use iobroker logger in code of the converters, via meta.logger
|
|
1037
|
+
meta.logger = this;
|
|
1038
|
+
|
|
1039
|
+
await this.adapter.checkIfModelUpdate(entity);
|
|
1040
|
+
|
|
1041
|
+
let _voltage = 0;
|
|
1042
|
+
let _temperature = 0;
|
|
1043
|
+
let _humidity = 0;
|
|
1044
|
+
|
|
1045
|
+
let isMessure = false;
|
|
1046
|
+
let isBattKey = false;
|
|
1047
|
+
|
|
1048
|
+
if (mappedModel && mappedModel.meta && mappedModel.meta.battery) {
|
|
1049
|
+
const isVoltage = mappedModel.meta.battery.hasOwnProperty('voltageToPercentage');
|
|
1050
|
+
|
|
1051
|
+
if (isVoltage) {
|
|
1052
|
+
const keys = Object.keys(message.data);
|
|
1053
|
+
|
|
1054
|
+
for (const key of keys) {
|
|
1055
|
+
const value = message.data[key];
|
|
1056
|
+
|
|
1057
|
+
if (value && value[1]) {
|
|
1058
|
+
if (key == 65282 && value[1][1]) {
|
|
1059
|
+
_voltage = value[1][1].elmVal;
|
|
1060
|
+
isBattKey = true;
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
if (key == 65281) {
|
|
1064
|
+
_voltage = value[1];
|
|
1065
|
+
isBattKey = true;
|
|
1066
|
+
_temperature = value[100];
|
|
1067
|
+
_temperature = _temperature /100;
|
|
1068
|
+
_humidity = value[101];
|
|
1069
|
+
_humidity = _humidity / 100;
|
|
1070
|
+
isMessure = true;
|
|
1071
|
+
break;
|
|
940
1072
|
}
|
|
941
1073
|
}
|
|
942
|
-
has_published = true;
|
|
943
1074
|
}
|
|
944
|
-
} catch (e) {
|
|
945
|
-
this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`);
|
|
946
|
-
if (has_elevated_debug)
|
|
947
|
-
this.elevatedMessage(devId, `ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`, true);
|
|
948
1075
|
}
|
|
949
|
-
|
|
950
|
-
this.elevatedMessage(devId, `ELEVATED IE04: No value published for device ${devId}`, true);
|
|
1076
|
+
}
|
|
951
1077
|
|
|
1078
|
+
// always publish link_quality and battery
|
|
1079
|
+
if (message.linkquality) { // send battery with
|
|
1080
|
+
this.publishToState(devId, model, {linkquality: message.linkquality}, debugId);
|
|
1081
|
+
if (isBattKey) {
|
|
1082
|
+
this.publishToState(devId, model, {voltage: _voltage}, debugId);
|
|
1083
|
+
const battProz = zigbeeHerdsmanConvertersUtils.batteryVoltageToPercentage(_voltage,entity.mapped.meta.battery.voltageToPercentage);
|
|
1084
|
+
this.publishToState(devId, model, {battery: battProz}, debugId);
|
|
1085
|
+
}
|
|
1086
|
+
if (isMessure) {
|
|
1087
|
+
this.publishToState(devId, model, {temperature: _temperature}, debugId);
|
|
1088
|
+
this.publishToState(devId, model, {humidity: _humidity}), debugId;
|
|
952
1089
|
}
|
|
953
1090
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1091
|
+
|
|
1092
|
+
// publish raw event to "from_zigbee"
|
|
1093
|
+
// some cleanup
|
|
1094
|
+
|
|
1095
|
+
this.publishToState(devId, model, {msg_from_zigbee: safeJsonStringify(msgForState)}, -1);
|
|
1096
|
+
|
|
1097
|
+
if (!entity.mapped) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
let converters = mappedModel.fromZigbee.filter(c => c && c.cluster === cluster && (
|
|
1102
|
+
Array.isArray(c.type) ? c.type.includes(type) : c.type === type));
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
if (!converters.length && type === 'readResponse') {
|
|
1106
|
+
converters = mappedModel.fromZigbee.filter(c => c.cluster === cluster && (
|
|
1107
|
+
Array.isArray(c.type) ? c.type.includes('attributeReport') : c.type === 'attributeReport'));
|
|
957
1108
|
}
|
|
1109
|
+
|
|
1110
|
+
if (!converters.length) {
|
|
1111
|
+
if (type !== 'readResponse') {
|
|
1112
|
+
const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
|
|
1113
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'NOCONV', IO:true }, message:message});
|
|
1114
|
+
else this.debug(message);
|
|
1115
|
+
}
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
meta.state = { state: '' }; // for tuya
|
|
1120
|
+
|
|
1121
|
+
this.processConverters(converters, devId, model, mappedModel, message, meta, debugId)
|
|
1122
|
+
.catch((error) => {
|
|
1123
|
+
// 'Error: Expected one of: 0, 1, got: 'undefined''
|
|
1124
|
+
if (cluster !== '64529') {
|
|
1125
|
+
if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { error:'EPROC', IO:true }});
|
|
1126
|
+
this.error(`Error while processing converters DEVICE_ID: '${devId}' cluster '${cluster}' type '${type}'`);
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
958
1129
|
}
|
|
959
1130
|
|
|
1131
|
+
|
|
1132
|
+
|
|
960
1133
|
}
|
|
961
1134
|
|
|
962
1135
|
module.exports = StatesController;
|
|
@@ -178,7 +178,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
178
178
|
this.publishAvailability(device, false);
|
|
179
179
|
if (pingCount.failed++ <= this.max_ping) {
|
|
180
180
|
if (pingCount.failed < 2 && pingCount.reported < this.max_ping) {
|
|
181
|
-
this.
|
|
181
|
+
this.info(`Failed to ping ${ieeeAddr} ${device.modelID}`);
|
|
182
182
|
pingCount.reported++;
|
|
183
183
|
} else {
|
|
184
184
|
this.debug(`Failed to ping ${ieeeAddr} ${device.modelID} on ${pingCount} consecutive attempts`);
|
|
@@ -186,7 +186,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
186
186
|
this.setTimerPingable(device, pingCount.failed);
|
|
187
187
|
this.ping_counters[device.ieeeAddr] = pingCount;
|
|
188
188
|
} else {
|
|
189
|
-
this.
|
|
189
|
+
this.info(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
}
|
package/lib/zbDeviceConfigure.js
CHANGED
|
@@ -14,6 +14,8 @@ class DeviceConfigure extends BaseExtension {
|
|
|
14
14
|
this.configuring = new Set();
|
|
15
15
|
this.attempts = {};
|
|
16
16
|
this.name = 'DeviceConfigure';
|
|
17
|
+
|
|
18
|
+
this.configureKeys = {};
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
setOptions(options) {
|
|
@@ -27,23 +29,24 @@ class DeviceConfigure extends BaseExtension {
|
|
|
27
29
|
if (!mappedDevice || !mappedDevice.configure) {
|
|
28
30
|
return false;
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return (device.interviewing !== true && this.checkDelayedConfigure(device.ieeeAddr)>0);
|
|
32
|
+
const delayedAttempts = this.checkDelayedConfigure(device, 0);
|
|
33
|
+
if (!this.configureKeys.hasOwnProperty(device.ieeeAddr)) this.configureKeys[device.ieeeAddr] = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
|
|
34
|
+
if (delayedAttempts > 0 && !device.interviewing) return true;
|
|
35
|
+
if (device.meta.hasOwnProperty('configured') && device.meta.configured !== this.configureKeys[device.ieeeAddr]) return true;
|
|
36
|
+
return (device.interviewing !== true && this.checkDelayedConfigure(device)>0);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
checkDelayedConfigure(device, num) {
|
|
39
40
|
if (!this.delayedConfigure.hasOwnProperty(device.ieeeAddr)) {
|
|
40
41
|
if (num && num > 0) {
|
|
42
|
+
// this.warn('adding dev ' + device.ieeeAddr + ' to delayedConfigure with ' + num + ' attempts');
|
|
41
43
|
this.delayedConfigure[device.ieeeAddr] = { maxAttempts:num };
|
|
42
44
|
return num;
|
|
43
45
|
}
|
|
44
46
|
return 0;
|
|
45
47
|
}
|
|
46
48
|
const dc = this.delayedConfigure[device.ieeeAddr];
|
|
49
|
+
// this.warn('checkDelayedConfigure for ' + device.ieeeAddr + ' with ' + JSON.stringify(dc));
|
|
47
50
|
dc.maxAttempts--;
|
|
48
51
|
if (dc.maxAttempts > 0) return dc.maxAttempts;
|
|
49
52
|
if (num && num > 0) {
|
|
@@ -90,6 +93,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
90
93
|
try {
|
|
91
94
|
const device = data.device;
|
|
92
95
|
if (this.shouldConfigure(device, mappedDevice)) {
|
|
96
|
+
this.debug('ShouldConfigure ' + device.ieeeAddr);
|
|
93
97
|
this.configure(device, mappedDevice);
|
|
94
98
|
}
|
|
95
99
|
} catch (error) {
|
|
@@ -98,6 +102,7 @@ class DeviceConfigure extends BaseExtension {
|
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
|
|
105
|
+
|
|
101
106
|
onDeviceRemove(device) {
|
|
102
107
|
try {
|
|
103
108
|
if (this.configuring.has(device.ieeeAddr)) {
|
|
@@ -141,14 +146,16 @@ class DeviceConfigure extends BaseExtension {
|
|
|
141
146
|
const coordinatorEndpoint = await this.zigbee.getDevicesByType('Coordinator')[0].endpoints[0];
|
|
142
147
|
try {
|
|
143
148
|
if (mappedDevice) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
if (mappedDevice.configure === undefined) return `No configure available for ${device.ieeeAddr} ${device.modelID}.`;
|
|
150
|
+
this.info(`Configuring ${device.ieeeAddr} ${device.modelID}`);
|
|
151
|
+
if (typeof mappedDevice.configure === 'function') await mappedDevice.configure(device, coordinatorEndpoint, this);
|
|
152
|
+
else {
|
|
153
|
+
const promises = [];
|
|
154
|
+
promises.push(...mappedDevice.configure);
|
|
155
|
+
await Promise.all(promises.map(callback => callback(device, coordinatorEndpoint, mappedDevice)))
|
|
156
|
+
}
|
|
151
157
|
device.meta.configured = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
|
|
158
|
+
this.configureKeys[device.ieeeAddr] = device.meta.configured;
|
|
152
159
|
device.save();
|
|
153
160
|
this.info(`DeviceConfigure successful ${device.ieeeAddr} ${device.modelID}`);
|
|
154
161
|
this.delayedConfigureAttempt(device, true);
|
|
@@ -162,8 +169,8 @@ class DeviceConfigure extends BaseExtension {
|
|
|
162
169
|
if (error && error.message && error.message.match(/(\d+)ms/gm)) {
|
|
163
170
|
// timeout message - we do want to start the configure chain
|
|
164
171
|
const num = this.delayedConfigureAttempt(device, false);
|
|
165
|
-
this.
|
|
166
|
-
return `
|
|
172
|
+
this.warn(`Timeout trying to configure ${device.ieeeAddr} ${device.modelID}: ${num} attempts remaining`)
|
|
173
|
+
return `Configuration timed out ${device.ieeeAddr} ${device.modelID}. The device did not repond in time to the configuration request. Another attempt will be made when the device is awake.`;
|
|
167
174
|
} else {
|
|
168
175
|
this.sendError(error);
|
|
169
176
|
this.warn(`${device.ieeeAddr} ${device.modelID} Failed to configure. --> ${error && error.message ? error.message : ' no error message given'} `);
|