zigbee-herdsman-converters 15.0.36 → 15.0.38

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.
@@ -5387,19 +5387,19 @@ const converters = {
5387
5387
  cluster: 'genBasic',
5388
5388
  type: ['attributeReport', 'readResponse'],
5389
5389
  options: xiaomi.numericAttributes2Options,
5390
- convert: (model, msg, publish, options, meta) => {
5391
- return xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
5390
+ convert: async (model, msg, publish, options, meta) => {
5391
+ return await xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
5392
5392
  },
5393
5393
  },
5394
5394
  xiaomi_basic_raw: {
5395
5395
  cluster: 'genBasic',
5396
5396
  type: ['raw'],
5397
5397
  options: xiaomi.numericAttributes2Options,
5398
- convert: (model, msg, publish, options, meta) => {
5398
+ convert: async (model, msg, publish, options, meta) => {
5399
5399
  let payload = {};
5400
5400
  if (Buffer.isBuffer(msg.data)) {
5401
5401
  const dataObject = xiaomi.buffer2DataObject(meta, model, msg.data);
5402
- payload = xiaomi.numericAttributes2Payload(msg, meta, model, options, dataObject);
5402
+ payload = await xiaomi.numericAttributes2Payload(msg, meta, model, options, dataObject);
5403
5403
  }
5404
5404
  return payload;
5405
5405
  },
@@ -5408,8 +5408,8 @@ const converters = {
5408
5408
  cluster: 'aqaraOpple',
5409
5409
  type: ['attributeReport', 'readResponse'],
5410
5410
  options: xiaomi.numericAttributes2Options,
5411
- convert: (model, msg, publish, options, meta) => {
5412
- return xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
5411
+ convert: async (model, msg, publish, options, meta) => {
5412
+ return await xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
5413
5413
  },
5414
5414
  },
5415
5415
  xiaomi_on_off_action: {
@@ -6862,7 +6862,9 @@ const converters = {
6862
6862
  cluster: 'msOccupancySensing',
6863
6863
  type: ['raw'],
6864
6864
  convert: (model, msg, publish, options, meta) => {
6865
- return {occupancy: msg.data[7] === 0};
6865
+ if (msg.data[7] === 1) {
6866
+ return {action: 'motion'};
6867
+ }
6866
6868
  },
6867
6869
  },
6868
6870
  DNCKAT_S00X_buttons: {
@@ -16,7 +16,7 @@ module.exports = [
16
16
  extend: extend.light_onoff_brightness_colortemp_color({colorTempRange: [158, 495], disableEffect: true}),
17
17
  },
18
18
  {
19
- zigbeeModel: ['AJ_ZBPROA60'],
19
+ zigbeeModel: ['AJ_ZBPROA60', 'AJ_ZBPROA6'],
20
20
  model: 'AJ_ZIGPROA60',
21
21
  vendor: 'Ajax Online',
22
22
  description: 'Smart Zigbee pro 12W A60 bulb',
package/devices/kami.js CHANGED
@@ -7,9 +7,9 @@ module.exports = [
7
7
  zigbeeModel: ['Z3ContactSensor'],
8
8
  model: 'N20',
9
9
  vendor: 'KAMI',
10
- description: 'Entry sensor',
10
+ description: 'Contact sensor or motion sensor',
11
11
  fromZigbee: [fz.KAMI_contact, fz.KAMI_occupancy],
12
12
  toZigbee: [],
13
- exposes: [e.contact(), e.occupancy()],
13
+ exposes: [e.contact(), e.action(['motion'])],
14
14
  },
15
15
  ];
@@ -2383,7 +2383,7 @@ module.exports = [
2383
2383
  model: '929002422702',
2384
2384
  vendor: 'Philips',
2385
2385
  description: 'Hue Play gradient lightstrip 55',
2386
- extend: philips.extend.light_onoff_brightness_colortemp_color_gradient({colorTempRange: [153, 500]}),
2386
+ extend: philips.extend.light_onoff_brightness_colortemp_color_gradient({disableHueEffects: false, colorTempRange: [153, 500]}),
2387
2387
  },
2388
2388
  {
2389
2389
  zigbeeModel: ['LCX002'],
@@ -682,6 +682,26 @@ module.exports = [
682
682
  await reporting.instantaneousDemand(endpoint);
683
683
  },
684
684
  },
685
+ {
686
+ zigbeeModel: ['2GANG/SWITCH/2'],
687
+ model: 'MEG5126-0300',
688
+ vendor: 'Schneider Electric',
689
+ description: 'Merten MEG5165 PlusLink shutter insert with Merten Wiser System M push button (2fold)',
690
+ extend: extend.switch(),
691
+ exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2')],
692
+ meta: {multiEndpoint: true},
693
+ configure: async (device, coordinatorEndpoint, logger) => {
694
+ const endpoint1 = device.getEndpoint(1);
695
+ await reporting.bind(endpoint1, coordinatorEndpoint, ['genOnOff']);
696
+ await reporting.onOff(endpoint1);
697
+ const endpoint2 = device.getEndpoint(2);
698
+ await reporting.bind(endpoint2, coordinatorEndpoint, ['genOnOff']);
699
+ await reporting.onOff(endpoint2);
700
+ },
701
+ endpoint: (device) => {
702
+ return {l1: 1, l2: 2};
703
+ },
704
+ },
685
705
  {
686
706
  zigbeeModel: ['EH-ZB-VACT'],
687
707
  model: 'EER53000',
package/devices/sinope.js CHANGED
@@ -12,6 +12,18 @@ const {precisionRound} = require('../lib/utils');
12
12
  const manuSinope = {manufacturerCode: 0x119C};
13
13
 
14
14
  const fzLocal = {
15
+ ias_water_leak_alarm: {
16
+ // RM3500ZB specific
17
+ cluster: 'ssIasZone',
18
+ type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'],
19
+ convert: (model, msg, publish, options, meta) => {
20
+ const zoneStatus = msg.data.zoneStatus;
21
+ return {
22
+ water_leak: (zoneStatus & 1) > 0,
23
+ tamper: (zoneStatus & 1<<2) > 0,
24
+ };
25
+ },
26
+ },
15
27
  thermostat: {
16
28
  cluster: 'hvacThermostat',
17
29
  type: ['attributeReport', 'readResponse'],
@@ -1179,21 +1191,26 @@ module.exports = [
1179
1191
  model: 'RM3500ZB',
1180
1192
  vendor: 'Sinopé',
1181
1193
  description: 'Calypso smart water heater controller',
1182
- fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.ias_water_leak_alarm_1, fz.temperature],
1194
+ fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fzLocal.ias_water_leak_alarm,
1195
+ fzLocal.sinope, fz.temperature],
1183
1196
  toZigbee: [tz.on_off],
1184
1197
  exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.water_leak(), e.temperature()],
1185
1198
  configure: async (device, coordinatorEndpoint) => {
1186
1199
  const endpoint = device.getEndpoint(1);
1187
- const binds = ['genOnOff', 'haElectricalMeasurement', 'seMetering', 'Temperature'];
1200
+ const binds = ['genOnOff', 'haElectricalMeasurement', 'seMetering', 'msTemperatureMeasurement', 'ssIasZone',
1201
+ 'manuSpecificSinope'];
1188
1202
  await reporting.bind(endpoint, coordinatorEndpoint, binds);
1189
1203
  await reporting.onOff(endpoint);
1204
+ await reporting.temperature(endpoint, {min: 10, max: 301, change: 10}); // divider 100: 0.1C
1190
1205
  await reporting.readEletricalMeasurementMultiplierDivisors(endpoint);
1191
- await reporting.activePower(endpoint);
1192
- await reporting.rmsCurrent(endpoint);
1193
- await reporting.rmsVoltage(endpoint);
1206
+ await reporting.activePower(endpoint, {min: 10, max: 305, change: 2}); // divider 1 : 2W
1207
+ await reporting.rmsCurrent(endpoint, {min: 10, max: 306, change: 10}); // divider 1000: 0.01Arms
1208
+ await reporting.rmsVoltage(endpoint, {min: 10, max: 307, change: 1}); // divider 1: 1Vrms
1194
1209
  await reporting.readMeteringMultiplierDivisor(endpoint);
1195
- await reporting.currentSummDelivered(endpoint);
1196
- await reporting.temperature(endpoint, {min: 60, max: 3600, change: 1});
1210
+ await reporting.currentSummDelivered(endpoint, {min: 10, max: 303, change: [10, 10]}); // divider 1000: 0,01kWh
1211
+
1212
+ await endpoint.configureReporting('ssIasZone', [{attribute: 'zoneStatus', minimumReportInterval: 1,
1213
+ maximumReportInterval: constants.repInterval.HOUR, reportableChange: 1}]);
1197
1214
  },
1198
1215
  },
1199
1216
  ];
package/devices/tuya.js CHANGED
@@ -2118,7 +2118,7 @@ module.exports = [
2118
2118
  {modelID: 'TS0601', manufacturerName: '_TZE200_3ylew7b4'},
2119
2119
  {modelID: 'TS0601', manufacturerName: '_TZE200_llm0epxg'},
2120
2120
  ],
2121
- model: 'TS0601_cover',
2121
+ model: 'TS0601_cover_1',
2122
2122
  vendor: 'TuYa',
2123
2123
  description: 'Curtain motor/roller blind motor/window pusher/tubular motor',
2124
2124
  whiteLabel: [
@@ -2150,6 +2150,21 @@ module.exports = [
2150
2150
  .withValueMax(255)
2151
2151
  .withDescription('Motor speed'))],
2152
2152
  },
2153
+ {
2154
+ fingerprint: [
2155
+ // Curtain motors:
2156
+ {modelID: 'TS0601', manufacturerName: '_TZE200_eegnwoyw'},
2157
+ ],
2158
+ model: 'TS0601_cover_2',
2159
+ vendor: 'TuYa',
2160
+ description: 'Curtain motor fixed speed',
2161
+ whiteLabel: [
2162
+ {vendor: 'Zemismart', model: 'BCM100DB'},
2163
+ ],
2164
+ fromZigbee: [fz.tuya_cover, fz.ignore_basic_report],
2165
+ toZigbee: [tz.tuya_cover_control],
2166
+ exposes: [e.cover_position().setAccess('position', ea.STATE_SET)],
2167
+ },
2153
2168
  {
2154
2169
  zigbeeModel: ['kud7u2l'],
2155
2170
  fingerprint: [
package/devices/xiaomi.js CHANGED
@@ -297,6 +297,49 @@ const fzLocal = {
297
297
  return payload;
298
298
  },
299
299
  },
300
+ CTPR01_action_multistate: {
301
+ cluster: 'genMultistateInput',
302
+ type: ['attributeReport', 'readResponse'],
303
+ options: [],
304
+ convert: (model, msg, publish, options, meta) => {
305
+ const value = msg.data['presentValue'];
306
+ let payload;
307
+
308
+ if (value === 0) payload = {action: 'shake'};
309
+ else if (value === 1) payload = {action: 'throw'};
310
+ else if (value === 2) payload = {action: '1_min_inactivity'};
311
+ else if (value === 4) payload = {action: 'hold'};
312
+ else if (value >= 1024) payload = {action: 'flip_to_side', side: value - 1023};
313
+ else if (value >= 512) payload = {action: 'tap', side: value - 511};
314
+ else if (value >= 256) payload = {action: 'slide', side: value - 255};
315
+ else if (value >= 128) {
316
+ payload = {
317
+ action: 'flip180', side: value - 127,
318
+ action_from_side: 7 - value + 127,
319
+ };
320
+ } else if (value >= 64) {
321
+ payload = {
322
+ action: 'flip90', side: value % 8 + 1,
323
+ action_from_side: Math.floor((value - 64) / 8) + 1,
324
+ };
325
+ } else {
326
+ meta.logger.debug(`${model.zigbeeModel}: unknown action with value ${value}`);
327
+ }
328
+ return payload;
329
+ },
330
+ },
331
+ CTPR01_action_analog: {
332
+ cluster: 'genAnalogInput',
333
+ type: ['attributeReport', 'readResponse'],
334
+ options: [],
335
+ convert: (model, msg, publish, options, meta) => {
336
+ const value = msg.data['presentValue'];
337
+ return {
338
+ action: value < 0 ? 'rotate_left' : 'rotate_right',
339
+ action_angle: Math.floor(value * 100) / 100,
340
+ };
341
+ },
342
+ },
300
343
  };
301
344
 
302
345
  const tzLocal = {
@@ -608,6 +651,25 @@ const tzLocal = {
608
651
  await entity.write('aqaraOpple', payload, {manufacturerCode});
609
652
  },
610
653
  },
654
+ CTPR01_operation_mode: {
655
+ key: ['operation_mode'],
656
+ convertSet: async (entity, key, value, meta) => {
657
+ const lookup = {action_mode: 0, scene_mode: 1};
658
+ /**
659
+ * schedule the callback to run when the configuration window comes
660
+ */
661
+ const callback = async () => {
662
+ await entity.write(
663
+ 'aqaraOpple',
664
+ {0x0148: {value: lookup[value], type: 0x20}},
665
+ {manufacturerCode: 0x115f, disableDefaultResponse: true},
666
+ );
667
+ meta.logger.info('operation_mode switch success!');
668
+ };
669
+ globalStore.putValue(meta.device, 'opModeSwitchTask', {callback, newMode: value});
670
+ meta.logger.info('Now give your cube a forceful throw motion (Careful not to drop it)!');
671
+ },
672
+ },
611
673
  };
612
674
 
613
675
  module.exports = [
@@ -2991,4 +3053,43 @@ module.exports = [
2991
3053
  exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'hold', 'release']),
2992
3054
  e.device_temperature(), e.power_outage_count()],
2993
3055
  },
3056
+ {
3057
+ zigbeeModel: ['lumi.remote.cagl02'],
3058
+ model: 'CTP-R01',
3059
+ vendor: 'Xiaomi',
3060
+ description: 'Aqara magic cube T1 Pro',
3061
+ meta: {battery: {voltageToPercentage: '3V_2850_3000'}},
3062
+ ota: ota.zigbeeOTA,
3063
+ fromZigbee: [fz.aqara_opple, fzLocal.CTPR01_action_multistate, fzLocal.CTPR01_action_analog, fz.ignore_onoff_report],
3064
+ toZigbee: [tzLocal.CTPR01_operation_mode],
3065
+ exposes: [
3066
+ e.battery(),
3067
+ e.battery_voltage(),
3068
+ e.device_temperature(),
3069
+ e.power_outage_count(false),
3070
+ exposes
3071
+ .enum('operation_mode', ea.SET, ['action_mode', 'scene_mode'])
3072
+ .withDescription('[Soft Switch]: There is a configuration window, opens once an hour on itself, ' +
3073
+ 'only during which the cube will respond to mode switch. ' +
3074
+ 'Mode switch will be scheduled to take effect when the window becomes available. ' +
3075
+ 'You can also give it a throw action (no backward motion) to force a respond! ' +
3076
+ 'Otherwise, you may open lid and click LINK once to make the cube respond immediately. ' +
3077
+ '[Hard Switch]: Open lid and click LINK button 5 times.'),
3078
+ e.cube_side('side'),
3079
+ e.action([
3080
+ 'shake', 'throw', 'tap', 'slide', 'flip180', 'flip90', 'hold', 'side_up',
3081
+ 'rotate_left', 'rotate_right', '1_min_inactivity', 'flip_to_side',
3082
+ ]).withDescription('Triggered action'),
3083
+ e.cube_side('action_from_side'),
3084
+ e.angle('action_angle'),
3085
+ ],
3086
+ configure: async (device, coordinatorEndpoint, logger) => {
3087
+ device.softwareBuildID = `0.0.0_00${device.applicationVersion}`;
3088
+ device.save();
3089
+
3090
+ const endpoint = device.getEndpoint(1);
3091
+ await endpoint.write('aqaraOpple', {mode: 1}, {manufacturerCode: 0x115f, disableDefaultResponse: true, disableResponse: true});
3092
+ await endpoint.read('aqaraOpple', [0x148], {manufacturerCode: 0x115f, disableDefaultResponse: true, disableResponse: true});
3093
+ },
3094
+ },
2994
3095
  ];
