zigbee-herdsman-converters 14.0.265 → 14.0.269

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.
@@ -3769,6 +3769,16 @@ const converters = {
3769
3769
  });
3770
3770
  },
3771
3771
  },
3772
+ tuya_switch_type: {
3773
+ cluster: 'manuSpecificTuya_3',
3774
+ type: ['attributeReport', 'readResponse'],
3775
+ convert: (model, msg, publish, options, meta) => {
3776
+ const lookup = {0: 'toggle', 1: 'state', 2: 'momentary'};
3777
+ if (msg.data.hasOwnProperty('switchType')) {
3778
+ return {switch_type: lookup[msg.data['switchType']]};
3779
+ }
3780
+ },
3781
+ },
3772
3782
  restorable_brightness: {
3773
3783
  cluster: 'genLevelCtrl',
3774
3784
  type: ['attributeReport', 'readResponse'],
@@ -2869,6 +2869,19 @@ const converters = {
2869
2869
  return {state: {state: value.toUpperCase()}};
2870
2870
  },
2871
2871
  },
2872
+ tuya_switch_type: {
2873
+ key: ['switch_type'],
2874
+ convertSet: async (entity, key, value, meta) => {
2875
+ value = value.toLowerCase();
2876
+ const lookup = {'toggle': 0, 'state': 1, 'momentary': 2};
2877
+ utils.validateValue(value, Object.keys(lookup));
2878
+ await entity.write('manuSpecificTuya_3', {'switchType': lookup[value]}, {disableDefaultResponse: true});
2879
+ return {state: {switch_type: value}};
2880
+ },
2881
+ convertGet: async (entity, key, meta) => {
2882
+ await entity.read('manuSpecificTuya_3', ['switchType']);
2883
+ },
2884
+ },
2872
2885
  frankever_threshold: {
2873
2886
  key: ['threshold'],
2874
2887
  convertSet: async (entity, key, value, meta) => {
@@ -4612,21 +4625,27 @@ const converters = {
4612
4625
  convertSet: async (entity, key, value, meta) => {
4613
4626
  const isGroup = entity.constructor.name === 'Group';
4614
4627
  const groupid = isGroup ? entity.groupID : 0;
4615
- const sceneid = value;
4628
+ let sceneid = value;
4629
+ let scenename = null;
4630
+ if (typeof value === 'object') {
4631
+ sceneid = value.ID;
4632
+ scenename = value.name;
4633
+ }
4634
+
4616
4635
  const response = await entity.command('genScenes', 'store', {groupid, sceneid}, utils.getOptions(meta.mapped));
4617
4636
 
4618
4637
  if (isGroup) {
4619
4638
  if (meta.membersState) {
4620
4639
  for (const member of entity.members) {
4621
- utils.saveSceneState(member, sceneid, groupid, meta.membersState[member.getDevice().ieeeAddr]);
4640
+ utils.saveSceneState(member, sceneid, groupid, meta.membersState[member.getDevice().ieeeAddr], scenename);
4622
4641
  }
4623
4642
  }
4624
4643
  } else if (response.status === 0) {
4625
- utils.saveSceneState(entity, sceneid, groupid, meta.state);
4644
+ utils.saveSceneState(entity, sceneid, groupid, meta.state, scenename);
4626
4645
  } else {
4627
4646
  throw new Error(`Scene add not succesfull ('${herdsman.Zcl.Status[response.status]}')`);
4628
4647
  }
4629
-
4648
+ meta.logger.info('Successfully stored scene');
4630
4649
  return {state: {}};
4631
4650
  },
4632
4651
  },
@@ -4652,15 +4671,11 @@ const converters = {
4652
4671
  };
4653
4672
 
4654
4673
  const isGroup = entity.constructor.name === 'Group';
4655
- const metaKey = `${sceneid}_${groupid}`;
4656
4674
  if (isGroup) {
4657
4675
  const membersState = {};
4658
4676
  for (const member of entity.members) {
4659
- if (member.meta.hasOwnProperty('scenes') && member.meta.scenes.hasOwnProperty(metaKey)) {
4660
- membersState[member.getDevice().ieeeAddr] = addColorMode(member.meta.scenes[metaKey].state);
4661
-
4662
- let recalledState = member.meta.scenes[metaKey].state;
4663
-
4677
+ let recalledState = utils.getSceneState(member, sceneid, groupid);
4678
+ if (recalledState) {
4664
4679
  // add color_mode if saved state does not contain it
4665
4680
  if (!recalledState.hasOwnProperty('color_mode')) {
4666
4681
  recalledState = addColorMode(recalledState);
@@ -4673,18 +4688,18 @@ const converters = {
4673
4688
  membersState[member.getDevice().ieeeAddr] = {};
4674
4689
  }
4675
4690
  }
4691
+ meta.logger.info('Successfully recalled group scene');
4676
4692
  return {membersState};
4677
4693
  } else {
4678
- if (entity.meta.hasOwnProperty('scenes') && entity.meta.scenes.hasOwnProperty(metaKey)) {
4679
- let recalledState = entity.meta.scenes[metaKey].state;
4680
-
4694
+ let recalledState = utils.getSceneState(entity, sceneid, groupid);
4695
+ if (recalledState) {
4681
4696
  // add color_mode if saved state does not contain it
4682
4697
  if (!recalledState.hasOwnProperty('color_mode')) {
4683
4698
  recalledState = addColorMode(recalledState);
4684
4699
  }
4685
4700
 
4686
4701
  Object.assign(recalledState, libColor.syncColorState(recalledState, meta.state, meta.options));
4687
-
4702
+ meta.logger.info('Successfully recalled scene');
4688
4703
  return {state: recalledState};
4689
4704
  } else {
4690
4705
  meta.logger.warn(`Unknown scene was recalled for ${entity.deviceIeeeAddress}, can't restore state.`);
@@ -4711,7 +4726,7 @@ const converters = {
4711
4726
  const isGroup = entity.constructor.name === 'Group';
4712
4727
  const groupid = isGroup ? entity.groupID : 0;
4713
4728
  const sceneid = value.ID;
4714
- const scenename = '';
4729
+ const scenename = value.name;
4715
4730
  const transtime = value.hasOwnProperty('transition') ? value.transition : 0;
4716
4731
 
4717
4732
  const state = {};
@@ -4813,24 +4828,24 @@ const converters = {
4813
4828
 
4814
4829
  if (isGroup || (removeresp.status === 0 || removeresp.status == 133 || removeresp.status == 139)) {
4815
4830
  const response = await entity.command(
4816
- 'genScenes', 'add', {groupid, sceneid, scenename, transtime, extensionfieldsets}, utils.getOptions(meta.mapped),
4831
+ 'genScenes', 'add', {groupid, sceneid, scenename: '', transtime, extensionfieldsets}, utils.getOptions(meta.mapped),
4817
4832
  );
4818
4833
 
4819
4834
  if (isGroup) {
4820
4835
  if (meta.membersState) {
4821
4836
  for (const member of entity.members) {
4822
- utils.saveSceneState(member, sceneid, groupid, state);
4837
+ utils.saveSceneState(member, sceneid, groupid, state, scenename);
4823
4838
  }
4824
4839
  }
4825
4840
  } else if (response.status === 0) {
4826
- utils.saveSceneState(entity, sceneid, groupid, state);
4841
+ utils.saveSceneState(entity, sceneid, groupid, state, scenename);
4827
4842
  } else {
4828
4843
  throw new Error(`Scene add not succesfull ('${herdsman.Zcl.Status[response.status]}')`);
4829
4844
  }
4830
4845
  } else {
4831
4846
  throw new Error(`Scene add unable to remove existing scene ('${herdsman.Zcl.Status[removeresp.status]}')`);
4832
4847
  }
4833
-
4848
+ meta.logger.info('Successfully added scene');
4834
4849
  return {state: {}};
4835
4850
  },
4836
4851
  },
@@ -4842,26 +4857,19 @@ const converters = {
4842
4857
  const response = await entity.command(
4843
4858
  'genScenes', 'remove', {groupid, sceneid}, utils.getOptions(meta.mapped),
4844
4859
  );
4845
-
4846
4860
  const isGroup = entity.constructor.name === 'Group';
4847
- const metaKey = `${sceneid}_${groupid}`;
4848
4861
  if (isGroup) {
4849
4862
  if (meta.membersState) {
4850
4863
  for (const member of entity.members) {
4851
- if (member.meta.scenes && member.meta.scenes.hasOwnProperty(metaKey)) {
4852
- delete member.meta.scenes[metaKey];
4853
- member.save();
4854
- }
4864
+ utils.deleteSceneState(member, sceneid, groupid);
4855
4865
  }
4856
4866
  }
4857
4867
  } else if (response.status === 0) {
4858
- if (entity.meta.scenes && entity.meta.scenes.hasOwnProperty(metaKey)) {
4859
- delete entity.meta.scenes[metaKey];
4860
- entity.save();
4861
- }
4868
+ utils.deleteSceneState(entity, sceneid, groupid);
4862
4869
  } else {
4863
4870
  throw new Error(`Scene remove not succesfull ('${herdsman.Zcl.Status[response.status]}')`);
4864
4871
  }
4872
+ meta.logger.info('Successfully removed scene');
4865
4873
  },
4866
4874
  },
4867
4875
  scene_remove_all: {
@@ -4871,25 +4879,19 @@ const converters = {
4871
4879
  const response = await entity.command(
4872
4880
  'genScenes', 'removeAll', {groupid}, utils.getOptions(meta.mapped),
4873
4881
  );
4874
-
4875
4882
  const isGroup = entity.constructor.name === 'Group';
4876
4883
  if (isGroup) {
4877
4884
  if (meta.membersState) {
4878
4885
  for (const member of entity.members) {
4879
- if (member.meta.scenes) {
4880
- member.meta.scenes = {};
4881
- member.save();
4882
- }
4886
+ utils.deleteSceneState(member);
4883
4887
  }
4884
4888
  }
4885
4889
  } else if (response.status === 0) {
4886
- if (entity.meta.scenes) {
4887
- entity.meta.scenes = {};
4888
- entity.save();
4889
- }
4890
+ utils.deleteSceneState(entity);
4890
4891
  } else {
4891
4892
  throw new Error(`Scene remove all not succesfull ('${herdsman.Zcl.Status[response.status]}')`);
4892
4893
  }
4894
+ meta.logger.info('Successfully removed all scenes');
4893
4895
  },
4894
4896
  },
4895
4897
  TS0003_curtain_switch: {
@@ -501,6 +501,15 @@ module.exports = [
501
501
  extend: hueExtend.light_onoff_brightness(),
502
502
  ota: ota.zigbeeOTA,
503
503
  },
504
+ {
505
+ zigbeeModel: ['LWA017'],
506
+ model: '‎929002469202',
507
+ vendor: 'Philips',
508
+ description: 'Hue white A60 bulb E27 1050lm with Bluetooth',
509
+ meta: {turnsOffAtBrightness1: true},
510
+ extend: extend.light_onoff_brightness(),
511
+ ota: ota.zigbeeOTA,
512
+ },
504
513
  {
505
514
  zigbeeModel: ['LWA002'],
506
515
  model: '9290018215',
@@ -17,6 +17,7 @@ module.exports = [
17
17
  {modelID: 'TS0601', manufacturerName: '_TZE200_yw7cahqs'},
18
18
  {modelID: 'TS0601', manufacturerName: '_TZE200_azqp6ssj'},
19
19
  {modelID: 'TS0601', manufacturerName: '_TZE200_zuhszj9s'},
20
+ {modelID: 'TS0601', manufacturerName: '_TZE200_9gvruqf5'},
20
21
  ],
21
22
  model: 'SEA801-Zigbee/SEA802-Zigbee',
22
23
  vendor: 'Saswell',
package/devices/tuya.js CHANGED
@@ -585,6 +585,40 @@ module.exports = [
585
585
  await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']);
586
586
  },
587
587
  },
588
+ {
589
+ fingerprint: [{modelID: 'TS0001', manufacturerName: '_TZ3000_tqlv4ug4'}],
590
+ model: 'TS0001_switch_module',
591
+ vendor: 'TuYa',
592
+ description: '1 gang switch module',
593
+ whiteLabel: [{vendor: 'OXT', model: 'SWTZ21'}],
594
+ toZigbee: extend.switch().toZigbee.concat([tz.moes_power_on_behavior, tz.tuya_switch_type]),
595
+ fromZigbee: extend.switch().fromZigbee.concat([fz.moes_power_on_behavior, fz.tuya_switch_type]),
596
+ exposes: extend.switch().exposes.concat([exposes.presets.power_on_behavior(),
597
+ exposes.enum('switch_type', ea.ALL, ['toggle', 'state', 'momentary'])
598
+ .withDescription('Switch type settings')]),
599
+ configure: async (device, coordinatorEndpoint, logger) => {
600
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']);
601
+ },
602
+ },
603
+ {
604
+ fingerprint: [{modelID: 'TS0002', manufacturerName: '_TZ3000_01gpyda5'}],
605
+ model: 'TS0002_switch_module',
606
+ vendor: 'TuYa',
607
+ description: '2 gang switch module',
608
+ whiteLabel: [{vendor: 'OXT', model: 'SWTZ22'}],
609
+ toZigbee: extend.switch().toZigbee.concat([tz.moes_power_on_behavior, tz.tuya_switch_type]),
610
+ fromZigbee: extend.switch().fromZigbee.concat([fz.moes_power_on_behavior, fz.tuya_switch_type]),
611
+ exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), exposes.presets.power_on_behavior(),
612
+ exposes.enum('switch_type', ea.ALL, ['toggle', 'state', 'momentary']).withDescription('Switch type settings')],
613
+ endpoint: (device) => {
614
+ return {'l1': 1, 'l2': 2};
615
+ },
616
+ meta: {multiEndpoint: true},
617
+ configure: async (device, coordinatorEndpoint, logger) => {
618
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']);
619
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']);
620
+ },
621
+ },
588
622
  {
589
623
  zigbeeModel: [
590
624
  'owvfni3\u0000', 'owvfni3', 'u1rkty3', 'aabybja', // Curtain motors
package/lib/exposes.js CHANGED
@@ -469,7 +469,7 @@ module.exports = {
469
469
  child_lock: () => new Lock().withState('child_lock', 'LOCK', 'UNLOCK', 'Enables/disables physical input on the device', access.STATE_SET),
470
470
  co2: () => new Numeric('co2', access.STATE).withUnit('ppm').withDescription('The measured CO2 (carbon dioxide) value'),
471
471
  comfort_temperature: () => new Numeric('comfort_temperature', access.STATE_SET).withUnit('°C').withDescription('Comfort temperature'),
472
- consumer_connected: () => new Binary('consumer_connected', access.STATE, true, false).withDescription('Indicates whether device is physically attached. Device does not have to pull power or even be connected electrically (switch can be ON even if switch is OFF).'),
472
+ consumer_connected: () => new Binary('consumer_connected', access.STATE, true, false).withDescription('Indicates whether a plug is physically attached. Device does not have to pull power or even be connected electrically (state of this binary switch can be ON even if main power switch is OFF)'),
473
473
  contact: () => new Binary('contact', access.STATE, false, true).withDescription('Indicates if the contact is closed (= true) or open (= false)'),
474
474
  cover_position: () => new Cover().withPosition(),
475
475
  cover_position_tilt: () => new Cover().withPosition().withTilt(),
package/lib/utils.js CHANGED
@@ -271,14 +271,37 @@ function toCamelCase(value) {
271
271
  }
272
272
  }
273
273
 
274
- function saveSceneState(entity, sceneID, groupID, state) {
274
+ function saveSceneState(entity, sceneID, groupID, state, name) {
275
275
  const attributes = ['state', 'brightness', 'color', 'color_temp', 'color_mode'];
276
276
  if (!entity.meta.hasOwnProperty('scenes')) entity.meta.scenes = {};
277
277
  const metaKey = `${sceneID}_${groupID}`;
278
- entity.meta.scenes[metaKey] = {state: filterObject(state, attributes)};
278
+ entity.meta.scenes[metaKey] = {name, state: filterObject(state, attributes)};
279
279
  entity.save();
280
280
  }
281
281
 
282
+ function deleteSceneState(entity, sceneID=null, groupID=null) {
283
+ if (entity.meta.scenes) {
284
+ if (sceneID == null && groupID == null) {
285
+ entity.meta.scenes = {};
286
+ } else {
287
+ const metaKey = `${sceneID}_${groupID}`;
288
+ if (entity.meta.scenes.hasOwnProperty(metaKey)) {
289
+ delete entity.meta.scenes[metaKey];
290
+ }
291
+ }
292
+ entity.save();
293
+ }
294
+ }
295
+
296
+ function getSceneState(entity, sceneID, groupID) {
297
+ const metaKey = `${sceneID}_${groupID}`;
298
+ if (entity.meta.hasOwnProperty('scenes') && entity.meta.scenes.hasOwnProperty(metaKey)) {
299
+ return entity.meta.scenes[metaKey].state;
300
+ }
301
+
302
+ return null;
303
+ }
304
+
282
305
  function getEntityOrFirstGroupMember(entity) {
283
306
  if (entity.constructor.name === 'Group') {
284
307
  return entity.members.length > 0 ? entity.members[0] : null;
@@ -381,4 +404,6 @@ module.exports = {
381
404
  toSnakeCase,
382
405
  toCamelCase,
383
406
  normalizeCelsiusVersionOfFahrenheit,
407
+ deleteSceneState,
408
+ getSceneState,
384
409
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.265",
3
+ "version": "14.0.269",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.265",
3
+ "version": "14.0.269",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [