iobroker.zigbee 1.6.3 → 1.6.14

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.
@@ -4,6 +4,9 @@ const EventEmitter = require('events').EventEmitter;
4
4
  const statesMapping = require('./devices');
5
5
  const getAdId = require('./utils').getAdId;
6
6
  const getZbId = require('./utils').getZbId;
7
+ const fs = require('fs');
8
+ var savedDeviceNames = {};
9
+ var knownUndefinedDevices = {};
7
10
 
8
11
 
9
12
  class StatesController extends EventEmitter {
@@ -13,6 +16,13 @@ class StatesController extends EventEmitter {
13
16
  this.adapter.on('stateChange', this.onStateChange.bind(this));
14
17
  this.query_device_block = [];
15
18
  this.debugDevices = undefined;
19
+ let fn = adapter.expandFileName('dev_names.json');
20
+ this.dev_names_fn = fn.replace('.', '_');
21
+ fs.readFile(this.dev_names_fn, (err, data) => {
22
+ if (!err) {
23
+ savedDeviceNames = JSON.parse(data);
24
+ }
25
+ });
16
26
  }
17
27
 
18
28
  info(message, data) {
@@ -35,6 +45,16 @@ class StatesController extends EventEmitter {
35
45
  this.adapter.sendError(error, message);
36
46
  }
37
47
 
48
+ retainDeviceNames()
49
+ {
50
+ fs.writeFile(this.dev_names_fn, JSON.stringify(savedDeviceNames, null, 2), (err) => {
51
+ if (err)
52
+ this.error('error saving device names: ' + JSON.Stringify(err));
53
+ else
54
+ this.debug('saved device names');
55
+ });
56
+ }
57
+
38
58
  getDebugDevices() {
39
59
  this.debugDevices = [];
40
60
  this.adapter.getState(this.adapter.namespace + '.info.debugmessages', (err, state) => {
@@ -55,8 +75,20 @@ class StatesController extends EventEmitter {
55
75
  });
56
76
  }
57
77
  });
58
-
78
+ this.adapter.setObject('info.undefinedDevices', {
79
+ 'type': 'state',
80
+ 'common': {
81
+ 'name': 'Recorded undefined devices',
82
+ 'role': '',
83
+ 'type': 'string',
84
+ 'read': true,
85
+ 'write': false,
86
+ },
87
+ 'native': {},
88
+ });
89
+ this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
59
90
  }