package/devices/yale.js CHANGED
@@ -8,14 +8,16 @@ const lockExtend = (meta, lockStateOptions=null, binds=['closuresDoorLock', 'gen
8
8
  return {
9
9
  fromZigbee: [fz.lock, fz.battery, fz.lock_operation_event, fz.lock_programming_event, fz.lock_pin_code_response,
10
10
  fz.lock_user_status_response],
11
- toZigbee: [tz.lock, tz.pincode_lock, tz.lock_userstatus],
11
+ toZigbee: [tz.lock, tz.pincode_lock, tz.lock_userstatus, tz.lock_auto_relock_time, tz.lock_sound_volume],
12
12
  meta: {pinCodeCount: 250, ...meta},
13
- exposes: [e.lock(), e.battery(), e.pincode(), e.lock_action(), e.lock_action_source_name(), e.lock_action_user()],
13
+ exposes: [e.lock(), e.battery(), e.pincode(), e.lock_action(), e.lock_action_source_name(), e.lock_action_user(),
14
+ e.auto_relock_time().withValueMin(0).withValueMax(3600), e.sound_volume(), e.battery_low()],
14
15
  configure: async (device, coordinatorEndpoint, logger) => {
15
16
  const endpoint = device.getEndpoint(1);
16
17
  await reporting.bind(endpoint, coordinatorEndpoint, binds);
17
18
  await reporting.lockState(endpoint, lockStateOptions);
18
19
  await reporting.batteryPercentageRemaining(endpoint);
20
+ await reporting.batteryAlarmState(endpoint);
19
21
  },
20
22
  };
21
23
  };
package/lib/reporting.js CHANGED
@@ -69,6 +69,7 @@ module.exports = {
69
69
  batteryAlarmState: async (endpoint, overrides) => {
70
70
  const p = payload('batteryAlarmState', repInterval.HOUR, repInterval.MAX, 0, overrides);
71
71
  await endpoint.configureReporting('genPowerCfg', p);
72
+ await endpoint.read('genPowerCfg', ['batteryAlarmState']);
72
73
  },
73
74
  onOff: async (endpoint, overrides) => {
74
75
  const p = payload('onOff', 0, repInterval.HOUR, 0, overrides);
package/lib/xiaomi.js CHANGED
@@ -10,6 +10,7 @@ const {
10
10
  } = require('./utils');
11
11
 
12
12
  const exposes = require('../lib/exposes');
13
+ const globalStore = require('../lib/store');
13
14
 
14
15
  const buffer2DataObject = (meta, model, buffer) => {
15
16
  const dataObject = {};
@@ -142,10 +143,10 @@ const buffer2DataObject = (meta, model, buffer) => {
142
143
  return dataObject;
143
144
  };
144
145
 
145
- const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
146
+ const numericAttributes2Payload = async (msg, meta, model, options, dataObject) => {
146
147
  let payload = {};
147
148
 
148
- Object.entries(dataObject).forEach(([key, value]) => {
149
+ for (const [key, value] of Object.entries(dataObject)) {
149
150
  switch (key) {
150
151
  case '0':
151
152
  payload.detection_period = value;
@@ -188,7 +189,7 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
188
189
  }
189
190
  break;
190
191
  case '10':
191
- if (['ZNLDP13LM'].includes(model.model)) {
192
+ if (['ZNLDP13LM', 'CTP-R01'].includes(model.model)) {
192
193
  // We don't know what the value means for these devices.
193
194
  } else {
194
195
  payload.switch_type = {1: 'toggle', 2: 'momentary'}[value];
@@ -422,7 +423,23 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
422
423
  case '247':
423
424
  {
424
425
  const dataObject247 = buffer2DataObject(meta, model, value);
425
- const payload247 = numericAttributes2Payload(msg, meta, model, options, dataObject247);
426
+ if (['CTP-R01'].includes(model.model)) {
427
+ // execute pending soft switch of operation_mode, if exists
428
+ const opModeSwitchTask = globalStore.getValue(meta.device, 'opModeSwitchTask');
429
+ if (opModeSwitchTask) {
430
+ const {callback, newMode} = opModeSwitchTask;
431
+ try {
432
+ await callback();
433
+ payload.operation_mode = newMode;
434
+ globalStore.putValue(meta.device, 'opModeSwitchTask', null);
435
+ } catch (error) {
436
+ // do nothing when callback fails
437
+ }
438
+ } else {
439
+ payload.operation_mode = {0: 'action_mode', 1: 'scene_mode'}[dataObject247[155]];
440
+ }
441
+ }
442
+ const payload247 = await numericAttributes2Payload(msg, meta, model, options, dataObject247);
426
443
  payload = {...payload, ...payload247};
427
444
  }
428
445
  break;
@@ -506,6 +523,19 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
506
523
  payload.approach_distance = {0: 'far', 1: 'medium', 2: 'near'}[value];
507
524
  }
508
525
  break;
526
+ case '328':
527
+ if (['CTP-R01'].includes(model.model)) {
528
+ // detected hard switch of operation_mode (attribute 0x148[328])
529
+ payload.operation_mode = {0: 'action_mode', 1: 'scene_mode'}[msg.data[328]];
530
+ }
531
+ break;
532
+ case '329':
533
+ if (['CTP-R01'].includes(model.model)) {
534
+ // side_up attribute report (attribute 0x149[329])
535
+ payload.action = 'side_up';
536
+ payload.side = msg.data[329] + 1;
537
+ }
538
+ break;
509
539
  case '331':
510
540
  if (['JT-BZ-01AQ/A', 'JY-GZ-01AQ'].includes(model.model)) {
511
541
  payload.linkage_alarm = value === 1;
@@ -621,7 +651,7 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
621
651
  break;
622
652
  case '65281':
623
653
  {
624
- const payload65281 = numericAttributes2Payload(msg, meta, model, options, value);
654
+ const payload65281 = await numericAttributes2Payload(msg, meta, model, options, value);
625
655
  payload = {...payload, ...payload65281};
626
656
  }
627
657
  break;
@@ -650,7 +680,7 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
650
680
  default:
651
681
  if (meta.logger) meta.logger.debug(`${model.zigbeeModel}: unknown key ${key} with value ${value}`);
652
682
  }
653
- });
683
+ }
654
684
 
655
685
  if (meta.logger) meta.logger.debug(`${model.zigbeeModel}: Processed data into payload ${JSON.stringify(payload)}`);
656
686
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "15.0.36",
3
+ "version": "15.0.38",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -34,11 +34,11 @@
34
34
  },
35
35
  "homepage": "https://github.com/Koenkk/zigbee-herdsman-converters",
36
36
  "dependencies": {
37
- "axios": "^1.2.6",
37
+ "axios": "^1.3.0",
38
38
  "buffer-crc32": "^0.2.13",
39
39
  "https-proxy-agent": "^5.0.1",
40
40
  "tar-stream": "^3.0.0",
41
- "zigbee-herdsman": "^0.14.88"
41
+ "zigbee-herdsman": "^0.14.89"
42
42
  },
43
43
  "devDependencies": {
44
44
  "eslint": "*",