iobroker.zigbee 1.6.6 → 1.6.15

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,21 @@ 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
+ this.retTimeoutHandle = null;
22
+ fs.readFile(this.dev_names_fn, (err, data) => {
23
+ if (!err) {
24
+ try
25
+ {
26
+ savedDeviceNames = JSON.parse(data);
27
+ }
28
+ catch
29
+ {
30
+ savedDeviceNames = {};
31
+ };
32
+ }
33
+ });
16
34
  }
17
35
 
18
36
  info(message, data) {
@@ -35,6 +53,18 @@ class StatesController extends EventEmitter {
35
53
  this.adapter.sendError(error, message);
36
54
  }
37
55
 
56
+ retainDeviceNames()
57
+ {
58
+ clearTimeout(this.retTimeoutHandle);
59
+ this.retTimeoutHanlde = setTimeout(()=> {
60
+ fs.writeFile(this.dev_names_fn, JSON.stringify(savedDeviceNames, null, 2), (err) => {
61
+ if (err)
62
+ this.error('error saving device names: ' + JSON.Stringify(err));
63
+ else
64
+ this.debug('saved device names');
65
+ });},5000);
66
+ }
67
+
38
68
  getDebugDevices() {
39
69
  this.debugDevices = [];
40
70
  this.adapter.getState(this.adapter.namespace + '.info.debugmessages', (err, state) => {
@@ -55,8 +85,20 @@ class StatesController extends EventEmitter {
55
85
  });
56
86
  }
57
87
  });
58
-
88
+ this.adapter.setObject('info.undefinedDevices', {
89
+ 'type': 'state',
90
+ 'common': {
91
+ 'name': 'Recorded undefined devices',
92
+ 'role': '',
93
+ 'type': 'string',
94
+ 'read': true,
95
+ 'write': false,
96
+ },
97
+ 'native': {},
98
+ });
99
+ this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
59
100
  }
101
+
60
102
  onStateChange(id, state){
61
103
  if (!this.adapter.zbController || !this.adapter.zbController.connected()) return;
62
104
  if (this.debugDevices === undefined) this.getDebugDevices();
@@ -91,6 +133,10 @@ class StatesController extends EventEmitter {
91
133
  if (obj) {
92
134
  const model = obj.common.type;
93
135
  if (!model) return;
136
+ if (obj.common.deactivated) {
137
+ this.debug('State Change detected on deactivated Device - ignored');
138
+ return;
139
+ }
94
140
  if (model === 'group') {
95
141
  deviceId = parseInt(deviceId.replace('0xgroup_', ''));
96
142
  }
@@ -116,20 +162,25 @@ class StatesController extends EventEmitter {
116
162
  return;
117
163
  }
118
164
  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
- }
165
+ try {
166
+ const len = states.length;
167
+ states.forEach(statedesc => {
168
+ const id = this.adapter.namespace + '.' + devId + '.' + statedesc.id;
169
+ this.adapter.getState(id, (err, state) => {
170
+ cnt = cnt + 1;
171
+ if (!err && state) {
172
+ result[statedesc.id] = state.val;
173
+ }
174
+ if (cnt === len) {
175
+ callback(result);
176
+ }
177
+ });
130
178
  });
131
- });
132
- if (!len) callback(result);
179
+ if (!len) callback(result);
180
+ } catch (error) {
181
+ this.sendError(error);
182
+ this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
183
+ }
133
184
  }
134
185
 