91
+
60
92
  onStateChange(id, state){
61
93
  if (!this.adapter.zbController || !this.adapter.zbController.connected()) return;
62
94
  if (this.debugDevices === undefined) this.getDebugDevices();
@@ -91,6 +123,10 @@ class StatesController extends EventEmitter {
91
123
  if (obj) {
92
124
  const model = obj.common.type;
93
125
  if (!model) return;
126
+ if (obj.common.deactivated) {
127
+ this.debug('State Change detected on deactivated Device - ignored');
128
+ return;
129
+ }
94
130
  if (model === 'group') {
95
131
  deviceId = parseInt(deviceId.replace('0xgroup_', ''));
96
132
  }
@@ -116,20 +152,25 @@ class StatesController extends EventEmitter {
116
152
  return;
117
153
  }
118
154
  let cnt = 0;
119
- const len = states.length;
120
- states.forEach(statedesc => {
121
- const id = this.adapter.namespace + '.' + devId + '.' + statedesc.id;
122
- this.adapter.getState(id, (err, state) => {
123
- cnt = cnt + 1;
124
- if (!err && state) {
125
- result[statedesc.id] = state.val;
126
- }
127
- if (cnt === len) {
128
- callback(result);
129
- }
155
+ try {
156
+ const len = states.length;
157
+ states.forEach(statedesc => {
158
+ const id = this.adapter.namespace + '.' + devId + '.' + statedesc.id;
159
+ this.adapter.getState(id, (err, state) => {
160
+ cnt = cnt + 1;
161
+ if (!err && state) {
162
+ result[statedesc.id] = state.val;
163
+ }
164
+ if (cnt === len) {
165
+ callback(result);
166
+ }
167
+ });
130
168
  });
131
- });
132
- if (!len) callback(result);
169
+ if (!len) callback(result);
170
+ } catch (error) {
171
+ this.sendError(error);
172
+ this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
173
+ }
133
174
  }
134
175
 
135
176
  async getDevStates(deviceId, model) {
@@ -141,7 +182,15 @@ class StatesController extends EventEmitter {
141
182
  } else {
142
183
  stateModel = statesMapping.findModel(model);
143
184
  if (!stateModel) {
144
- this.error('Device ' + deviceId + ' "' + model + '" not described in statesMapping.');
185
+ if (knownUndefinedDevices[deviceId])
186
+ {
187
+ knownUndefinedDevices[deviceId]++;
188
+ }
189
+ else {
190
+ knownUndefinedDevices[deviceId] = 1;
191
+ this.error('Device ' + deviceId + ' "' + model + '" not described in statesMapping.');
192
+ }
193
+ this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
145
194
  states = statesMapping.commonStates;
146
195
  } else {
147
196
  states = stateModel.states;
@@ -221,9 +270,28 @@ class StatesController extends EventEmitter {
221
270
  }
222
271
 
223
272
  renameDevice(id, newName) {
273
+ this.storeDeviceName(id, newName);
224
274
  this.adapter.extendObject(id, {common: {name: newName}});
225
275
  }
226
276
 
277
+ setDeviceActivated(id, active) {
278
+ this.adapter.extendObject(id, {common: {deactivated: active }})
279
+ }
280
+
281
+ storeDeviceName(id, name) {
282
+ savedDeviceNames[id.replace(`${this.adapter.namespace}.`, '')] = name;
283
+ this.retainDeviceNames();
284
+ }
285
+
286
+ verifyDeviceName(id, name) {
287
+ const savedId = id.replace(`${this.adapter.namespace}.`, '');
288
+ if (!savedDeviceNames.hasOwnProperty(savedId)) {
289
+ savedDeviceNames[savedId] = name;
290
+ this.retainDeviceNames();
291
+ }
292
+ return savedDeviceNames[savedId];
293
+ }
294
+
227
295
  deleteDeviceStates(devId, callback) {
228
296
  this.adapter.getStatesOf(devId, (err, states) => {
229
297
  if (!err && states) {
@@ -236,6 +304,15 @@ class StatesController extends EventEmitter {
236
304
  });
237
305
  });
238
306
  }
307
+
308
+ async deleteDeviceStatesAsync(devId) {
309
+ const states = await this.adapter.getStatesOf(devId);
310
+ if (states) {
311
+ await this.adapter.deleteState(devId, null, state._id);
312
+ }
313
+ await this.adapter.deleteDevice(devId);
314
+ }
315
+
239
316
  // eslint-disable-next-line no-unused-vars
240
317
  async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback) {
241
318
  const devStates = await this.getDevStates(ieeeAddr, model);
@@ -254,7 +331,7 @@ class StatesController extends EventEmitter {
254
331
  statename = arr[1];
255
332
  }
256
333
  if (commonStates.find((statedesc) => statename === statedesc.id) === undefined &&
257
- devStates.states.find((statedesc) => statename === statedesc.id) === undefined && statename != 'groups') {
334
+ devStates.states.find((statedesc) => statename === statedesc.id) === undefined) {
258
335
  if (state.common.hasOwnProperty('custom') && !force) {
259
336
  this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
260
337
  } else {
@@ -278,88 +355,127 @@ class StatesController extends EventEmitter {
278
355
  updateState(devId, name, value, common) {
279
356
  this.adapter.getObject(devId, (err, obj) => {
280
357
  if (obj) {
281
- const new_common = {name: name};
282
- const id = devId + '.' + name;
283
- const new_name = obj.common.name;
284
- if (common) {
285
- if (common.name !== undefined) {
286
- new_common.name = common.name;
287
- }
288
- if (common.type !== undefined) {
289
- new_common.type = common.type;
290
- }
291
- if (common.unit !== undefined) {
292
- new_common.unit = common.unit;
293
- }
294
- if (common.states !== undefined) {
295
- new_common.states = common.states;
296
- }
297
- if (common.read !== undefined) {
298
- new_common.read = common.read;
299
- }
300
- if (common.write !== undefined) {
301
- new_common.write = common.write;
302
- }
303
- if (common.role !== undefined) {
304
- new_common.role = common.role;
305
- }
306
- if (common.min !== undefined) {
307
- new_common.min = common.min;
308
- }
309
- if (common.max !== undefined) {
310
- new_common.max = common.max;
311
- }
312
- if (common.icon !== undefined) {
313
- new_common.icon = common.icon;
358
+ if (!obj.common.deactivated) {
359
+ const new_common = {name: name};
360
+ const id = devId + '.' + name;
361
+ const new_name = obj.common.name;
362
+ if (common) {
363
+ if (common.name !== undefined) {
364
+ new_common.name = common.name;
365
+ }
366
+ if (common.type !== undefined) {
367
+ new_common.type = common.type;
368
+ }
369
+ if (common.unit !== undefined) {
370
+ new_common.unit = common.unit;
371
+ }
372
+ if (common.states !== undefined) {
373
+ new_common.states = common.states;
374
+ }
375
+ if (common.read !== undefined) {
376
+ new_common.read = common.read;
377
+ }
378
+ if (common.write !== undefined) {
379
+ new_common.write = common.write;
380
+ }
381
+ if (common.role !== undefined) {
382
+ new_common.role = common.role;
383
+ }
384
+ if (common.min !== undefined) {
385
+ new_common.min = common.min;
386
+ }
387
+ if (common.max !== undefined) {
388
+ new_common.max = common.max;
389
+ }
390
+ if (common.icon !== undefined) {
391
+ new_common.icon = common.icon;
392
+ }
314
393
  }
315
- }
316
- // check if state exist
317
- this.adapter.getObject(id, (err, stobj) => {
318
- let hasChanges = false;
319
- if (stobj) {
320
- // update state - not change name and role (user can it changed)
321
- if (stobj.common.name)
322
- delete new_common.name;
323
- else
324
- new_common.name = new_name + ' ' + new_common.name;
325
- delete new_common.role;
326
-
327
- // check whether any common property is different
328
- if (stobj.common) {
329
- for (const property in new_common) {
330
- if (stobj.common.hasOwnProperty(property)) {
331
- if (stobj.common[property] === new_common[property]) {
332
- delete new_common[property];
333
- } else {
334
- hasChanges = true;
394
+ // check if state exist
395
+ this.adapter.getObject(id, (err, stobj) => {
396
+ let hasChanges = false;
397
+ if (stobj) {
398
+ // update state - not change name and role (user can it changed)
399
+ if (stobj.common.name)
400
+ delete new_common.name;
401
+ else
402
+ new_common.name = new_name + ' ' + new_common.name;
403
+ delete new_common.role;
404
+
405
+ // check whether any common property is different
406
+ if (stobj.common) {
407
+ for (const property in new_common) {
408
+ if (stobj.common.hasOwnProperty(property)) {
409
+ if (stobj.common[property] === new_common[property]) {
410
+ delete new_common[property];
411
+ } else {
412
+ hasChanges = true;
413
+ }
335
414
  }
336
415
  }
337
416
  }
417
+ } else {
418
+ hasChanges = true;
338
419
  }
339
- } else {
340
- hasChanges = true;
341
- }
342
-
343
- // only change object when any common property has changed
344
- if (hasChanges) {
345
- this.adapter.extendObject(id, {type: 'state', common: new_common, native: {} }, () => {
346
- value !== undefined && this.adapter.setState(id, value, true);
347
- });
348
- } else if (value !== undefined) {
349
- if (typeof(value) !== 'object') {
350
- this.adapter.setState(id, value, true);
351
- } else this.warn('set state with object for id :' + JSON.stringify(id) + ' '+ JSON.stringify(value));
352
420
 
353
- }
421
+ // only change object when any common property has changed
422
+ if (hasChanges) {
423
+ this.adapter.extendObject(id, {type: 'state', common: new_common, native: {} }, () => {
424
+ value !== undefined && this.setState_typed(id, value, true, (stobj) ? stobj.common.type : new_common.type);
425
+ });
426
+ } else if (value !== undefined) {
427
+ this.setState_typed(id, value, true, stobj.common.type);
428
+ }
354
429
 
355
- });
430
+ });
431
+ }
432
+ else this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
356
433
  } else {
357
- this.debug(`Wrong device ${devId} ${JSON.stringify(obj)}`);
434
+ this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
358
435
  }
359
436
  });
360
437
  }
361
438
 
439
+ setState_typed(id, value, ack, type, callback)
440
+ {
441
+ // never set a null or undefined value
442
+ if (value === null || value === undefined) return;
443
+ if (!type) {
444
+ this.debug("SetState_typed called without type");
445
+ // identify datatype, recursively call this function with set datatype
446
+ this.adapter.getObject(id, (err, obj) => {
447
+ if (obj && obj.common)
448
+ this.setState_typed(id, value, ack, obj.common.type, callback);
449
+ else {
450
+ this.setState_typed(id, value, ack, 'noobj', callback);
451
+ }
452
+ });
453
+ return;
454
+ }
455
+ if (typeof value != type) {
456
+ this.debug("SetState_typed : converting " + JSON.stringify(value) + " for " + id + " from " + typeof value + " to " + type);
457
+ switch (type) {
458
+ case 'number':
459
+ value = parseFloat(value);
460
+ if (isNaN (value)) value = 0;
461
+ break;
462
+ case 'string':
463
+ case 'text': value = JSON.stringify(value); break;
464
+ case 'boolean':
465
+ if (typeof value == 'number') {
466
+ value = (value != 0);
467
+ break;
468
+ }
469
+ const sval = JSON.stringify(value).toLowerCase().trim();
470
+ value = (sval == 'true' || sval == 'yes' || sval == 'on');
471
+ break;
472
+ }
473
+ }
474
+ this.adapter.setState(id, value, ack, callback);
475
+ }
476
+
362
477
  updateDev(dev_id, dev_name, model, callback) {
478
+ const __dev_name = this.verifyDeviceName(dev_id, dev_name);
363
479
  const id = '' + dev_id;
364
480
  const modelDesc = statesMapping.findModel(model);
365
481
  let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
@@ -368,7 +484,7 @@ class StatesController extends EventEmitter {
368
484
  this.adapter.setObjectNotExists(id, {
369
485
  type: 'device',
370
486
  // actually this is an error, so device.common has no attribute type. It must be in native part
371
- common: {name: dev_name, type: model, icon: icon},
487
+ common: {name: __dev_name, type: model, icon: icon},
372
488
  native: {id: dev_id}
373
489
  }, () => {
374
490
  // update type and icon
@@ -377,15 +493,13 @@ class StatesController extends EventEmitter {
377
493
  }
378
494
 
379
495
  async syncDevStates(dev, model) {
380
- const devId = dev.ieeeAddr.substr(2),
381
- hasGroups = dev.type === 'Router';
496
+ const devId = dev.ieeeAddr.substr(2);
382
497
  // devId - iobroker device id
383
498
  const devStates = await this.getDevStates(dev.ieeeAddr, model);
384
499
  if (!devStates) {
385
500
  return;
386
501
  }
387
- const states = statesMapping.commonStates.concat(devStates.states)
388
- .concat((hasGroups) ? [statesMapping.groupsState] : []);
502
+ const states = statesMapping.commonStates.concat(devStates.states);
389
503
 
390
504
  for (const stateInd in states) {
391
505
  if (!states.hasOwnProperty(stateInd)) continue;
@@ -432,6 +546,7 @@ class StatesController extends EventEmitter {
432
546
  for (const addressPart of this.debugDevices) {
433
547
  if (typeof(devId) == 'string' && devId.indexOf(addressPart) > -1)
434
548
  {
549
+ if (payload.hasOwnProperty('msg_from_zigbee')) break;
435
550
  this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
436
551
  has_debug = true;
437
552
  break;
@@ -454,10 +569,10 @@ class StatesController extends EventEmitter {
454
569
  value = payload[statedesc.prop || statedesc.id];
455
570
  }
456
571
  // checking value
457
- if (value === undefined) continue;
572
+ if (value === undefined || value === null) continue;
458
573
  let stateID = statedesc.id;
459
574
 
460
- if (has_debug) {
575
+ if (has_debug && statedesc.id != 'msg_from_zigbee') {
461
576
  this.warn(`ELEVATED publishToState: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
462
577
  }
463
578
 
package/lib/utils.js CHANGED
@@ -115,7 +115,7 @@ const xiaomiManufacturerID = [4151, 4447];
115
115
  const ikeaTradfriManufacturerID = [4476];
116
116
 
117
117
  function sanitizeImageParameter(parameter) {
118
- const replaceByDash = [/\?/g, /&/g, /[^a-z\d\- _./:]/gi];
118
+ const replaceByDash = [/\?/g, /&/g, /[^a-z\d\-_./:]/gi, /[/]/gi];
119
119
  let sanitized = parameter;
120
120
  replaceByDash.forEach((r) => sanitized = sanitized.replace(r, '-'));
121
121
  return sanitized;
@@ -5,6 +5,7 @@
5
5
  class BaseExtension {
6
6
  constructor(zigbee, options) {
7
7
  this.zigbee = zigbee;
8
+ this.name = 'BaseExtension';
8
9
  }
9
10
 
10
11
  info(message, data) {
@@ -12,15 +13,15 @@ class BaseExtension {
12
13
  }
13
14
 
14
15
  error(message, data) {
15
- this.zigbee.error(message, data);
16
+ this.zigbee.error(this.name + ':' + message, data);
16
17
  }
17
18
 
18
19
  warn(message, data) {
19
- this.zigbee.warn(message, data);
20
+ this.zigbee.warn(this.name + ':' + message, data);
20
21
  }
21
22
 
22
23
  debug(message, data) {
23
- this.zigbee.debug(message, data);
24
+ this.zigbee.debug(this.name + ':' + message, data);
24
25
  }
25
26
 
26
27
  sendError(error, message) {
@@ -8,6 +8,7 @@ class DelayedAction extends BaseExtension {
8
8
 
9
9
  this.actions = {};
10
10
  this.zigbee.delayAction = this.delayAction.bind(this);
11
+ this.name = "DelayedAction";
11
12
  }
12
13
 
13
14
  setOptions(options) {
@@ -39,6 +39,7 @@ class DeviceAvailability extends BaseExtension {
39
39
  this.state = {};
40
40
  this.active_ping = true;
41
41
  this.forced_ping = true;
42
+ this.forcedNonPingable = {};
42
43
  this.number_of_registered_devices = 0;
43
44
  // force publish availability for new devices
44
45
  this.zigbee.on('new', (entity) => {
@@ -50,6 +51,7 @@ class DeviceAvailability extends BaseExtension {
50
51
  this.startDevicePingQueue = []; // simple fifo array for starting device pings
51
52
  this.startDevicePingTimeout = null; // handle for the timeout which empties the queue
52
53
  this.startDevicePingDelay = 200; // 200 ms delay between starting the ping timeout
54
+ this.name = "DeviceAvailability";
53
55
  }
54
56
 
55
57
  setOptions(options) {
@@ -80,6 +82,8 @@ class DeviceAvailability extends BaseExtension {
80
82
  }
81
83
 
82
84
  async registerDevicePing(device, entity) {
85
+ this.debug('register device Ping for ' + JSON.stringify(device.ieeeAddr));
86
+ this.forcedNonPingable[device.ieeeAddr] = false;
83
87
  // this.warn(`Called registerDevicePing for '${device}' of '${entity}'`);
84
88
  if (!this.isPingable(device)) return;
85
89
  // ensure we do not already have this device in the queue
@@ -96,6 +100,14 @@ class DeviceAvailability extends BaseExtension {
96
100
  }, this.startDevicePingDelay);
97
101
  }
98
102
 
103
+ async deregisterDevicePing(device) {
104
+ this.info('deregister device Ping for deactivated device ' + JSON.stringify(device.ieeeAddr));
105
+ this.forcedNonPingable[device.ieeeAddr] = true;
106
+ if (this.timers[device.ieeeAddr]) {
107
+ clearTimeout(this.timers[device.ieeeAddr]);
108
+ }
109
+ }
110
+
99
111
  async startDevicePing() {
100
112
  // this.warn(JSON.stringify(this));
101
113
  this.startDevicePingTimeout = null;
@@ -265,10 +277,10 @@ class DeviceAvailability extends BaseExtension {
265
277
  // }
266
278
  }
267
279
  }
268
-
280
+
269
281
  onZigbeeEvent(data) {
270
282
  const device = data.device;
271
- if (!device) {
283
+ if (!device || this.forcedNonPingable[device.ieeeAddr]) {
272
284
  return;
273
285
  }
274
286
 
@@ -18,6 +18,7 @@ class DeviceConfigure extends BaseExtension {
18
18
 
19
19
  this.configuring = new Set();
20
20
  this.attempts = {};
21
+ this.name = "DeviceConfigure";
21
22
  }
22
23
 
23
24
  setOptions(options) {
@@ -119,13 +120,8 @@ class DeviceConfigure extends BaseExtension {
119
120
  if (!this.attempts.hasOwnProperty(device.ieeeAddr)) {
120
121
  this.attempts[device.ieeeAddr] = 0;
121
122
  }
122
-
123
- this.info(`Configuring ${device.ieeeAddr} ${device.modelID}`);
124
123
  try {
125
- await mappedDevice.configure(device, this.coordinatorEndpoint);
126
- this.info(`DeviceConfigure successful ${device.ieeeAddr} ${device.modelID}`);
127
- device.meta.configured = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
128
- device.save();
124
+ await this.doConfigure(device, mappedDevice);
129
125
  } catch (error) {
130
126
  this.sendError(error);
131
127
  this.warn(
@@ -134,7 +130,6 @@ class DeviceConfigure extends BaseExtension {
134
130
  );
135
131
  this.attempts[device.ieeeAddr]++;
136
132
  }
137
-
138
133
  this.configuring.delete(device.ieeeAddr);
139
134
  } catch (error) {
140
135
  this.sendError(error);
@@ -143,6 +138,15 @@ class DeviceConfigure extends BaseExtension {
143
138
  );
144
139
  }
145
140
  }
141
+
142
+ async doConfigure(device, mappedDevice) {
143
+ this.info(`Configuring ${device.ieeeAddr} ${device.modelID}`);
144
+ const coordinatorEndpoint = await this.zigbee.getDevicesByType('Coordinator')[0].endpoints[0];
145
+ await mappedDevice.configure(device, coordinatorEndpoint);
146
+ device.meta.configured = zigbeeHerdsmanConverters.getConfigureKey(mappedDevice);
147
+ device.save();
148
+ this.info(`DeviceConfigure successful ${device.ieeeAddr} ${device.modelID}`);
149
+ }
146
150
  }
147
151
 
148
152
  module.exports = DeviceConfigure;
@@ -4,6 +4,12 @@ const BaseExtension = require('./zbBaseExtension');
4
4
  const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
5
5
 
6
6
  class DeviceEvent extends BaseExtension {
7
+ constructor(zigbee, options) {
8
+ super(zigbee, options);
9
+ this.name = "DeviceEvent";
10
+ }
11
+
12
+
7
13
  async onZigbeeStarted() {
8
14
  for (const device of await this.zigbee.getClients()) {
9
15
  this.callOnEvent(device, 'start', {});