iobroker.zigbee 2.0.1 → 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 +77 -36
- package/admin/admin.js +211 -19
- package/admin/img/philips_hue_lom001.png +0 -0
- package/admin/tab_m.html +13 -8
- package/docs/de/readme.md +21 -28
- 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 +30 -51
- package/lib/DeviceDebug.js +80 -0
- package/lib/commands.js +24 -3
- package/lib/developer.js +0 -0
- package/lib/devices.js +14 -4
- package/lib/exposes.js +46 -46
- package/lib/groups.js +6 -8
- package/lib/localConfig.js +11 -6
- package/lib/ota.js +11 -10
- package/lib/statescontroller.js +314 -101
- package/lib/utils.js +4 -2
- package/lib/zbDeviceAvailability.js +3 -9
- package/lib/zbDeviceConfigure.js +68 -34
- package/lib/zigbeecontroller.js +50 -14
- package/main.js +168 -203
- package/package.json +5 -5
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 {
|
|
@@ -31,6 +32,8 @@ class StatesController extends EventEmitter {
|
|
|
31
32
|
this.timeoutHandleUpload = null;
|
|
32
33
|
this.ImagesToDownload = [];
|
|
33
34
|
this.stashedErrors = {};
|
|
35
|
+
this.stashedUnknownModels = [];
|
|
36
|
+
this.debugMessages = { nodevice:{ in:[], out: []} };
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
info(message, data) {
|
|
@@ -71,8 +74,35 @@ class StatesController extends EventEmitter {
|
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
getStashedErrors() {
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
const rv = [];
|
|
78
|
+
if (Object.keys(this.stashedErrors).length > 0)
|
|
79
|
+
{
|
|
80
|
+
rv.push('<p><b>Stashed Messages</b></p>')
|
|
81
|
+
rv.push(Object.values(this.stashedErrors).join('<br>'));
|
|
82
|
+
}
|
|
83
|
+
if (this.stashedUnknownModels.length > 0) {
|
|
84
|
+
rv.push('<p><b>Devices whithout Model definition</b></p>')
|
|
85
|
+
rv.push()
|
|
86
|
+
}
|
|
87
|
+
return rv;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
debugMessagesById() {
|
|
91
|
+
return this.debugMessages;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async AddModelFromHerdsman(device, model) {
|
|
95
|
+
// this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
|
|
96
|
+
if (this.localConfig.getOverrideWithKey(model, 'legacy', true)) {
|
|
97
|
+
//this.warn('legacy for ' + model);
|
|
98
|
+
await this.addLegacyDevice(model);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const pre = statesMapping.devices.length;
|
|
102
|
+
await statesMapping.addExposeToDevices(device, this, model);
|
|
103
|
+
const post = statesMapping.devices.length;
|
|
104
|
+
//this.warn('expose for ' + model + ' '+ pre + '->'+post);
|
|
105
|
+
}
|
|
76
106
|
}
|
|
77
107
|
|
|
78
108
|
async getDebugDevices(callback) {
|
|
@@ -128,6 +158,7 @@ class StatesController extends EventEmitter {
|
|
|
128
158
|
if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
|
|
129
159
|
return;
|
|
130
160
|
}
|
|
161
|
+
const debugId = Date.now();
|
|
131
162
|
if (state && !state.ack) {
|
|
132
163
|
if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
|
|
133
164
|
return;
|
|
@@ -142,16 +173,18 @@ class StatesController extends EventEmitter {
|
|
|
142
173
|
return;
|
|
143
174
|
}
|
|
144
175
|
|
|
145
|
-
if (this.checkDebugDevice(id))
|
|
146
|
-
this.warn(`ELEVATED O01: User state change of state ${id} with value ${state.val} (ack: ${state.ack}) from ${state.from}`);
|
|
147
|
-
|
|
148
|
-
this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
|
|
149
176
|
const devId = getAdId(this.adapter, id); // iobroker device id
|
|
150
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)}`);
|
|
151
184
|
// const stateKey = id.split('.')[3];
|
|
152
185
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
|
|
153
186
|
if (arr[1] === undefined) {
|
|
154
|
-
this.warn(`unable to extract id from state ${id}`);
|
|
187
|
+
//this.warn(`unable to extract id from state ${id}`);
|
|
155
188
|
return;
|
|
156
189
|
}
|
|
157
190
|
const stateKey = arr[1];
|
|
@@ -175,7 +208,7 @@ class StatesController extends EventEmitter {
|
|
|
175
208
|
|
|
176
209
|
}
|
|
177
210
|
this.collectOptions(id.split('.')[2], model, options =>
|
|
178
|
-
this.publishFromState(deviceId, model, stateKey, state, options));
|
|
211
|
+
this.publishFromState(deviceId, model, stateKey, state, options, debugId));
|
|
179
212
|
}
|
|
180
213
|
});
|
|
181
214
|
}
|
|
@@ -228,7 +261,13 @@ class StatesController extends EventEmitter {
|
|
|
228
261
|
} else {
|
|
229
262
|
stateModel = statesMapping.findModel(model);
|
|
230
263
|
if (!stateModel) {
|
|
231
|
-
|
|
264
|
+
// try to get the model from the exposes
|
|
265
|
+
if (!this.stashedErrors.hasOwnProperty(`${deviceId}.nomodel`))
|
|
266
|
+
{
|
|
267
|
+
this.warn(`Device ${deviceId} "${model}" not found.`);
|
|
268
|
+
this.stashedErrors[`${deviceId}.nomodel`] = `Device ${deviceId} "${model}" not found.`;
|
|
269
|
+
}
|
|
270
|
+
|
|
232
271
|
states = statesMapping.commonStates;
|
|
233
272
|
} else {
|
|
234
273
|
states = stateModel.states;
|
|
@@ -275,29 +314,38 @@ class StatesController extends EventEmitter {
|
|
|
275
314
|
}, (stateDesc.compositeTimeout ? stateDesc.compositeTimeout : 100) * factor);
|
|
276
315
|
}
|
|
277
316
|
|
|
278
|
-
async publishFromState(deviceId, model, stateKey, state, options) {
|
|
317
|
+
async publishFromState(deviceId, model, stateKey, state, options, debugId) {
|
|
279
318
|
this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
280
319
|
const elevated = this.checkDebugDevice(deviceId);
|
|
281
320
|
|
|
282
|
-
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
|
+
}
|
|
283
325
|
|
|
284
326
|
const devStates = await this.getDevStates(deviceId, model);
|
|
285
327
|
if (!devStates) {
|
|
286
|
-
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
|
+
}
|
|
287
332
|
return;
|
|
288
333
|
}
|
|
289
334
|
const commonStates = statesMapping.commonStates.find(statedesc => stateKey === statedesc.id);
|
|
290
335
|
const stateDesc = (commonStates === undefined ? devStates.states.find(statedesc => stateKey === statedesc.id) : commonStates);
|
|
291
336
|
const stateModel = devStates.stateModel;
|
|
292
337
|
if (!stateDesc) {
|
|
293
|
-
|
|
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});
|
|
294
340
|
return;
|
|
295
341
|
}
|
|
296
342
|
|
|
297
343
|
const value = state.val;
|
|
298
344
|
if (value === undefined || value === '') {
|
|
299
|
-
if (elevated)
|
|
300
|
-
|
|
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
|
+
}
|
|
301
349
|
return;
|
|
302
350
|
}
|
|
303
351
|
let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0, source:state.from}];
|
|
@@ -314,7 +362,8 @@ class StatesController extends EventEmitter {
|
|
|
314
362
|
}
|
|
315
363
|
} catch (e) {
|
|
316
364
|
this.sendError(e);
|
|
317
|
-
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'));
|
|
318
367
|
}
|
|
319
368
|
|
|
320
369
|
});
|
|
@@ -329,7 +378,7 @@ class StatesController extends EventEmitter {
|
|
|
329
378
|
readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
|
|
330
379
|
}
|
|
331
380
|
|
|
332
|
-
this.emit('changed', deviceId, model, stateModel, stateList, options);
|
|
381
|
+
this.emit('changed', deviceId, model, stateModel, stateList, options, debugId);
|
|
333
382
|
}
|
|
334
383
|
|
|
335
384
|
async renameDevice(id, newName) {
|
|
@@ -383,7 +432,7 @@ class StatesController extends EventEmitter {
|
|
|
383
432
|
let statename = state._id;
|
|
384
433
|
const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(statename);
|
|
385
434
|
if (arr[1] === undefined) {
|
|
386
|
-
this.
|
|
435
|
+
this.debug(`unable to extract id from state ${statename}`);
|
|
387
436
|
const idx = statename.lastIndexOf('.');
|
|
388
437
|
if (idx > -1) {
|
|
389
438
|
statename = statename.slice(idx + 1);
|
|
@@ -615,6 +664,11 @@ class StatesController extends EventEmitter {
|
|
|
615
664
|
//this.warn('devices are' + modelarr2.join(','));
|
|
616
665
|
}
|
|
617
666
|
|
|
667
|
+
async addLegacyDevice(model) {
|
|
668
|
+
statesMapping.setLegacyDevices([model]);
|
|
669
|
+
statesMapping.getByModel();
|
|
670
|
+
}
|
|
671
|
+
|
|
618
672
|
async getDefaultGroupIcon(id) {
|
|
619
673
|
const regexResult = id.match(new RegExp(/group_(\d+)/));
|
|
620
674
|
if (!regexResult) return '';
|
|
@@ -720,7 +774,7 @@ class StatesController extends EventEmitter {
|
|
|
720
774
|
this.adapter.fileExists(namespace, target, async (err,result) => {
|
|
721
775
|
if (result) return;
|
|
722
776
|
const src = `${tmpdir()}/${path.basename(target)}`;
|
|
723
|
-
const msg = `downloading ${url} to ${src}`;
|
|
777
|
+
//const msg = `downloading ${url} to ${src}`;
|
|
724
778
|
if (this.ImagesToDownload.indexOf(url) ==-1) {
|
|
725
779
|
await this.downloadIcon(url, src)
|
|
726
780
|
try {
|
|
@@ -738,7 +792,6 @@ class StatesController extends EventEmitter {
|
|
|
738
792
|
this.info(`copied ${src} to ${target}.`)
|
|
739
793
|
fs.rm(src, (err) => {
|
|
740
794
|
if (err) this.warn(`error removing ${src} : ${JSON.stringify(err)}`);
|
|
741
|
-
else this.info(`removed ${src}`)
|
|
742
795
|
});
|
|
743
796
|
})
|
|
744
797
|
}
|
|
@@ -807,7 +860,12 @@ class StatesController extends EventEmitter {
|
|
|
807
860
|
async getExposes() {
|
|
808
861
|
await this.localConfig.init();
|
|
809
862
|
await this.applyLegacyDevices();
|
|
810
|
-
|
|
863
|
+
try {
|
|
864
|
+
statesMapping.fillStatesWithExposes(this);
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
this.error(`Error applying exposes: ${error && error.message ? error.message : 'no error message'} ${error && error.stack ? error.stack : ''}`);
|
|
868
|
+
}
|
|
811
869
|
}
|
|
812
870
|
|
|
813
871
|
async elevatedMessage(device, message, isError) {
|
|
@@ -821,102 +879,257 @@ class StatesController extends EventEmitter {
|
|
|
821
879
|
this.emit('debugmessage', {id: id, message:message});
|
|
822
880
|
}
|
|
823
881
|
|
|
824
|
-
async publishToState(devId, model, payload) {
|
|
825
|
-
|
|
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
|
+
}
|
|
826
918
|
|
|
827
|
-
|
|
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
|
+
}
|
|
828
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 }});
|
|
829
951
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
+
}
|
|
833
980
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
this.elevatedMessage(devId, `ELEVATED IE02: no device states for device ${devId} type '${model}'`, true)
|
|
837
|
-
return;
|
|
981
|
+
catch (error) {
|
|
982
|
+
this.error('Something went horribly wrong: ' + (error && error.message ? error.message : 'no reason given'));
|
|
838
983
|
}
|
|
839
|
-
|
|
840
|
-
let has_published = false;
|
|
984
|
+
}
|
|
841
985
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const statedesc = states[stateInd];
|
|
850
|
-
let value;
|
|
851
|
-
if (statedesc.getter) {
|
|
852
|
-
value = statedesc.getter(payload);
|
|
853
|
-
} else {
|
|
854
|
-
value = payload[statedesc.prop || statedesc.id];
|
|
855
|
-
}
|
|
856
|
-
// checking value
|
|
857
|
-
if (value === undefined || value === null) {
|
|
858
|
-
continue;
|
|
859
|
-
}
|
|
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
|
+
};
|
|
860
993
|
|
|
861
|
-
|
|
994
|
+
const options = await new Promise((resolve, reject) => {
|
|
995
|
+
this.collectOptions(devId, model, (options) => {
|
|
996
|
+
resolve(options);
|
|
997
|
+
});
|
|
998
|
+
});
|
|
862
999
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
+
});
|
|
866
1006
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
unit: statedesc.unit,
|
|
871
|
-
read: statedesc.read,
|
|
872
|
-
write: statedesc.write,
|
|
873
|
-
icon: statedesc.icon,
|
|
874
|
-
role: statedesc.role,
|
|
875
|
-
min: statedesc.min,
|
|
876
|
-
max: statedesc.max,
|
|
877
|
-
};
|
|
878
|
-
|
|
879
|
-
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
880
|
-
stateID = `${stateID}.${value.stateid}`;
|
|
881
|
-
if (value.hasOwnProperty('unit')) {
|
|
882
|
-
common.unit = value.unit;
|
|
883
|
-
}
|
|
884
|
-
common.name = value.name ? value.name : value.stateid;
|
|
885
|
-
common.role = value.role ? `value.${value.role}` : 'number';
|
|
886
|
-
value = value.value;
|
|
887
|
-
}
|
|
1007
|
+
publish(payload, debugId);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
888
1010
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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;
|
|
900
1072
|
}
|
|
901
1073
|
}
|
|
902
|
-
has_published = true;
|
|
903
1074
|
}
|
|
904
|
-
} catch (e) {
|
|
905
|
-
this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`);
|
|
906
|
-
if (has_elevated_debug)
|
|
907
|
-
this.elevatedMessage(devId, `ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`, true);
|
|
908
1075
|
}
|
|
909
|
-
|
|
910
|
-
this.elevatedMessage(devId, `ELEVATED IE04: No value published for device ${devId}`, true);
|
|
1076
|
+
}
|
|
911
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;
|
|
912
1089
|
}
|
|
913
1090
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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'));
|
|
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;
|
|
917
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
|
+
});
|
|
918
1129
|
}
|
|
919
1130
|
|
|
1131
|
+
|
|
1132
|
+
|
|
920
1133
|
}
|
|
921
1134
|
|
|
922
1135
|
module.exports = StatesController;
|
package/lib/utils.js
CHANGED
|
@@ -106,11 +106,13 @@ function flatten(arr) {
|
|
|
106
106
|
flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten), []);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const forceEndDevice =
|
|
109
|
+
const forceEndDevice = ['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM'];
|
|
110
|
+
|
|
111
|
+
/*flatten(
|
|
110
112
|
['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM']
|
|
111
113
|
.map(model => zigbeeHerdsmanConverters.findByModel(model))
|
|
112
114
|
.map(mappedModel => mappedModel.zigbeeModel));
|
|
113
|
-
|
|
115
|
+
*/
|
|
114
116
|
// Xiaomi uses 4151 and 4447 (lumi.plug) as manufacturer ID.
|
|
115
117
|
const xiaomiManufacturerID = [4151, 4447];
|
|
116
118
|
const ikeaTradfriManufacturerID = [4476];
|
|
@@ -6,13 +6,7 @@ const utils = require('./utils');
|
|
|
6
6
|
|
|
7
7
|
// Some EndDevices should be pinged
|
|
8
8
|
// e.g. E11-G13 https://github.com/Koenkk/zigbee2mqtt/issues/775#issuecomment-453683846
|
|
9
|
-
const forcedPingable = [
|
|
10
|
-
zigbeeHerdsmanConverters.findByModel('E11-G13'),
|
|
11
|
-
zigbeeHerdsmanConverters.findByModel('53170161'),
|
|
12
|
-
zigbeeHerdsmanConverters.findByModel('V3-BTZB'),
|
|
13
|
-
zigbeeHerdsmanConverters.findByModel('SPZB0001'),
|
|
14
|
-
zigbeeHerdsmanConverters.findByModel('014G2461')
|
|
15
|
-
];
|
|
9
|
+
const forcedPingable = ['E11-G13','53170161','V3-BTZB','SPZB0001','014G2461'];
|
|
16
10
|
|
|
17
11
|
// asgothian: 29.12.2020: Removed color and color_temp from readable
|
|
18
12
|
// state candidates as most states do not provide a getter to ikea_transform
|
|
@@ -184,7 +178,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
184
178
|
this.publishAvailability(device, false);
|
|
185
179
|
if (pingCount.failed++ <= this.max_ping) {
|
|
186
180
|
if (pingCount.failed < 2 && pingCount.reported < this.max_ping) {
|
|
187
|
-
this.
|
|
181
|
+
this.info(`Failed to ping ${ieeeAddr} ${device.modelID}`);
|
|
188
182
|
pingCount.reported++;
|
|
189
183
|
} else {
|
|
190
184
|
this.debug(`Failed to ping ${ieeeAddr} ${device.modelID} on ${pingCount} consecutive attempts`);
|
|
@@ -192,7 +186,7 @@ class DeviceAvailability extends BaseExtension {
|
|
|
192
186
|
this.setTimerPingable(device, pingCount.failed);
|
|
193
187
|
this.ping_counters[device.ieeeAddr] = pingCount;
|
|
194
188
|
} else {
|
|
195
|
-
this.
|
|
189
|
+
this.info(`Stopping to ping ${ieeeAddr} ${device.modelID} after ${pingCount.failed} ping attempts`);
|
|
196
190
|
}
|
|
197
191
|
}
|
|
198
192
|
}
|