135
186
  async getDevStates(deviceId, model) {
@@ -141,7 +192,15 @@ class StatesController extends EventEmitter {
141
192
  } else {
142
193
  stateModel = statesMapping.findModel(model);
143
194
  if (!stateModel) {
144
- this.error('Device ' + deviceId + ' "' + model + '" not described in statesMapping.');
195
+ if (knownUndefinedDevices[deviceId])
196
+ {
197
+ knownUndefinedDevices[deviceId]++;
198
+ }
199
+ else {
200
+ knownUndefinedDevices[deviceId] = 1;
201
+ this.error('Device ' + deviceId + ' "' + model + '" not described in statesMapping.');
202
+ }
203
+ this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
145
204
  states = statesMapping.commonStates;
146
205
  } else {
147
206
  states = stateModel.states;
@@ -221,9 +280,28 @@ class StatesController extends EventEmitter {
221
280
  }
222
281
 
223
282
  renameDevice(id, newName) {
283
+ this.storeDeviceName(id, newName);
224
284
  this.adapter.extendObject(id, {common: {name: newName}});
225
285
  }
226
286
 
287
+ setDeviceActivated(id, active) {
288
+ this.adapter.extendObject(id, {common: {deactivated: active }})
289
+ }
290
+
291
+ storeDeviceName(id, name) {
292
+ savedDeviceNames[id.replace(`${this.adapter.namespace}.`, '')] = name;
293
+ this.retainDeviceNames();
294
+ }
295
+
296
+ verifyDeviceName(id, name) {
297
+ const savedId = id.replace(`${this.adapter.namespace}.`, '');
298
+ if (!savedDeviceNames.hasOwnProperty(savedId)) {
299
+ savedDeviceNames[savedId] = name;
300
+ this.retainDeviceNames();
301
+ }
302
+ return savedDeviceNames[savedId];
303
+ }
304
+
227
305
  deleteDeviceStates(devId, callback) {
228
306
  this.adapter.getStatesOf(devId, (err, states) => {
229
307
  if (!err && states) {
@@ -236,6 +314,15 @@ class StatesController extends EventEmitter {
236
314
  });
237
315
  });
238
316
  }
317
+
318
+ async deleteDeviceStatesAsync(devId) {
319
+ const states = await this.adapter.getStatesOf(devId);
320
+ if (states) {
321
+ await this.adapter.deleteState(devId, null, state._id);
322
+ }
323
+ await this.adapter.deleteDevice(devId);
324
+ }
325
+
239
326
  // eslint-disable-next-line no-unused-vars
240
327
  async deleteOrphanedDeviceStates(ieeeAddr, model, force, callback) {
241
328
  const devStates = await this.getDevStates(ieeeAddr, model);
@@ -254,7 +341,7 @@ class StatesController extends EventEmitter {
254
341
  statename = arr[1];
255
342
  }
256
343
  if (commonStates.find((statedesc) => statename === statedesc.id) === undefined &&
257
- devStates.states.find((statedesc) => statename === statedesc.id) === undefined && statename != 'groups') {
344
+ devStates.states.find((statedesc) => statename === statedesc.id) === undefined) {
258
345
  if (state.common.hasOwnProperty('custom') && !force) {
259
346
  this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
260
347
  } else {
@@ -278,88 +365,127 @@ class StatesController extends EventEmitter {
278
365
  updateState(devId, name, value, common) {
279
366
  this.adapter.getObject(devId, (err, obj) => {
280
367
  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;
368
+ if (!obj.common.deactivated) {
369
+ const new_common = {name: name};
370
+ const id = devId + '.' + name;
371
+ const new_name = obj.common.name;
372
+ if (common) {
373
+ if (common.name !== undefined) {
374
+ new_common.name = common.name;
375
+ }
376
+ if (common.type !== undefined) {
377
+ new_common.type = common.type;
378
+ }
379
+ if (common.unit !== undefined) {
380
+ new_common.unit = common.unit;
381
+ }
382
+ if (common.states !== undefined) {
383
+ new_common.states = common.states;
384
+ }
385
+ if (common.read !== undefined) {
386
+ new_common.read = common.read;
387
+ }
388
+ if (common.write !== undefined) {
389
+ new_common.write = common.write;
390
+ }
391
+ if (common.role !== undefined) {
392
+ new_common.role = common.role;
393
+ }
394
+ if (common.min !== undefined) {
395
+ new_common.min = common.min;
396
+ }
397
+ if (common.max !== undefined) {
398
+ new_common.max = common.max;
399
+ }
400
+ if (common.icon !== undefined) {
401
+ new_common.icon = common.icon;
402
+ }
314
403
  }
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;
404
+ // check if state exist
405
+ this.adapter.getObject(id, (err, stobj) => {
406
+ let hasChanges = false;
407
+ if (stobj) {
408
+ // update state - not change name and role (user can it changed)
409
+ if (stobj.common.name)
410
+ delete new_common.name;
411
+ else
412
+ new_common.name = new_name + ' ' + new_common.name;
413
+ delete new_common.role;
414
+
415
+ // check whether any common property is different
416
+ if (stobj.common) {
417
+ for (const property in new_common) {
418
+ if (stobj.common.hasOwnProperty(property)) {
419
+ if (stobj.common[property] === new_common[property]) {
420
+ delete new_common[property];
421
+ } else {
422
+ hasChanges = true;
423
+ }
335
424
  }
336
425
  }
337
426
  }
427
+ } else {
428
+ hasChanges = true;
338
429
  }
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
430
 
353
- }
431
+ // only change object when any common property has changed
432
+ if (hasChanges) {
433
+ this.adapter.extendObject(id, {type: 'state', common: new_common, native: {} }, () => {
434
+ value !== undefined && this.setState_typed(id, value, true, (stobj) ? stobj.common.type : new_common.type);
435
+ });
436
+ } else if (value !== undefined) {
437
+ this.setState_typed(id, value, true, stobj.common.type);
438
+ }
354
439
 
355
- });
440
+ });
441
+ }
442
+ else this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
356
443
  } else {
357
- this.debug(`Wrong device ${devId} ${JSON.stringify(obj)}`);
444
+ this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
358
445
  }
359
446
  });
360
447
  }
361
448
 
449
+ setState_typed(id, value, ack, type, callback)
450
+ {
451
+ // never set a null or undefined value
452
+ if (value === null || value === undefined) return;
453
+ if (!type) {
454
+ this.debug("SetState_typed called without type");
455
+ // identify datatype, recursively call this function with set datatype
456
+ this.adapter.getObject(id, (err, obj) => {
457
+ if (obj && obj.common)
458
+ this.setState_typed(id, value, ack, obj.common.type, callback);
459
+ else {
460
+ this.setState_typed(id, value, ack, 'noobj', callback);
461
+ }
462
+ });
463
+ return;
464
+ }
465
+ if (typeof value != type) {
466
+ this.debug("SetState_typed : converting " + JSON.stringify(value) + " for " + id + " from " + typeof value + " to " + type);
467
+ switch (type) {
468
+ case 'number':
469
+ value = parseFloat(value);
470
+ if (isNaN (value)) value = 0;
471
+ break;
472
+ case 'string':
473
+ case 'text': value = JSON.stringify(value); break;
474
+ case 'boolean':
475
+ if (typeof value == 'number') {
476
+ value = (value != 0);
477
+ break;
478
+ }
479
+ const sval = JSON.stringify(value).toLowerCase().trim();
480
+ value = (sval == 'true' || sval == 'yes' || sval == 'on');
481
+ break;
482
+ }
483
+ }
484
+ this.adapter.setState(id, value, ack, callback);
485
+ }
486
+
362
487
  updateDev(dev_id, dev_name, model, callback) {
488
+ const __dev_name = this.verifyDeviceName(dev_id, dev_name);
363
489
  const id = '' + dev_id;
364
490
  const modelDesc = statesMapping.findModel(model);
365
491
  let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
@@ -368,7 +494,7 @@ class StatesController extends EventEmitter {
368
494
  this.adapter.setObjectNotExists(id, {
369
495
  type: 'device',
370
496
  // 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},
497
+ common: {name: __dev_name, type: model, icon: icon},
372
498
  native: {id: dev_id}
373
499
  }, () => {
374
500
  // update type and icon
@@ -377,15 +503,13 @@ class StatesController extends EventEmitter {
377
503
  }
378
504
 
379
505
  async syncDevStates(dev, model) {
380
- const devId = dev.ieeeAddr.substr(2),
381
- hasGroups = dev.type === 'Router';
506
+ const devId = dev.ieeeAddr.substr(2);
382
507
  // devId - iobroker device id
383
508
  const devStates = await this.getDevStates(dev.ieeeAddr, model);
384
509
  if (!devStates) {
385
510
  return;
386
511
  }
387
- const states = statesMapping.commonStates.concat(devStates.states)
388
- .concat((hasGroups) ? [statesMapping.groupsState] : []);
512
+ const states = statesMapping.commonStates.concat(devStates.states);
389
513
 
390
514
  for (const stateInd in states) {
391
515
  if (!states.hasOwnProperty(stateInd)) continue;
@@ -432,6 +556,7 @@ class StatesController extends EventEmitter {
432
556
  for (const addressPart of this.debugDevices) {
433
557
  if (typeof(devId) == 'string' && devId.indexOf(addressPart) > -1)
434
558
  {
559
+ if (payload.hasOwnProperty('msg_from_zigbee')) break;
435
560
  this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
436
561
  has_debug = true;
437
562
  break;
@@ -454,10 +579,10 @@ class StatesController extends EventEmitter {
454
579
  value = payload[statedesc.prop || statedesc.id];
455
580
  }
456
581
  // checking value
457
- if (value === undefined) continue;
582
+ if (value === undefined || value === null) continue;
458
583
  let stateID = statedesc.id;
459
584
 
460
- if (has_debug) {
585
+ if (has_debug && statedesc.id != 'msg_from_zigbee') {
461
586
  this.warn(`ELEVATED publishToState: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
462
587
  }
463
588
 
@@ -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', {});