iobroker.zigbee 3.1.2 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,19 +5,11 @@ const { EventEmitter } = require('events');
5
5
  const statesMapping = require('./devices');
6
6
  const { getAdId, getZbId } = require('./utils');
7
7
  const fs = require('fs');
8
- const axios = require('axios');
9
8
  const localConfig = require('./localConfig');
10
- //const { deviceAddCustomCluster } = require('zigbee-herdsman-converters/lib/modernExtend');
11
- //const { setDefaultAutoSelectFamilyAttemptTimeout } = require('net');
12
- //const { runInThisContext } = require('vm');
13
- //const { time } = require('console');
14
- const { exec } = require('child_process');
15
- const { tmpdir } = require('os');
16
9
  const path = require('path');
17
- const { throwDeprecation } = require('process');
10
+ const axios = require('axios');
18
11
  const zigbeeHerdsmanConvertersUtils = require('zigbee-herdsman-converters/lib/utils');
19
12
 
20
-
21
13
  class StatesController extends EventEmitter {
22
14
  constructor(adapter) {
23
15
  super();
@@ -84,7 +76,6 @@ class StatesController extends EventEmitter {
84
76
  async AddModelFromHerdsman(device, model) {
85
77
  const namespace = `${this.adapter.name}.admin`;
86
78
 
87
- // this.warn('addModelFromHerdsman ' + JSON.stringify(model) + ' ' + JSON.stringify(this.localConfig.getOverrideWithKey(model, 'legacy', true)));
88
79
  if (this.localConfig.getOverrideWithTargetAndKey(model, 'legacy', true)) {
89
80
  this.debug('Applying legacy definition for ' + model);
90
81
  await this.addLegacyDevice(model);
@@ -213,7 +204,7 @@ class StatesController extends EventEmitter {
213
204
  async toggleDeviceDebug(id) {
214
205
  const arr = /zigbee.[0-9].([^.]+)/gm.exec(id);
215
206
  if (!arr) {
216
- this.warn(`unable to toggle debug for device ${id}: there was no mat (${JSON.stringify(arr)}) `);
207
+ this.warn(`unable to toggle debug for device ${id}: there was no matc (${JSON.stringify(arr)}) `);
217
208
  return this.debugDevices;
218
209
  }
219
210
  if (arr[1] === undefined) {
@@ -278,10 +269,9 @@ class StatesController extends EventEmitter {
278
269
  this.emit('device_debug', { ID:debugId, data: { ID: deviceId, flag:'01' }, message:message});
279
270
  } else
280
271
  if (this.debugActive) this.debug(`User stateChange ${id} ${JSON.stringify(state)}`);
281
- // const stateKey = id.split('.')[3];
282
272
  const arr = /zigbee.[0-9].[^.]+.(\S+)/gm.exec(id);
283
273
  if (arr[1] === undefined) {
284
- //this.warn(`unable to extract id from state ${id}`);
274
+ this.debug(`unable to extract id from state ${id}`);
285
275
  return;
286
276
  }
287
277
  const stateKey = arr[1];
@@ -295,16 +285,7 @@ class StatesController extends EventEmitter {
295
285
  if (this.debugActive) this.debug('State Change detected on deactivated Device - ignored');
296
286
  return;
297
287
  }
298
- // check for group (model is group, deviceId is numerical, and not Nan or 0)
299
- /*
300
- if (model === 'group' && typeof deviceId == 'number' && Boolean(deviceId)) {
301
- const options = this.localConfig.getOptions(`group_${deviceId}`);
302
- options.isActive == (obj.common !== null);
303
- this.publishFromState(deviceId, model, stateKey, state, options, debugId);
304
- return;
305
- }
306
- */
307
- // handle send_payload here
288
+
308
289
  if (model && model.id === 'device_query') {
309
290
  if (this.query_device_block.indexOf(deviceId) > -1 && !state.source.includes('.admin.')) {
310
291
  this.info(`Device query for '${deviceId}' blocked - device query timeout has not elapsed yet.`);
@@ -480,7 +461,7 @@ class StatesController extends EventEmitter {
480
461
  const payload = {device: deviceId.replace('0x', ''), payload: json_value, model:model, stateModel:stateModel};
481
462
  if (has_elevated_debug) this.emit('device_debug', { ID:debugID, data: { flag: '04' ,payload:value ,states:[{id:stateDesc.id, value:json_value, payload:'none'}], IO:false }});
482
463
 
483
- this.emit('send_payload', payload, debugID);
464
+ this.emit('send_payload', payload, debugID, has_elevated_debug);
484
465
  } catch (error) {
485
466
  const message = `send_payload: ${value} does not parse as JSON Object : ${error.message}`;
486
467
  if (has_elevated_debug) this.emit('device_debug', { ID:debugID, data: { error: 'EXSEND' ,states:[{id:stateDesc.id, value:value, payload:error.message}], IO:false }, message:message});
@@ -571,8 +552,8 @@ class StatesController extends EventEmitter {
571
552
  }
572
553
 
573
554
 
574
- setDeviceActivated(id, active) {
575
- this.adapter.extendObject(id, {common: {deactivated: active}});
555
+ setDeviceActivated(id, inActive) {
556
+ this.adapter.extendObject(id, {common: {deactivated: inActive, color:inActive ? '#888888' : null, statusStates: inActive ? null : {onlineId:`${id}.available`} }});
576
557
  }
577
558
 
578
559
  storeDeviceName(id, name) {
@@ -804,13 +785,10 @@ class StatesController extends EventEmitter {
804
785
  async applyLegacyDevices() {
805
786
  const legacyModels = await this.localConfig.getLegacyModels();
806
787
  const modelarr1 = [];
807
- //this.warn('devices are' + modelarr1.join(','));
808
788
  statesMapping.devices.forEach(item => modelarr1.push(item.models));
809
- //this.warn('legacy models are ' + JSON.stringify(legacyModels));
810
789
  statesMapping.setLegacyDevices(legacyModels);
811
790
  const modelarr2 = [];
812
791
  statesMapping.devices.forEach(item => modelarr2.push(item.models));
813
- //this.warn('devices are' + modelarr2.join(','));
814
792
  }
815
793
 
816
794
  async addLegacyDevice(model) {
@@ -856,7 +834,6 @@ class StatesController extends EventEmitter {
856
834
  } else {
857
835
  const model_modif = model.replace(/\//g, '-');
858
836
  const pathToAdminIcon = `img/${model_modif}.png`;
859
- // await this.fetchIcon(icon, '')
860
837
 
861
838
 
862
839
  if (icon.startsWith('http')) {
@@ -869,31 +846,82 @@ class StatesController extends EventEmitter {
869
846
  }
870
847
  }
871
848
 
872
- this.adapter.setObjectNotExists(id, {
873
- type: 'device',
874
- // actually this is an error, so device.common has no attribute type. It must be in native part
875
- common: {
876
- name: __dev_name,
877
- type: model,
878
- icon,
879
- modelIcon: modelIcon,
880
- color: null,
881
- statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
882
- },
883
- native: {id: dev_id}
884
- }, () => {
885
- // update type and icon
849
+ const obj = await this.adapter.getObjectAsync(id);
850
+
851
+ const myCommon = {
852
+ name: __dev_name,
853
+ type: model,
854
+ icon,
855
+ modelIcon: modelIcon,
856
+ color: (obj && obj.common && obj.common.deactivated) ? `#888888` : null,
857
+ statusStates: (obj && obj.common && obj.common.deactivated) ? null : {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
858
+ }
859
+ if (obj) {
886
860
  this.adapter.extendObject(id, {
887
- common: {
888
- name: __dev_name,
889
- type: model,
890
- icon,
891
- modelIcon: modelIcon,
892
- color: null,
893
- statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
894
- }
861
+ common: myCommon
895
862
  }, callback);
896
- });
863
+ } else {
864
+ this.adapter.setObjectNotExists(id, {
865
+ type: 'device',
866
+ // actually this is an error, so device.common has no attribute type. It must be in native part
867
+ common: myCommon,
868
+ native: {id: dev_id}
869
+ }, callback);
870
+ }
871
+ }
872
+
873
+ async streamToBufferFetch(readableStream) {
874
+ const reader = readableStream.getReader();
875
+ const chunks = [];
876
+ let done, value;
877
+ try {
878
+ while (true) {
879
+ const result = await reader.read();
880
+ done = result.done;
881
+ value = result.value;
882
+ if (done) break;
883
+ if (value) chunks.push(Buffer.from(value));
884
+ }
885
+ return Buffer.concat(chunks);
886
+ } catch (err) {
887
+ this.error(`error getting buffer from stream: ${err && err.message ? err.message : 'no reason given'}`);
888
+ throw err;
889
+ }
890
+ }
891
+
892
+ async fetchIcon(url, image_path) {
893
+ const namespace = `${this.adapter.name}.admin`;
894
+ try {
895
+ return new Promise((resolve, reject) => {
896
+ fetch(url)
897
+ .then(async response => {
898
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
899
+ const data = await this.streamToBufferFetch(response.body);
900
+ this.adapter.writeFile(namespace, image_path, data, (err) => {
901
+ if (err) {
902
+ this.error(`error writing ${image_path} to admin: ${err.message ? err.message : 'no message given'}`);
903
+ reject(err);
904
+ return;
905
+ }
906
+ this.info(`downloaded ${url} to ${image_path}.`);
907
+ resolve();
908
+ });
909
+ })
910
+ .catch(err => {
911
+ this.warn(`error downloading icon ${err && err.message ? err.message : 'no message given'}`);
912
+ reject(err);
913
+ })
914
+ .finally(() => {
915
+ const idx = this.ImagesToDownload.indexOf(url);
916
+ if (idx > -1) {
917
+ this.ImagesToDownload.splice(idx, 1);
918
+ }
919
+ });
920
+ });
921
+ }
922
+ catch (error) {
923
+ this.warn(`error fetching ${url} : ${error && error.message ? error.message : 'no reason given'}`)
924
+ }
897
925
  }
898
926
 
899
927
  async streamToBuffer(readableStream) {
@@ -921,12 +949,6 @@ class StatesController extends EventEmitter {
921
949
  });
922
950
  }
923
951
 
924
- async fetchIcon(url, image_path) {
925
- const response = await fetch(url);
926
- const data = await this.streamToBuffer(response.body);
927
- fs.writeFileSync('/opt/iobroker/iobroker-data/zigbee_0/test.png', response.data);
928
- }
929
-
930
952
  async downloadIcon(url, image_path) {
931
953
  try {
932
954
  const namespace = `${this.adapter.name}.admin`;
@@ -971,7 +993,6 @@ class StatesController extends EventEmitter {
971
993
  }
972
994
  });
973
995
  }
974
-
975
996
  CleanupRequired(set) {
976
997
  try {
977
998
  if (typeof set === 'boolean') this.cleanupRequired = set;
@@ -1060,8 +1081,10 @@ class StatesController extends EventEmitter {
1060
1081
  const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee'));
1061
1082
 
1062
1083
  const message = `message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`;
1063
- if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'01', IO:true }, message:message});
1064
- else if (this.debugActive) this.debug(message);
1084
+ if (has_elevated_debug)
1085
+ this.emit('device_debug', { ID:debugId, data: { deviceID: devId, flag:'03', IO:true }, message:message});
1086
+ else
1087
+ if (this.debugActive) this.debug(message);
1065
1088
  if (!devStates) {
1066
1089
  const message = `no device states for device ${devId} type '${model}'`;
1067
1090
  if (has_elevated_debug)this.emit('device_debug', { ID:debugId, data: { error:'NOSTATE',states:[{ id:'--', value:'--', payload:payload}], IO:true }, message:message});
@@ -1092,7 +1115,7 @@ class StatesController extends EventEmitter {
1092
1115
  let stateID = statedesc.id;
1093
1116
 
1094
1117
  const message = `value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`;
1095
- if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'02', IO:true }, message});
1118
+ if (has_elevated_debug) this.emit('device_debug', { ID:debugId, data: { states:[{id:stateID, value:value, payload:payload }],flag:'04', IO:true }, message});
1096
1119
  else if (this.debugActive) this.debug(message);
1097
1120
 
1098
1121
  const common = {
@@ -1179,35 +1202,57 @@ class StatesController extends EventEmitter {
1179
1202
  }
1180
1203
  }
1181
1204
 
1182
- async processConverters(converters, devId, model, mappedModel, message, meta, debugId) {
1183
- for (const converter of converters) {
1184
- const publish = (payload, dID) => {
1185
- if (typeof payload === 'object') {
1186
- this.publishToState(devId, model, payload,dID);
1187
- }
1188
- };
1189
-
1190
- const options = await new Promise((resolve, reject) => {
1191
- this.collectOptions(devId, model, false, (options) => {
1192
- resolve(options);
1193
- });
1205
+ async processConverters(converters, devId, model, mappedModel, message, meta, debugId, has_elevated_debug) {
1206
+ let cnt = 0;
1207
+ const publish = (payload, dID) => {
1208
+ if (typeof payload === 'object' && Object.keys(payload).length > 0) {
1209
+ this.publishToState(devId, model, payload,dID);
1210
+ }
1211
+ else if (has_elevated_debug)
1212
+ this.emit('device_debug', {ID:debugId,data: { error:`NOVAL`, IO:true }, message:` payload ${JSON.stringify(payload)} is empty`})
1213
+ };
1214
+ const options = await new Promise((resolve, reject) => {
1215
+ this.collectOptions(devId, model, false, (options) => {
1216
+ resolve(options);
1194
1217
  });
1218
+ });
1195
1219
 
1196
- const payload = await new Promise((resolve, reject) => {
1220
+ const chain = [];
1221
+ for (const converter of converters) {
1222
+ const idx = cnt++;
1223
+ chain.push(new Promise((resolve) => {
1224
+ if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}a`, IO:true }, message:`converter ${cnt} : Cluster ${converter.cluster}`})
1197
1225
  const payloadConv = converter.convert(mappedModel, message, publish, options, meta);
1226
+ const metapost = meta ? {
1227
+ deviceIEEE: meta.device ? meta.device.ieeeAddr : 'no device',
1228
+ deviceModelId: meta.device ? meta.device.ModelId : 'no device',
1229
+ logger: meta.logger ? (meta.logger.constructor ? meta.logger.constructor.name : 'not a class') : 'undefined',
1230
+ state : meta.state
1231
+ } : 'undefined';
1232
+ if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${idx}b`, IO:true }, message:` data: ${safeJsonStringify(message.data)} options: ${safeJsonStringify(options)} meta:${safeJsonStringify(metapost)} result:${safeJsonStringify(payloadConv)}`})
1198
1233
  if (typeof payloadConv === 'object') {
1199
1234
  resolve(payloadConv);
1200
1235
  }
1201
- });
1236
+ else resolve({});
1237
+ }));
1238
+ }
1239
+ const candidates = await Promise.all(chain);
1240
+ const payload = {};
1202
1241
 
1203
- if (Object.keys(payload).length > 0 && Object.keys(options).length > 0) {
1204
- this.postProcessConvertedFromZigbeeMessage(mappedModel, payload, options, null);
1205
- }
1242
+ for (const candidate of candidates) {
1243
+ for (const key in candidate)
1244
+ payload[key] = candidate[key];
1245
+ }
1206
1246
 
1207
- publish(payload, debugId);
1247
+ if (Object.keys(payload).length > 0 && Object.keys(options).length > 0) {
1248
+ const premsg = `candidates: ${JSON.stringify(candidates)} => payload ${JSON.stringify(payload)}`
1249
+ this.postProcessConvertedFromZigbeeMessage(mappedModel, payload, options, null);
1250
+ if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}d`, IO:true }, message:`${premsg} => processed payload : ${JSON.stringify(payload)}`})
1208
1251
  }
1209
- }
1252
+ else if (has_elevated_debug) this.emit('device_debug', {ID:debugId,data: { flag:`02.${cnt}c`, IO:true }, message:`candidates: ${JSON.stringify(candidates)} => payload ${JSON.stringify(payload)}`})
1210
1253
 
1254
+ publish(payload, debugId);
1255
+ }
1211
1256
 
1212
1257
  async onZigbeeEvent(type, entity, message) {
1213
1258
  if (this.debugActive) this.debug(`Type ${type} device ${safeJsonStringify(entity)} incoming event: ${safeJsonStringify(message)}`);
@@ -1293,9 +1338,6 @@ class StatesController extends EventEmitter {
1293
1338
  }
1294
1339
  }
1295
1340
 
1296
- // publish raw event to "from_zigbee"
1297
- // some cleanup
1298
-
1299
1341
  this.publishToState(devId, model, {msg_from_zigbee: safeJsonStringify(msgForState)}, -1);
1300
1342
 
1301
1343
  if (!entity.mapped) {
@@ -1311,6 +1353,11 @@ class StatesController extends EventEmitter {
1311
1353
  Array.isArray(c.type) ? c.type.includes('attributeReport') : c.type === 'attributeReport'));
1312
1354
  }
1313
1355
 
1356
+ if (has_elevated_debug) {
1357
+ const message = `${converters.length} converter${converters.length > 1 ? 's' : ''} available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`
1358
+ this.emit('device_debug', { ID:debugId, data: { flag:'02', IO:true }, message:message})
1359
+ }
1360
+
1314
1361
  if (!converters.length) {
1315
1362
  if (type !== 'readResponse' && type !== 'commandQueryNextImageRequest') {
1316
1363
  const message = `No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`;
@@ -1322,7 +1369,7 @@ class StatesController extends EventEmitter {
1322
1369
 
1323
1370
  meta.state = { state: '' }; // for tuya
1324
1371
 
1325
- this.processConverters(converters, devId, model, mappedModel, message, meta, debugId)
1372
+ this.processConverters(converters, devId, model, mappedModel, message, meta, debugId, has_elevated_debug)
1326
1373
  .catch((error) => {
1327
1374
  // 'Error: Expected one of: 0, 1, got: 'undefined''
1328
1375
  if (cluster !== '64529') {
@@ -1336,9 +1383,6 @@ class StatesController extends EventEmitter {
1336
1383
  async stop() {
1337
1384
  this.localConfig.retainData();
1338
1385
  }
1339
-
1340
-
1341
-
1342
1386
  }
1343
1387
 
1344
- module.exports = StatesController;
1388
+ module.exports = StatesController;
@@ -45,10 +45,10 @@ class DelayedAction extends BaseExtension {
45
45
 
46
46
  onZigbeeEvent(data) {
47
47
  try {
48
- const device = data.device;
49
- // if (this.shouldAction(device, mappedDevice)) {
50
- this.doActions(device);
51
- // }
48
+ if (data && data.device) {
49
+ const device = data.device;
50
+ this.doActions(device);
51
+ }
52
52
  } catch (error) {
53
53
  this.sendError(error);
54
54
  this.error(
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const BaseExtension = require('./zbBaseExtension');
4
- const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
5
4
  const utils = require('./utils');
6
5
 
7
6
  // Some EndDevices should be pinged
@@ -19,6 +18,11 @@ const Hours25 = 1000 * 60 * 60 * 25;
19
18
  const MinAvailabilityTimeout = 300; // ping every 5 minutes with few devices
20
19
  const MaxAvailabilityTimeout = 1800; // ping every 30 minutes with many devices;
21
20
  const AverageTimeBetweenPings = 45; // on average, plan for 30 seconds between pings.
21
+ const pingClusters = {
22
+ c0a0: {id:'genBasic', attribute:'zclVersion'},
23
+ c0a5: {id:'genBasic', attribute:'modelId'},
24
+ c25a0: {id:'genOta', attribute:'upgradeServerId'},
25
+ };
22
26
 
23
27
  /**
24
28
  * This extensions pings devices to check if they are online.
@@ -31,8 +35,6 @@ class DeviceAvailability extends BaseExtension {
31
35
  this.ping_counters = {};
32
36
  this.max_ping = 3;
33
37
  this.state = {};
34
- this.active_ping = true;
35
- this.forced_ping = true;
36
38
  this.forcedNonPingable = {};
37
39
  this.number_of_registered_devices = 0;
38
40
  // force publish availability for new devices
@@ -48,34 +50,14 @@ class DeviceAvailability extends BaseExtension {
48
50
  this.name = 'DeviceAvailability';
49
51
  this.elevate_debug = false;
50
52
  this.isStarted = false;
51
- this.active_ping = !config.disablePing;
52
- this.forced_ping = false;
53
+ this.active_ping = config.pingCluster != 'off';
53
54
  this.max_ping = 5;
54
55
  this.availability_timeout = Math.max(60, typeof config.pingTimeout == 'number' ? config.pingTimeout : 300);
55
56
  this.startReadDelay = config.readAllAtStart ? Math.max(500, Math.min(10000, config.startReadDelay * 1000)) : 0;
56
57
  this.debugDevices = [];
58
+ this.pingCluster = pingClusters[config.pingCluster] ? pingClusters[config.pingCluster] : {};
57
59
  }
58
60
 
59
- /* setOptions(options) {
60
- if (typeof options !== 'object') {
61
- return false;
62
- }
63
- if (options.disableActivePing) {
64
- this.active_ping = false;
65
- }
66
- if (options.disableForcedPing) {
67
- this.forced_ping = false;
68
- }
69
- if (typeof options.pingTimeout === 'number') {
70
- this.availability_timeout = Math.min(60, options.pingTimeout);
71
- }
72
- if (typeof options.pingCount === 'number') {
73
- this.max_ping = Math.min(2, options.pingCount);
74
- }
75
- this.startReadDelay = (typeof options.startReadDelay === 'number') ? Math.max(500, Math.min(10000, options.startReadDelay * 1000)) : 0;
76
- this.warn(`DA: setting options to ${JSON.stringify(options)} - Read Delay is ${this.startReadDelay}`)
77
- return true;
78
- } */
79
61
  checkDebugDevice(dev) {
80
62
  if (typeof dev != 'string' || dev == '') return false;
81
63
  if (this.debugDevices === undefined) return false;
@@ -115,7 +97,6 @@ class DeviceAvailability extends BaseExtension {
115
97
  if (!this.isStarted) return;
116
98
  this.debug(`register device Ping for ${JSON.stringify(device.ieeeAddr)}`);
117
99
  this.forcedNonPingable[device.ieeeAddr] = false;
118
- // this.warn(`Called registerDevicePing for '${device}' of '${entity}'`);
119
100
  if (!this.isPingable(device)) {
120
101
  return;
121
102
  }
@@ -145,7 +126,6 @@ class DeviceAvailability extends BaseExtension {
145
126
  }
146
127
 
147
128
  async startDevicePing() {
148
- // this.warn(JSON.stringify(this));
149
129
  if (!this.isStarted) return;
150
130
  this.startDevicePingTimeout = null;
151
131
  const item = this.startDevicePingQueue.shift();
@@ -174,21 +154,21 @@ class DeviceAvailability extends BaseExtension {
174
154
  const readables = [];
175
155
 
176
156
  this.isStarted = true;
177
- // this.warn('onZigbeeStarted called');
157
+ this.debug('onZigbeeStarted called');
178
158
 
179
159
  for (const device of clients) {
180
- if (this.isPingable(device)) {
160
+ if (this.isPingable(device) && this.active_ping) {
181
161
  readables.push(device);
182
162
  // this.setTimerPingable(device);
183
163
  } else {
184
- // this.warn(`Setting '${device.ieeeAddr}' as available - battery driven`);
164
+ this.debug(`Setting '${device.ieeeAddr}' as available - battery driven or no active availability check`);
185
165
  this.publishAvailability(device, true);
186
166
  this.timers[device.ieeeAddr] = setInterval(() =>
187
167
  this.handleIntervalNotPingable(device), utils.secondsToMilliseconds(this.availability_timeout));
188
168
  }
189
169
  }
190
170
  if (this.startReadDelay > 0 && readables.length > 0) {
191
- this.warn(`Triggering device_query on ${readables.length} devices in ${this.startReadDelay / 1000} seconds.`)
171
+ this.info(`Triggering device_query on ${readables.length} devices in ${this.startReadDelay / 1000} seconds.`)
192
172
  setTimeout(() => {
193
173
  readables.forEach(device => this.zigbee.doDeviceQuery(device, Date().now, false));
194
174
  }, this.startReadDelay)
@@ -197,6 +177,9 @@ class DeviceAvailability extends BaseExtension {
197
177
 
198
178
  async handleIntervalPingable(device, entity) {
199
179
  if (!this.isStarted) return;
180
+ if (!this.active_ping) {
181
+ return await this.handleIntervalnonPingable();
182
+ }
200
183
  const has_elevated_debug = this.checkDebugDevice(device.ieeeAddr)
201
184
 
202
185
  const ieeeAddr = device.ieeeAddr;
@@ -215,13 +198,29 @@ class DeviceAvailability extends BaseExtension {
215
198
  pingCount = {failed: 0, reported: 0};
216
199
  }
217
200
  try {
218
- //this.warn(`Pinging '${ieeeAddr}' (${device.modelID})`)
219
- await device.ping();
201
+ if (!this.pingCluster || !this.pingCluster.hasOwnProperty('id')) {
202
+ this.debug(`Pinging '${ieeeAddr}' (${device.modelID}) via ZH Ping`)
203
+ await device.ping();
204
+ }
205
+ else {
206
+ const zclData = {};
207
+ zclData[this.pingCluster.attribute] = {};
208
+ this.debug(`Pinging '${ieeeAddr}' (${device.modelID}) via ZCL Read with ${this.pingCluster.id}:${this.pingCluster.attribute}`)
209
+ await this.zigbee.publish(device, this.pingCluster.id, 'read', zclData, null, undefined, 'foundation');
210
+ }
220
211
  this.publishAvailability(device, true);
221
212
  if (has_elevated_debug) this.warn(`ELEVATED : Successfully pinged ${ieeeAddr} (${device.modelID}) in ${Date.now()-pt} ms`);
222
213
  this.setTimerPingable(device, 1);
223
214
  this.ping_counters[device.ieeeAddr].failed = 0;
224
215
  } catch (error) {
216
+ if (error && error.message && error.message.includes('UNSUPPORTED_ATTRIBUTE')) {
217
+ // this error is acceptable, as it is raised off an answer of the device.
218
+ this.publishAvailability(device, true);
219
+ if (has_elevated_debug) this.warn(`ELEVATED : Successfully pinged ${ieeeAddr} (${device.modelID}) in ${Date.now()-pt} ms`);
220
+ this.setTimerPingable(device, 1);
221
+ this.ping_counters[device.ieeeAddr].failed = 0;
222
+ return;
223
+ }
225
224
  if (has_elevated_debug) this.warn(`ELEVATED : Failed to ping ${ieeeAddr} (${device.modelID}) after ${Date.now()-pt} ms${error && error.message ? ' - '+error.message : ''}`);
226
225
  this.publishAvailability(device, false);
227
226
  if (pingCount.failed++ <= this.max_ping) {
@@ -90,6 +90,7 @@ class DeviceConfigure extends BaseExtension {
90
90
  }
91
91
 
92
92
  onZigbeeEvent(data, mappedDevice) {
93
+ if (!mappedDevice || !data.device) return;
93
94
  try {
94
95
  const device = data.device;
95
96
  const com = this.configureOnMessageAttempts[device.ieeeAddr];
@@ -100,7 +101,13 @@ class DeviceConfigure extends BaseExtension {
100
101
  this.info('Configure on Message for ' + device.ieeeAddr);
101
102
  this.doConfigure(device, mappedDevice);
102
103
  }
104
+ return;
105
+ }
106
+ // check if the event is a 'deviceInterview successful' Event
107
+ if (data.type == 'deviceJoined' || data.type == 'deviceInterview' && data.status == 'successful') {
108
+ this.PushDeviceToQueue(device, mappedDevice);
103
109
  }
110
+
104
111
  } catch (error) {
105
112
  this.sendError(error);
106
113
  this.error(`Failed to DeviceConfigure.onZigbeeEvent (${error && error.message ? error.message : 'no error message'})`);
@@ -2,7 +2,6 @@
2
2
 
3
3
  const BaseExtension = require('./zbBaseExtension');
4
4
  const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
5
-
6
5
  class DeviceEvent extends BaseExtension {
7
6
  constructor(zigbee, options) {
8
7
  super(zigbee, options);
@@ -10,8 +9,10 @@ class DeviceEvent extends BaseExtension {
10
9
  }
11
10
 
12
11
  async onZigbeeStarted() {
13
- for (const device of await this.zigbee.getClients()) {
14
- await this.callOnEvent(device, 'start', {});
12
+ for (const device of await this.zigbee.getClientIterator()) {
13
+ const entity = await this.zigbee.resolveEntity(device);
14
+
15
+ await this.callOnEvent(device, 'start', {device, options:entity.options || {}});
15
16
  }
16
17
  }
17
18
 
@@ -29,7 +30,7 @@ class DeviceEvent extends BaseExtension {
29
30
  async stop() {
30
31
  if (this.zigbee.getClients() > 0) {
31
32
  for (const device of await this.zigbee.getClients()) {
32
- await this.callOnEvent(device, 'stop', {});
33
+ await this.callOnEvent(device, 'stop', {ieeeAddr:device.ieeeAddr});
33
34
  }
34
35
  }
35
36
  }
@@ -38,9 +39,41 @@ class DeviceEvent extends BaseExtension {
38
39
  if (!mappedDevice) {
39
40
  mappedDevice = await zigbeeHerdsmanConverters.findByDevice(device);
40
41
  }
42
+ const baseData = {device, deviceExposeChanged: function() { return; }, options: data.options || {}, state: data.state || {}}
43
+ const eventData = {
44
+ type,
45
+ }
41
46
 
42
- if (mappedDevice && mappedDevice.onEvent) {
43
- mappedDevice.onEvent(type, data, device,mappedDevice.options,'{}');
47
+ switch (type) {
48
+ case 'start':
49
+ case 'deviceNetworkAddressChanged':
50
+ case 'deviceAnnounce':
51
+ case `deviceJoined`:
52
+ {
53
+ eventData.data = baseData;
54
+ break;
55
+ }
56
+ case 'stop':
57
+ eventData.data = { ieeeAddr:device.ieeeAddr };
58
+ break;
59
+ case 'deviceInterview':
60
+ eventData.data = {...baseData,status: data.status};
61
+ break;
62
+ case 'deviceOptionsChanged':
63
+ // NOTE: This does not currently work. OptionsChange is not yet defined.
64
+ eventData.data = {...baseData, from:data.from || {}, to:data.to || {}};
65
+ break;
66
+ }
67
+
68
+
69
+ if (mappedDevice && mappedDevice.onEvent && eventData.data) {
70
+ this.warn(`calling onEvent for ${eventData.type} on ${device.ieeeAddr}`);
71
+ try {
72
+ mappedDevice.onEvent(eventData);
73
+ }
74
+ catch (error) {
75
+ this.warn(`Error in onEvent: ${error && error.message ? error.message : 'no message'}`);
76
+ }
44
77
  }
45
78
  }
46
79
  }