zigbee-herdsman-converters 14.0.654 → 14.0.656

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.
@@ -782,7 +782,7 @@ const converters = {
782
782
  // Device sends multiple messages with the same transactionSequenceNumber,
783
783
  // prevent that multiple messages get send.
784
784
  // https://github.com/Koenkk/zigbee2mqtt/issues/3687
785
- if (msg.data.hasOwnProperty('onOff') && !hasAlreadyProcessedMessage(msg)) {
785
+ if (msg.data.hasOwnProperty('onOff') && !hasAlreadyProcessedMessage(msg, model)) {
786
786
  const property = postfixWithEndpointName('state', msg, model, meta);
787
787
  return {[property]: msg.data['onOff'] === 1 ? 'ON' : 'OFF'};
788
788
  }
@@ -1084,7 +1084,7 @@ const converters = {
1084
1084
  cluster: 'genScenes',
1085
1085
  type: 'commandStore',
1086
1086
  convert: (model, msg, publish, options, meta) => {
1087
- if (hasAlreadyProcessedMessage(msg)) return;
1087
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1088
1088
  const payload = {action: postfixWithEndpointName(`store_${msg.data.sceneid}`, msg, model, meta)};
1089
1089
  addActionGroup(payload, msg, model);
1090
1090
  return payload;
@@ -1094,7 +1094,7 @@ const converters = {
1094
1094
  cluster: 'genScenes',
1095
1095
  type: 'commandRecall',
1096
1096
  convert: (model, msg, publish, options, meta) => {
1097
- if (hasAlreadyProcessedMessage(msg)) return;
1097
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1098
1098
  const payload = {action: postfixWithEndpointName(`recall_${msg.data.sceneid}`, msg, model, meta)};
1099
1099
  addActionGroup(payload, msg, model);
1100
1100
  return payload;
@@ -1104,7 +1104,7 @@ const converters = {
1104
1104
  cluster: 'ssIasAce',
1105
1105
  type: 'commandPanic',
1106
1106
  convert: (model, msg, publish, options, meta) => {
1107
- if (hasAlreadyProcessedMessage(msg)) return;
1107
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1108
1108
  const payload = {action: postfixWithEndpointName(`panic`, msg, model, meta)};
1109
1109
  addActionGroup(payload, msg, model);
1110
1110
  return payload;
@@ -1114,7 +1114,7 @@ const converters = {
1114
1114
  cluster: 'ssIasAce',
1115
1115
  type: 'commandArm',
1116
1116
  convert: (model, msg, publish, options, meta) => {
1117
- if (hasAlreadyProcessedMessage(msg)) return;
1117
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1118
1118
  const payload = {
1119
1119
  action: postfixWithEndpointName(constants.armMode[msg.data['armmode']], msg, model, meta),
1120
1120
  action_code: msg.data.code,
@@ -1138,7 +1138,7 @@ const converters = {
1138
1138
  cluster: 'closuresWindowCovering',
1139
1139
  type: 'commandStop',
1140
1140
  convert: (model, msg, publish, options, meta) => {
1141
- if (hasAlreadyProcessedMessage(msg)) return;
1141
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1142
1142
  const payload = {action: postfixWithEndpointName('stop', msg, model, meta)};
1143
1143
  addActionGroup(payload, msg, model);
1144
1144
  return payload;
@@ -1148,7 +1148,7 @@ const converters = {
1148
1148
  cluster: 'closuresWindowCovering',
1149
1149
  type: 'commandUpOpen',
1150
1150
  convert: (model, msg, publish, options, meta) => {
1151
- if (hasAlreadyProcessedMessage(msg)) return;
1151
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1152
1152
  const payload = {action: postfixWithEndpointName('open', msg, model, meta)};
1153
1153
  addActionGroup(payload, msg, model);
1154
1154
  return payload;
@@ -1158,7 +1158,7 @@ const converters = {
1158
1158
  cluster: 'closuresWindowCovering',
1159
1159
  type: 'commandDownClose',
1160
1160
  convert: (model, msg, publish, options, meta) => {
1161
- if (hasAlreadyProcessedMessage(msg)) return;
1161
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1162
1162
  const payload = {action: postfixWithEndpointName('close', msg, model, meta)};
1163
1163
  addActionGroup(payload, msg, model);
1164
1164
  return payload;
@@ -1168,7 +1168,7 @@ const converters = {
1168
1168
  cluster: 'genOnOff',
1169
1169
  type: 'commandOn',
1170
1170
  convert: (model, msg, publish, options, meta) => {
1171
- if (hasAlreadyProcessedMessage(msg)) return;
1171
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1172
1172
  const payload = {action: postfixWithEndpointName('on', msg, model, meta)};
1173
1173
  addActionGroup(payload, msg, model);
1174
1174
  return payload;
@@ -1178,7 +1178,7 @@ const converters = {
1178
1178
  cluster: 'genOnOff',
1179
1179
  type: 'commandOff',
1180
1180
  convert: (model, msg, publish, options, meta) => {
1181
- if (hasAlreadyProcessedMessage(msg)) return;
1181
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1182
1182
  const payload = {action: postfixWithEndpointName('off', msg, model, meta)};
1183
1183
  addActionGroup(payload, msg, model);
1184
1184
  return payload;
@@ -1188,7 +1188,7 @@ const converters = {
1188
1188
  cluster: 'genOnOff',
1189
1189
  type: 'commandOffWithEffect',
1190
1190
  convert: (model, msg, publish, options, meta) => {
1191
- if (hasAlreadyProcessedMessage(msg)) return;
1191
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1192
1192
  const payload = {action: postfixWithEndpointName(`off`, msg, model, meta)};
1193
1193
  addActionGroup(payload, msg, model);
1194
1194
  return payload;
@@ -1198,7 +1198,7 @@ const converters = {
1198
1198
  cluster: 'genOnOff',
1199
1199
  type: 'commandToggle',
1200
1200
  convert: (model, msg, publish, options, meta) => {
1201
- if (hasAlreadyProcessedMessage(msg)) return;
1201
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1202
1202
  const payload = {action: postfixWithEndpointName('toggle', msg, model, meta)};
1203
1203
  addActionGroup(payload, msg, model);
1204
1204
  return payload;
@@ -1209,7 +1209,7 @@ const converters = {
1209
1209
  type: ['commandMoveToLevel', 'commandMoveToLevelWithOnOff'],
1210
1210
  options: [exposes.options.simulated_brightness()],
1211
1211
  convert: (model, msg, publish, options, meta) => {
1212
- if (hasAlreadyProcessedMessage(msg)) return;
1212
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1213
1213
  const payload = {
1214
1214
  action: postfixWithEndpointName(`brightness_move_to_level`, msg, model, meta),
1215
1215
  action_level: msg.data.level,
@@ -1231,7 +1231,7 @@ const converters = {
1231
1231
  type: ['commandMove', 'commandMoveWithOnOff'],
1232
1232
  options: [exposes.options.simulated_brightness()],
1233
1233
  convert: (model, msg, publish, options, meta) => {
1234
- if (hasAlreadyProcessedMessage(msg)) return;
1234
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1235
1235
  const direction = msg.data.movemode === 1 ? 'down' : 'up';
1236
1236
  const action = postfixWithEndpointName(`brightness_move_${direction}`, msg, model, meta);
1237
1237
  const payload = {action, action_rate: msg.data.rate};
@@ -1267,7 +1267,7 @@ const converters = {
1267
1267
  type: ['commandStep', 'commandStepWithOnOff'],
1268
1268
  options: [exposes.options.simulated_brightness()],
1269
1269
  convert: (model, msg, publish, options, meta) => {
1270
- if (hasAlreadyProcessedMessage(msg)) return;
1270
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1271
1271
  const direction = msg.data.stepmode === 1 ? 'down' : 'up';
1272
1272
  const payload = {
1273
1273
  action: postfixWithEndpointName(`brightness_step_${direction}`, msg, model, meta),
@@ -1294,7 +1294,7 @@ const converters = {
1294
1294
  type: ['commandStop', 'commandStopWithOnOff'],
1295
1295
  options: [exposes.options.simulated_brightness()],
1296
1296
  convert: (model, msg, publish, options, meta) => {
1297
- if (hasAlreadyProcessedMessage(msg)) return;
1297
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1298
1298
  if (options.simulated_brightness) {
1299
1299
  clearInterval(globalStore.getValue(msg.endpoint, 'simulated_brightness_timer'));
1300
1300
  globalStore.putValue(msg.endpoint, 'simulated_brightness_timer', undefined);
@@ -1309,7 +1309,7 @@ const converters = {
1309
1309
  cluster: 'lightingColorCtrl',
1310
1310
  type: ['commandMoveColorTemp'],
1311
1311
  convert: (model, msg, publish, options, meta) => {
1312
- if (hasAlreadyProcessedMessage(msg)) return;
1312
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1313
1313
  const direction = msg.data.movemode === 1 ? 'down' : 'up';
1314
1314
  const action = postfixWithEndpointName(`color_temperature_move_${direction}`, msg, model, meta);
1315
1315
  const payload = {action, action_rate: msg.data.rate, action_minimum: msg.data.minimum, action_maximum: msg.data.maximum};
@@ -1321,7 +1321,7 @@ const converters = {
1321
1321
  cluster: 'lightingColorCtrl',
1322
1322
  type: 'commandStepColorTemp',
1323
1323
  convert: (model, msg, publish, options, meta) => {
1324
- if (hasAlreadyProcessedMessage(msg)) return;
1324
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1325
1325
  const direction = msg.data.stepmode === 1 ? 'up' : 'down';
1326
1326
  const payload = {
1327
1327
  action: postfixWithEndpointName(`color_temperature_step_${direction}`, msg, model, meta),
@@ -1340,7 +1340,7 @@ const converters = {
1340
1340
  cluster: 'lightingColorCtrl',
1341
1341
  type: 'commandEnhancedMoveToHueAndSaturation',
1342
1342
  convert: (model, msg, publish, options, meta) => {
1343
- if (hasAlreadyProcessedMessage(msg)) return;
1343
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1344
1344
  const payload = {
1345
1345
  action: postfixWithEndpointName(`enhanced_move_to_hue_and_saturation`, msg, model, meta),
1346
1346
  action_enhanced_hue: msg.data.enhancehue,
@@ -1357,7 +1357,7 @@ const converters = {
1357
1357
  cluster: 'lightingColorCtrl',
1358
1358
  type: ['commandStepHue'],
1359
1359
  convert: (model, msg, publish, options, meta) => {
1360
- if (hasAlreadyProcessedMessage(msg)) return;
1360
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1361
1361
  const direction = msg.data.stepmode === 1 ? 'up' : 'down';
1362
1362
  const payload = {
1363
1363
  action: postfixWithEndpointName(`color_hue_step_${direction}`, msg, model, meta),
@@ -1372,7 +1372,7 @@ const converters = {
1372
1372
  cluster: 'lightingColorCtrl',
1373
1373
  type: ['commandStepSaturation'],
1374
1374
  convert: (model, msg, publish, options, meta) => {
1375
- if (hasAlreadyProcessedMessage(msg)) return;
1375
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1376
1376
  const direction = msg.data.stepmode === 1 ? 'up' : 'down';
1377
1377
  const payload = {
1378
1378
  action: postfixWithEndpointName(`color_saturation_step_${direction}`, msg, model, meta),
@@ -1387,7 +1387,7 @@ const converters = {
1387
1387
  cluster: 'lightingColorCtrl',
1388
1388
  type: 'commandColorLoopSet',
1389
1389
  convert: (model, msg, publish, options, meta) => {
1390
- if (hasAlreadyProcessedMessage(msg)) return;
1390
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1391
1391
  const updateFlags = msg.data.updateflags;
1392
1392
  const actionLookup = {
1393
1393
  0x00: 'deactivate',
@@ -1417,7 +1417,7 @@ const converters = {
1417
1417
  cluster: 'lightingColorCtrl',
1418
1418
  type: 'commandMoveToColorTemp',
1419
1419
  convert: (model, msg, publish, options, meta) => {
1420
- if (hasAlreadyProcessedMessage(msg)) return;
1420
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1421
1421
  const payload = {
1422
1422
  action: postfixWithEndpointName(`color_temperature_move`, msg, model, meta),
1423
1423
  action_color_temperature: msg.data.colortemp,
@@ -1431,7 +1431,7 @@ const converters = {
1431
1431
  cluster: 'lightingColorCtrl',
1432
1432
  type: 'commandMoveToColor',
1433
1433
  convert: (model, msg, publish, options, meta) => {
1434
- if (hasAlreadyProcessedMessage(msg)) return;
1434
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1435
1435
  const payload = {
1436
1436
  action: postfixWithEndpointName(`color_move`, msg, model, meta),
1437
1437
  action_color: {
@@ -1448,7 +1448,7 @@ const converters = {
1448
1448
  cluster: 'lightingColorCtrl',
1449
1449
  type: 'commandMoveHue',
1450
1450
  convert: (model, msg, publish, options, meta) => {
1451
- if (hasAlreadyProcessedMessage(msg)) return;
1451
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1452
1452
  const movestop = msg.data.movemode == 1 ? 'move' : 'stop';
1453
1453
  const action = postfixWithEndpointName(`hue_${movestop}`, msg, model, meta);
1454
1454
  const payload = {action, action_rate: msg.data.rate};
@@ -1460,7 +1460,7 @@ const converters = {
1460
1460
  cluster: 'lightingColorCtrl',
1461
1461
  type: 'commandMoveToSaturation',
1462
1462
  convert: (model, msg, publish, options, meta) => {
1463
- if (hasAlreadyProcessedMessage(msg)) return;
1463
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1464
1464
  const payload = {
1465
1465
  action: postfixWithEndpointName('move_to_saturation', msg, model, meta),
1466
1466
  action_saturation: msg.data.saturation,
@@ -1474,7 +1474,7 @@ const converters = {
1474
1474
  cluster: 'lightingColorCtrl',
1475
1475
  type: 'commandMoveToHue',
1476
1476
  convert: (model, msg, publish, options, meta) => {
1477
- if (hasAlreadyProcessedMessage(msg)) return;
1477
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1478
1478
  const payload = {
1479
1479
  action: postfixWithEndpointName(`move_to_hue`, msg, model, meta),
1480
1480
  action_hue: msg.data.hue,
@@ -1489,7 +1489,7 @@ const converters = {
1489
1489
  cluster: 'ssIasAce',
1490
1490
  type: 'commandEmergency',
1491
1491
  convert: (model, msg, publish, options, meta) => {
1492
- if (hasAlreadyProcessedMessage(msg)) return;
1492
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1493
1493
  const payload = {action: postfixWithEndpointName(`emergency`, msg, model, meta)};
1494
1494
  addActionGroup(payload, msg, model);
1495
1495
  return payload;
@@ -1499,7 +1499,7 @@ const converters = {
1499
1499
  cluster: 'genOnOff',
1500
1500
  type: 'commandOn',
1501
1501
  convert: (model, msg, publish, options, meta) => {
1502
- if (hasAlreadyProcessedMessage(msg)) return;
1502
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1503
1503
  const property = postfixWithEndpointName('state', msg, model, meta);
1504
1504
  return {[property]: 'ON'};
1505
1505
  },
@@ -1508,7 +1508,7 @@ const converters = {
1508
1508
  cluster: 'genOnOff',
1509
1509
  type: 'commandOff',
1510
1510
  convert: (model, msg, publish, options, meta) => {
1511
- if (hasAlreadyProcessedMessage(msg)) return;
1511
+ if (hasAlreadyProcessedMessage(msg, model)) return;
1512
1512
  const property = postfixWithEndpointName('state', msg, model, meta);
1513
1513
  return {[property]: 'OFF'};
1514
1514
  },
@@ -2335,7 +2335,7 @@ const converters = {
2335
2335
  cluster: 'ssIasZone',
2336
2336
  type: 'commandStatusChangeNotification',
2337
2337
  convert: (model, msg, publish, options, meta) => {
2338
- if (hasAlreadyProcessedMessage(msg)) return;
2338
+ if (hasAlreadyProcessedMessage(msg, model)) return;
2339
2339
  const lookup = {1: 'pressed'};
2340
2340
  const zoneStatus = msg.data.zonestatus;
2341
2341
  return {
@@ -2818,7 +2818,7 @@ const converters = {
2818
2818
  cluster: 'genOnOff',
2819
2819
  type: 'raw',
2820
2820
  convert: (model, msg, publish, options, meta) => {
2821
- if (hasAlreadyProcessedMessage(msg, msg.data[1])) return;
2821
+ if (hasAlreadyProcessedMessage(msg, model, msg.data[1])) return;
2822
2822
  const clickMapping = {0: 'single', 1: 'double', 2: 'hold'};
2823
2823
  let buttonMapping = null;
2824
2824
  if (model.model === 'TS0042') {
@@ -4098,9 +4098,10 @@ const converters = {
4098
4098
  const dp = dpValue.dp; // First we get the data point ID
4099
4099
  const value = tuya.getDataValue(dpValue);
4100
4100
  const presetLookup = {0: 'auto', 1: 'manual', 2: 'off', 3: 'on'};
4101
+ const systemModeLookup = {0: 'auto', 1: 'auto', 2: 'off', 3: 'heat'};
4101
4102
  switch (dp) {
4102
4103
  case tuya.dataPoints.haozeeSystemMode:
4103
- return {preset: presetLookup[value]};
4104
+ return {preset: presetLookup[value], system_mode: systemModeLookup[value]};
4104
4105
  case tuya.dataPoints.haozeeHeatingSetpoint:
4105
4106
  return {current_heating_setpoint: (value / 10).toFixed(1)};
4106
4107
  case tuya.dataPoints.haozeeLocalTemp:
@@ -4784,7 +4785,7 @@ const converters = {
4784
4785
  cluster: 'genScenes',
4785
4786
  type: 'commandTradfriArrowSingle',
4786
4787
  convert: (model, msg, publish, options, meta) => {
4787
- if (hasAlreadyProcessedMessage(msg)) return;
4788
+ if (hasAlreadyProcessedMessage(msg, model)) return;
4788
4789
  if (msg.data.value === 2) {
4789
4790
  // This is send on toggle hold, ignore it as a toggle_hold is already handled above.
4790
4791
  return;
@@ -4798,7 +4799,7 @@ const converters = {
4798
4799
  cluster: 'genScenes',
4799
4800
  type: 'commandTradfriArrowHold',
4800
4801
  convert: (model, msg, publish, options, meta) => {
4801
- if (hasAlreadyProcessedMessage(msg)) return;
4802
+ if (hasAlreadyProcessedMessage(msg, model)) return;
4802
4803
  const direction = msg.data.value === 3329 ? 'left' : 'right';
4803
4804
  globalStore.putValue(msg.endpoint, 'direction', direction);
4804
4805
  return {action: `arrow_${direction}_hold`};
@@ -4809,7 +4810,7 @@ const converters = {
4809
4810
  type: 'commandTradfriArrowRelease',
4810
4811
  options: [exposes.options.legacy()],
4811
4812
  convert: (model, msg, publish, options, meta) => {
4812
- if (hasAlreadyProcessedMessage(msg)) return;
4813
+ if (hasAlreadyProcessedMessage(msg, model)) return;
4813
4814
  const direction = globalStore.getValue(msg.endpoint, 'direction');
4814
4815
  if (direction) {
4815
4816
  globalStore.clearValue(msg.endpoint, 'direction');
@@ -4880,7 +4881,7 @@ const converters = {
4880
4881
  type: ['commandNotification', 'commandCommisioningNotification'],
4881
4882
  convert: (model, msg, publish, options, meta) => {
4882
4883
  const commandID = msg.data.commandID;
4883
- if (hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
4884
+ if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
4884
4885
  if (commandID === 224) return; // Skip commisioning command.
4885
4886
 
4886
4887
  // Button 1: A0 (top left)
@@ -4902,7 +4903,7 @@ const converters = {
4902
4903
  type: ['commandNotification', 'commandCommisioningNotification'],
4903
4904
  convert: (model, msg, publish, options, meta) => {
4904
4905
  const commandID = msg.data.commandID;
4905
- if (hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
4906
+ if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
4906
4907
  if (commandID === 224) return;
4907
4908
 
4908
4909
  // Button 1: A0 (top left)
@@ -4930,7 +4931,7 @@ const converters = {
4930
4931
  type: ['commandNotification', 'commandCommisioningNotification'],
4931
4932
  convert: (model, msg, publish, options, meta) => {
4932
4933
  const commandID = msg.data.commandID;
4933
- if (hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
4934
+ if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
4934
4935
  if (commandID === 224) return;
4935
4936
 
4936
4937
  // Button 1: A0 (top left)
@@ -5342,7 +5343,7 @@ const converters = {
5342
5343
  type: ['commandNotification', 'commandCommisioningNotification'],
5343
5344
  convert: (model, msg, publish, options, meta) => {
5344
5345
  const commandID = msg.data.commandID;
5345
- if (hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
5346
+ if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
5346
5347
  if (commandID === 224) return;
5347
5348
  const lookup = {0x14: 'press_1', 0x15: 'press_2', 0x16: 'press_3', 0x17: 'press_4'};
5348
5349
  if (!lookup.hasOwnProperty(commandID)) {
@@ -5357,7 +5358,7 @@ const converters = {
5357
5358
  type: ['commandNotification', 'commandCommisioningNotification'],
5358
5359
  convert: (model, msg, publish, options, meta) => {
5359
5360
  const commandID = msg.data.commandID;
5360
- if (hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
5361
+ if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
5361
5362
  if (commandID === 224) return;
5362
5363
  const lookup = {0x22: 'press_once', 0x20: 'press_twice'};
5363
5364
  if (!lookup.hasOwnProperty(commandID)) {
@@ -5476,7 +5477,7 @@ const converters = {
5476
5477
  cluster: 'genMultistateInput',
5477
5478
  type: ['attributeReport'],
5478
5479
  convert: (model, msg, publish, options, meta) => {
5479
- if (hasAlreadyProcessedMessage(msg)) return;
5480
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5480
5481
  let actionLookup = {0: 'hold', 1: 'single', 2: 'double', 3: 'triple', 255: 'release'};
5481
5482
  if (model.model === 'WXKG12LM') {
5482
5483
  actionLookup = {...actionLookup, 16: 'hold', 17: 'release', 18: 'shake'};
@@ -5613,7 +5614,7 @@ const converters = {
5613
5614
  `increase it with this option (value is in ms).`),
5614
5615
  ],
5615
5616
  convert: (model, msg, publish, options, meta) => {
5616
- if (hasAlreadyProcessedMessage(msg)) return;
5617
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5617
5618
  const state = msg.data['onOff'];
5618
5619
 
5619
5620
  // 0 = click down, 1 = click up, else = multiple clicks
@@ -5894,7 +5895,7 @@ const converters = {
5894
5895
  cluster: 'genMultistateInput',
5895
5896
  type: ['attributeReport', 'readResponse'],
5896
5897
  convert: (model, msg, publish, options, meta) => {
5897
- if (hasAlreadyProcessedMessage(msg)) return;
5898
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5898
5899
  const actionLookup = {0: 'hold', 255: 'release', 1: 'single', 2: 'double', 3: 'triple', 5: 'quintuple', 6: 'many'};
5899
5900
  const button = msg.endpoint.ID;
5900
5901
  const value = msg.data.presentValue;
@@ -5917,7 +5918,7 @@ const converters = {
5917
5918
  cluster: 'genOnOff',
5918
5919
  type: 'commandOn',
5919
5920
  convert: (model, msg, publish, options, meta) => {
5920
- if (hasAlreadyProcessedMessage(msg)) return;
5921
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5921
5922
  return {action: 'button_2_single'};
5922
5923
  },
5923
5924
  },
@@ -5925,7 +5926,7 @@ const converters = {
5925
5926
  cluster: 'genOnOff',
5926
5927
  type: 'commandOff',
5927
5928
  convert: (model, msg, publish, options, meta) => {
5928
- if (hasAlreadyProcessedMessage(msg)) return;
5929
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5929
5930
  return {action: 'button_1_single'};
5930
5931
  },
5931
5932
  },
@@ -5933,7 +5934,7 @@ const converters = {
5933
5934
  cluster: 'genLevelCtrl',
5934
5935
  type: 'commandStep',
5935
5936
  convert: (model, msg, publish, options, meta) => {
5936
- if (hasAlreadyProcessedMessage(msg)) return;
5937
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5937
5938
  const button = msg.data.stepmode === 0 ? '4' : '3';
5938
5939
  return {action: `button_${button}_single`};
5939
5940
  },
@@ -5943,7 +5944,7 @@ const converters = {
5943
5944
  type: 'commandStop',
5944
5945
  options: [exposes.options.legacy()],
5945
5946
  convert: (model, msg, publish, options, meta) => {
5946
- if (hasAlreadyProcessedMessage(msg)) return;
5947
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5947
5948
  if (globalStore.hasValue(msg.endpoint, 'button')) {
5948
5949
  const value = globalStore.getValue(msg.endpoint, 'button');
5949
5950
  const duration = Date.now() - value.start;
@@ -5957,7 +5958,7 @@ const converters = {
5957
5958
  cluster: 'genLevelCtrl',
5958
5959
  type: 'commandMove',
5959
5960
  convert: (model, msg, publish, options, meta) => {
5960
- if (hasAlreadyProcessedMessage(msg)) return;
5961
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5961
5962
  const button = msg.data.movemode === 0 ? '4' : '3';
5962
5963
  globalStore.putValue(msg.endpoint, 'button', {button, start: Date.now()});
5963
5964
  return {action: `button_${button}_hold`};
@@ -5967,7 +5968,7 @@ const converters = {
5967
5968
  cluster: 'lightingColorCtrl',
5968
5969
  type: 'commandStepColorTemp',
5969
5970
  convert: (model, msg, publish, options, meta) => {
5970
- if (hasAlreadyProcessedMessage(msg)) return;
5971
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5971
5972
  let action;
5972
5973
  if (model.model === 'WXCJKG12LM') {
5973
5974
  // for WXCJKG12LM model it's double click event on buttons 3 and 4
@@ -5984,7 +5985,7 @@ const converters = {
5984
5985
  type: 'commandMoveColorTemp',
5985
5986
  options: [exposes.options.legacy()],
5986
5987
  convert: (model, msg, publish, options, meta) => {
5987
- if (hasAlreadyProcessedMessage(msg)) return;
5988
+ if (hasAlreadyProcessedMessage(msg, model)) return;
5988
5989
  const stop = msg.data.movemode === 0;
5989
5990
  let result = null;
5990
5991
  if (stop) {
@@ -7132,7 +7133,7 @@ const converters = {
7132
7133
  type: ['commandNotification', 'commandCommisioningNotification'],
7133
7134
  convert: (model, msg, publish, options, meta) => {
7134
7135
  const commandID = msg.data.commandID;
7135
- if (hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
7136
+ if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
7136
7137
  if (commandID === 224) return;
7137
7138
  const lookup = {0x22: 'press_1', 0x10: 'press_2', 0x11: 'press_3', 0x12: 'press_4'};
7138
7139
  if (!lookup.hasOwnProperty(commandID)) {
@@ -7410,7 +7411,7 @@ const converters = {
7410
7411
  cluster: 'wiserDeviceInfo',
7411
7412
  type: 'attributeReport',
7412
7413
  convert: (model, msg, publish, options, meta) => {
7413
- if (hasAlreadyProcessedMessage(msg)) return;
7414
+ if (hasAlreadyProcessedMessage(msg, model)) return;
7414
7415
 
7415
7416
  const data = msg.data['deviceInfo'].split(',');
7416
7417
  if (data[0] === 'UI' && data[1]) {
@@ -7623,7 +7624,7 @@ const converters = {
7623
7624
  cluster: 'ssIasZone',
7624
7625
  type: 'commandStatusChangeNotification',
7625
7626
  convert: (model, msg, publish, options, meta) => {
7626
- if (utils.hasAlreadyProcessedMessage(msg)) return;
7627
+ if (hasAlreadyProcessedMessage(msg, model)) return;
7627
7628
  const lookup = {32768: 'pressed'};
7628
7629
  const zoneStatus = msg.data.zonestatus;
7629
7630
  return {
@@ -7699,44 +7700,6 @@ const converters = {
7699
7700
  return result;
7700
7701
  },
7701
7702
  },
7702
- tuya_radar_sensor: {
7703
- cluster: 'manuSpecificTuya',
7704
- type: ['commandDataResponse', 'commandDataReport'],
7705
- convert: (model, msg, publish, options, meta) => {
7706
- const dpValue = tuya.firstDpValue(msg, meta, 'tuya_radar_sensor');
7707
- const dp = dpValue.dp;
7708
- const value = tuya.getDataValue(dpValue);
7709
- let result = null;
7710
- switch (dp) {
7711
- case tuya.dataPoints.trsPresenceState:
7712
- result = {presence: {0: false, 1: true}[value]};
7713
- break;
7714
- case tuya.dataPoints.trsMotionState:
7715
- result = {occupancy: {1: false, 2: true}[value]};
7716
- break;
7717
- case tuya.dataPoints.trsMotionSpeed:
7718
- result = {motion_speed: value};
7719
- break;
7720
- case tuya.dataPoints.trsMotionDirection:
7721
- result = {motion_direction: tuya.tuyaRadar.motionDirection[value]};
7722
- break;
7723
- case tuya.dataPoints.trsScene:
7724
- result = {radar_scene: tuya.tuyaRadar.radarScene[value]};
7725
- break;
7726
- case tuya.dataPoints.trsSensitivity:
7727
- result = {radar_sensitivity: value};
7728
- break;
7729
- case tuya.dataPoints.trsIlluminanceLux:
7730
- result = {illuminance_lux: value};
7731
- break;
7732
- case tuya.dataPoints.trsDetectionData: // Ignore this, function of this DP is unknown at the moment!
7733
- break;
7734
- default:
7735
- meta.logger.warn(`fromZigbee.tuya_radar_sensor: NOT RECOGNIZED DP ${dp} with data ${JSON.stringify(dpValue)}`);
7736
- }
7737
- return result;
7738
- },
7739
- },
7740
7703
  tuya_radar_sensor_fall: {
7741
7704
  cluster: 'manuSpecificTuya',
7742
7705
  type: ['commandDataResponse', 'commandDataReport'],
@@ -8077,7 +8040,7 @@ const converters = {
8077
8040
  type: ['commandNotification', 'commandCommisioningNotification'],
8078
8041
  convert: (model, msg, publish, options, meta) => {
8079
8042
  const commandID = msg.data.commandID;
8080
- if (hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
8043
+ if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
8081
8044
  if (commandID === 224) return;
8082
8045
  const lookup = {0x21: 'press_on', 0x20: 'press_off', 0x34: 'release', 0x35: 'hold_on', 0x36: 'hold_off'};
8083
8046
  if (!lookup.hasOwnProperty(commandID)) {
@@ -8092,7 +8055,7 @@ const converters = {
8092
8055
  type: ['commandNotification', 'commandCommisioningNotification'],
8093
8056
  convert: (model, msg, publish, options, meta) => {
8094
8057
  const commandID = msg.data.commandID;
8095
- if (hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
8058
+ if (hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
8096
8059
  if (commandID === 224) return;
8097
8060
  const lookup = {
8098
8061
  0x21: 'press_on',
@@ -8161,7 +8124,7 @@ const converters = {
8161
8124
  convert: (model, msg, publish, options, meta) => {
8162
8125
  // commandStopMove without params
8163
8126
  if (msg.data[2] !== 71) return;
8164
- if (hasAlreadyProcessedMessage(msg)) return;
8127
+ if (hasAlreadyProcessedMessage(msg, model)) return;
8165
8128
  const movestop = 'stop';
8166
8129
  const action = postfixWithEndpointName(`hue_${movestop}`, msg, model, meta);
8167
8130
  const payload = {action};
@@ -8173,7 +8136,7 @@ const converters = {
8173
8136
  cluster: 'genOnOff',
8174
8137
  type: 'raw',
8175
8138
  convert: (model, msg, publish, options, meta) => {
8176
- if (hasAlreadyProcessedMessage(msg, msg.data[1])) return;
8139
+ if (hasAlreadyProcessedMessage(msg, model, msg.data[1])) return;
8177
8140
  let action;
8178
8141
  if (msg.data[2] == 253) {
8179
8142
  action = {0: 'single', 1: 'double', 2: 'hold'}[msg.data[3]];
@@ -3514,13 +3514,24 @@ const converters = {
3514
3514
  return tuya.sendDataPointRaw(entity, tuya.dataPoints.moesSschedule, payload);
3515
3515
  },
3516
3516
  },
3517
- haozee_thermostat_system_mode: {
3517
+ haozee_thermostat_preset: {
3518
3518
  key: ['preset'],
3519
3519
  convertSet: async (entity, key, value, meta) => {
3520
3520
  const lookup = {'auto': 0, 'manual': 1, 'off': 2, 'on': 3};
3521
3521
  await tuya.sendDataPointEnum(entity, tuya.dataPoints.haozeeSystemMode, lookup[value]);
3522
3522
  },
3523
3523
  },
3524
+ haozee_thermostat_system_mode: {
3525
+ key: ['system_mode'],
3526
+ convertSet: async (entity, key, value, meta) => {
3527
+ // mapping 'heat' system mode to 100% heating (same as preset 'ON'),
3528
+ // mapping 'auto' system mode to heating up to the set point temperature (same as preset 'MANUAL')
3529
+ // mapping 'off' system mode to idle (same as preset 'OFF')
3530
+ // programmed schedule can be enabled by using preset mode 'AUTO' instead of 'MANUAL'
3531
+ const lookup = {'auto': 1, 'off': 2, 'heat': 3};
3532
+ await tuya.sendDataPointEnum(entity, tuya.dataPoints.haozeeSystemMode, lookup[value]);
3533
+ },
3534
+ },
3524
3535
  haozee_thermostat_current_heating_setpoint: {
3525
3536
  key: ['current_heating_setpoint'],
3526
3537
  convertSet: async (entity, key, value, meta) => {
@@ -6869,21 +6880,6 @@ const converters = {
6869
6880
  }
6870
6881
  },
6871
6882
  },
6872
- tuya_radar_sensor: {
6873
- key: ['radar_scene', 'radar_sensitivity'],
6874
- convertSet: async (entity, key, value, meta) => {
6875
- switch (key) {
6876
- case 'radar_scene':
6877
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.trsScene, utils.getKey(tuya.tuyaRadar.radarScene, value));
6878
- break;
6879
- case 'radar_sensitivity':
6880
- await tuya.sendDataPointValue(entity, tuya.dataPoints.trsSensitivity, value);
6881
- break;
6882
- default: // Unknown Key
6883
- meta.logger.warn(`toZigbee.tuya_radar_sensor: Unhandled Key ${key}`);
6884
- }
6885
- },
6886
- },
6887
6883
  tuya_radar_sensor_fall: {
6888
6884
  key: ['radar_scene', 'radar_sensitivity', 'tumble_alarm_time', 'tumble_switch', 'fall_sensitivity'],
6889
6885
  convertSet: async (entity, key, value, meta) => {
package/devices/ikea.js CHANGED
@@ -92,7 +92,7 @@ const fzLocal = {
92
92
  cluster: 'genOnOff',
93
93
  type: 'commandOn',
94
94
  convert: (model, msg, publish, options, meta) => {
95
- if (utils.hasAlreadyProcessedMessage(msg)) return;
95
+ if (utils.hasAlreadyProcessedMessage(msg, model)) return;
96
96
  const arrowReleaseAgo = Date.now() - globalStore.getValue(msg.endpoint, 'arrow_release', 0);
97
97
  if (arrowReleaseAgo > 700) {
98
98
  return {action: 'on'};
@@ -104,7 +104,7 @@ const fzLocal = {
104
104
  type: 'commandTradfriArrowRelease',
105
105
  options: [exposes.options.legacy()],
106
106
  convert: (model, msg, publish, options, meta) => {
107
- if (utils.hasAlreadyProcessedMessage(msg)) return;
107
+ if (utils.hasAlreadyProcessedMessage(msg, model)) return;
108
108
  globalStore.putValue(msg.endpoint, 'arrow_release', Date.now());
109
109
  const direction = globalStore.getValue(msg.endpoint, 'direction');
110
110
  if (direction) {
@@ -10,7 +10,7 @@ const fzLocal = {
10
10
  type: ['commandNotification', 'commandCommisioningNotification'],
11
11
  convert: (model, msg, publish, options, meta) => {
12
12
  const commandID = msg.data.commandID;
13
- if (utils.hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
13
+ if (utils.hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
14
14
  if (commandID === 224) return;
15
15
  const lookup = {0x13: 'press_1', 0x14: 'press_2', 0x15: 'press_3', 0x16: 'press_4',
16
16
  0x1B: 'hold_1', 0x1C: 'hold_2', 0x1D: 'hold_3', 0x1E: 'hold_4'};
@@ -6,7 +6,6 @@ const extend = require('../lib/extend');
6
6
  const e = exposes.presets;
7
7
  const ea = exposes.access;
8
8
  const ota = require('../lib/ota');
9
- const utils = require('../lib/utils');
10
9
 
11
10
  const readInitialBatteryState = async (type, data, device) => {
12
11
  if (['deviceAnnounce'].includes(type)) {
@@ -16,29 +15,6 @@ const readInitialBatteryState = async (type, data, device) => {
16
15
  }
17
16
  };
18
17
 
19
- const fzLocal = {
20
- command_off: {
21
- cluster: 'genOnOff',
22
- type: 'commandOff',
23
- convert: (model, msg, publish, options, meta) => {
24
- const payload = {action: utils.postfixWithEndpointName('off', msg, model, meta)};
25
- utils.addActionGroup(payload, msg, model);
26
- return payload;
27
- },
28
- },
29
- command_toggle: {
30
- cluster: 'genOnOff',
31
- type: 'commandToggle',
32
- convert: (model, msg, publish, options, meta) => {
33
- // Re-uses transactions sequence number so hasAlreadyProcessedMessage(msg) suppresess actions
34
- // https://github.com/Koenkk/zigbee2mqtt/issues/14256
35
- const payload = {action: utils.postfixWithEndpointName('toggle', msg, model, meta)};
36
- utils.addActionGroup(payload, msg, model);
37
- return payload;
38
- },
39
- },
40
- };
41
-
42
18
  module.exports = [
43
19
  {
44
20
  zigbeeModel: [' Contactor\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'+
@@ -133,12 +109,12 @@ module.exports = [
133
109
  vendor: 'Legrand',
134
110
  // led blink RED when battery is low
135
111
  description: 'Wireless remote switch',
136
- fromZigbee: [fz.identify, fz.command_on, fzLocal.command_off, fzLocal.command_toggle, fz.legacy.cmd_move, fz.legacy.cmd_stop,
112
+ fromZigbee: [fz.identify, fz.command_on, fz.command_off, fz.command_toggle, fz.legacy.cmd_move, fz.legacy.cmd_stop,
137
113
  fz.battery],
138
114
  exposes: [e.battery(), e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up',
139
115
  'brightness_move_down', 'brightness_stop'])],
140
116
  toZigbee: [],
141
- meta: {battery: {voltageToPercentage: '3V_2500'}},
117
+ meta: {battery: {voltageToPercentage: '3V_2500'}, publishDuplicateTransaction: true},
142
118
  configure: async (device, coordinatorEndpoint, logger) => {
143
119
  const endpoint = device.getEndpoint(1);
144
120
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'genOnOff', 'genLevelCtrl']);
@@ -149,11 +125,11 @@ module.exports = [
149
125
  model: '067774',
150
126
  vendor: 'Legrand',
151
127
  description: 'Wireless double remote switch',
152
- fromZigbee: [fz.identify, fz.command_on, fzLocal.command_off, fzLocal.command_toggle, fz.command_move, fz.command_stop, fz.battery],
128
+ fromZigbee: [fz.identify, fz.command_on, fz.command_off, fz.command_toggle, fz.command_move, fz.command_stop, fz.battery],
153
129
  exposes: [e.battery(),
154
130
  e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop'])],
155
131
  toZigbee: [],
156
- meta: {multiEndpoint: true, battery: {voltageToPercentage: '3V_2500'}},
132
+ meta: {multiEndpoint: true, battery: {voltageToPercentage: '3V_2500'}, publishDuplicateTransaction: true},
157
133
  configure: async (device, coordinatorEndpoint, logger) => {
158
134
  const endpoint = device.getEndpoint(1);
159
135
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'genOnOff', 'genLevelCtrl']);
package/devices/lidl.js CHANGED
@@ -145,14 +145,14 @@ const fzLocal = {
145
145
  metering_skip_duplicate: {
146
146
  ...fz.metering,
147
147
  convert: (model, msg, publish, options, meta) => {
148
- if (utils.hasAlreadyProcessedMessage(msg)) return;
148
+ if (utils.hasAlreadyProcessedMessage(msg, model)) return;
149
149
  return fz.metering.convert(model, msg, publish, options, meta);
150
150
  },
151
151
  },
152
152
  electrical_measurement_skip_duplicate: {
153
153
  ...fz.electrical_measurement,
154
154
  convert: (model, msg, publish, options, meta) => {
155
- if (utils.hasAlreadyProcessedMessage(msg)) return;
155
+ if (utils.hasAlreadyProcessedMessage(msg, model)) return;
156
156
  return fz.electrical_measurement.convert(model, msg, publish, options, meta);
157
157
  },
158
158
  },
@@ -208,7 +208,8 @@ module.exports = [
208
208
  {
209
209
  zigbeeModel: ['ZB-RGBCW'],
210
210
  fingerprint: [{modelID: 'ZB-CL01', manufacturerName: 'eWeLight'}, {modelID: 'ZB-CL01', manufacturerName: 'eWeLink'},
211
- {modelID: 'ZB-CL02', manufacturerName: 'eWeLight'}, {modelID: 'ZB-CL01', manufacturerName: 'eWeLi\u0001\u0000\u0010'}],
211
+ {modelID: 'ZB-CL02', manufacturerName: 'eWeLight'}, {modelID: 'ZB-CL01', manufacturerName: 'eWeLi\u0001\u0000\u0010'},
212
+ {modelID: 'Z102LG03-1', manufacturerName: 'eWeLink'}],
212
213
  model: 'ZB-RGBCW',
213
214
  vendor: 'Lonsonho',
214
215
  description: 'Zigbee 3.0 LED-bulb, RGBW LED',
@@ -116,10 +116,9 @@ module.exports = [
116
116
  model: '106-03',
117
117
  vendor: 'Plugwise',
118
118
  description: 'Tom thermostatic radiator valve',
119
- fromZigbee: [fz.thermostat, fz.temperature, fz.battery, fzLocal.plugwise_radiator_valve],
119
+ fromZigbee: [fz.temperature, fz.battery, fzLocal.plugwise_radiator_valve],
120
+ // sytem_mode and occupied_heating_setpoint is not supported: https://github.com/Koenkk/zigbee2mqtt.io/pull/1666
120
121
  toZigbee: [
121
- tz.thermostat_system_mode,
122
- tz.thermostat_occupied_heating_setpoint,
123
122
  tz.thermostat_pi_heating_demand,
124
123
  tzLocal.plugwise_valve_position,
125
124
  tzLocal.plugwise_push_force,
@@ -135,8 +134,7 @@ module.exports = [
135
134
  },
136
135
  exposes: [e.battery(),
137
136
  exposes.climate()
138
- .withSetpoint('occupied_heating_setpoint', 5, 30, 0.5, ea.ALL).withLocalTemperature(ea.STATE)
139
- .withSystemMode(['off', 'auto'], ea.ALL)
137
+ .withLocalTemperature(ea.STATE)
140
138
  .withPiHeatingDemand(ea.STATE_GET),
141
139
  exposes.numeric('valve_position', ea.ALL).withValueMin(0).withValueMax(100)
142
140
  .withDescription('Directly control the radiator valve. The values range from 0 (valve ' +
@@ -28,7 +28,7 @@ const fzLocal = {
28
28
  }
29
29
 
30
30
  const commandID = msg.data.commandID;
31
- if (utils.hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
31
+ if (utils.hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
32
32
 
33
33
  const rxAfterTx = (msg.data.options & (1<<11));
34
34
  const ret = {};
@@ -15,7 +15,7 @@ const fzLocal = {
15
15
  type: ['commandNotification', 'commandCommisioningNotification'],
16
16
  convert: (model, msg, publish, options, meta) => {
17
17
  const commandID = msg.data.commandID;
18
- if (utils.hasAlreadyProcessedMessage(msg, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
18
+ if (utils.hasAlreadyProcessedMessage(msg, model, msg.data.frameCounter, `${msg.device.ieeeAddr}_${commandID}`)) return;
19
19
  if (commandID === 224) return;
20
20
  const lookup = {
21
21
  0x21: 'press_on',
@@ -127,6 +127,28 @@ module.exports = [
127
127
  'brightness_move_up', 'brightness_move_down', 'brightness_move_stop', 'brightness_step_up', 'brightness_step_down',
128
128
  'recall_1', 'recall_2'])],
129
129
  },
130
+ {
131
+ zigbeeModel: ['ZGRC-KEY-012'],
132
+ model: 'SR-ZG9001K12-DIM-Z5',
133
+ vendor: 'Sunricher',
134
+ description: '5 zone remote and dimmer',
135
+ fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery],
136
+ toZigbee: [],
137
+ exposes: [e.battery(), e.action([
138
+ 'on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1',
139
+ 'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2',
140
+ 'on_3', 'off_3', 'brightness_move_up_3', 'brightness_move_down_3', 'brightness_stop_3',
141
+ 'on_4', 'off_4', 'brightness_move_up_4', 'brightness_move_down_4', 'brightness_stop_4',
142
+ 'on_5', 'off_5', 'brightness_move_up_5', 'brightness_move_down_5', 'brightness_stop_5'])],
143
+ meta: {multiEndpoint: true, battery: {dontDividePercentage: true}},
144
+ configure: async (device, coordinatorEndpoint, logger) => {
145
+ await reporting.bind(device.getEndpoint(1), coordinatorEndpoint, ['genOnOff']);
146
+ await reporting.bind(device.getEndpoint(2), coordinatorEndpoint, ['genOnOff']);
147
+ await reporting.bind(device.getEndpoint(3), coordinatorEndpoint, ['genOnOff']);
148
+ await reporting.bind(device.getEndpoint(4), coordinatorEndpoint, ['genOnOff']);
149
+ await reporting.bind(device.getEndpoint(5), coordinatorEndpoint, ['genOnOff']);
150
+ },
151
+ },
130
152
  {
131
153
  zigbeeModel: ['ZGRC-KEY-013'],
132
154
  model: 'SR-ZG9001K12-DIM-Z4',
package/devices/tuya.js CHANGED
@@ -375,14 +375,14 @@ const fzLocal = {
375
375
  metering_skip_duplicate: {
376
376
  ...fz.metering,
377
377
  convert: (model, msg, publish, options, meta) => {
378
- if (utils.hasAlreadyProcessedMessage(msg)) return;
378
+ if (utils.hasAlreadyProcessedMessage(msg, model)) return;
379
379
  return fz.metering.convert(model, msg, publish, options, meta);
380
380
  },
381
381
  },
382
382
  electrical_measurement_skip_duplicate: {
383
383
  ...fz.electrical_measurement,
384
384
  convert: (model, msg, publish, options, meta) => {
385
- if (utils.hasAlreadyProcessedMessage(msg)) return;
385
+ if (utils.hasAlreadyProcessedMessage(msg, model)) return;
386
386
  return fz.electrical_measurement.convert(model, msg, publish, options, meta);
387
387
  },
388
388
  },
@@ -923,7 +923,9 @@ module.exports = [
923
923
  {modelID: 'TS0505B', manufacturerName: '_TZ3000_xr5m6kfg'},
924
924
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_xr5m6kfg'},
925
925
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_bf175wi4'},
926
- {modelID: 'TS0505B', manufacturerName: '_TZB210_3zfp8mki'}],
926
+ {modelID: 'TS0505B', manufacturerName: '_TZB210_3zfp8mki'},
927
+ {modelID: 'TS0505B', manufacturerName: '_TZ3000_y1vbo44x'},
928
+ {modelID: 'TS0505B', manufacturerName: '_TZ3210_mny0zvkm'}],
927
929
  model: 'TS0505B',
928
930
  vendor: 'TuYa',
929
931
  description: 'Zigbee RGB+CCT light',
@@ -2257,7 +2259,7 @@ module.exports = [
2257
2259
  tz.haozee_thermostat_system_mode, tz.haozee_thermostat_current_heating_setpoint, tz.haozee_thermostat_boost_heating,
2258
2260
  tz.haozee_thermostat_boost_heating_countdown, tz.haozee_thermostat_window_detection,
2259
2261
  tz.haozee_thermostat_child_lock, tz.haozee_thermostat_temperature_calibration, tz.haozee_thermostat_max_temperature,
2260
- tz.haozee_thermostat_min_temperature,
2262
+ tz.haozee_thermostat_min_temperature, tz.haozee_thermostat_preset,
2261
2263
  ],
2262
2264
  exposes: [
2263
2265
  e.battery(), e.child_lock(), e.max_temperature(), e.min_temperature(),
@@ -2271,7 +2273,8 @@ module.exports = [
2271
2273
  'When the set temperature is lower than the "minimum temperature", the valve is closed (forced closed). ' +
2272
2274
  'AUTO MODE ⏱ - In this mode, the device executes a preset week programming temperature time and temperature. ' +
2273
2275
  'ON - In this mode, the thermostat stays open ' +
2274
- 'OFF - In this mode, the thermostat stays closed'),
2276
+ 'OFF - In this mode, the thermostat stays closed')
2277
+ .withSystemMode(['auto', 'heat', 'off'], ea.STATE_SET),
2275
2278
  exposes.composite('programming_mode').withDescription('Auto MODE ⏱ - In this mode, ' +
2276
2279
  'the device executes a preset week programming temperature time and temperature. ')
2277
2280
  .withFeature(exposes.text('monday_schedule', ea.STATE))
@@ -3148,27 +3151,10 @@ module.exports = [
3148
3151
  ],
3149
3152
  },
3150
3153
  {
3151
- fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_vrfecyku'}],
3154
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_lu01t0zl'},
3155
+ {modelID: 'TS0601', manufacturerName: '_TZE200_vrfecyku'}],
3152
3156
  model: 'MIR-HE200-TY',
3153
3157
  vendor: 'TuYa',
3154
- description: 'Human presence sensor',
3155
- fromZigbee: [fz.tuya_radar_sensor],
3156
- toZigbee: [tz.tuya_radar_sensor],
3157
- exposes: [
3158
- e.illuminance_lux(), e.presence(), e.occupancy(),
3159
- exposes.numeric('motion_speed', ea.STATE).withDescription('Speed of movement'),
3160
- exposes.enum('motion_direction', ea.STATE, Object.values(tuya.tuyaRadar.motionDirection))
3161
- .withDescription('direction of movement from the point of view of the radar'),
3162
- exposes.numeric('radar_sensitivity', ea.STATE_SET).withValueMin(0).withValueMax(10).withValueStep(1)
3163
- .withDescription('sensitivity of the radar'),
3164
- exposes.enum('radar_scene', ea.STATE_SET, Object.values(tuya.tuyaRadar.radarScene))
3165
- .withDescription('presets for sensitivity for presence and movement'),
3166
- ],
3167
- },
3168
- {
3169
- fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_lu01t0zl'}],
3170
- model: 'MIR-HE200-TY_fall',
3171
- vendor: 'TuYa',
3172
3158
  description: 'Human presence sensor with fall function',
3173
3159
  fromZigbee: [fz.tuya_radar_sensor_fall],
3174
3160
  toZigbee: [tz.tuya_radar_sensor_fall],
package/lib/legacy.js CHANGED
@@ -856,7 +856,7 @@ const fromZigbee = {
856
856
  type: 'commandToggle',
857
857
  options: [exposes.options.legacy()],
858
858
  convert: (model, msg, publish, options, meta) => {
859
- if (hasAlreadyProcessedMessage(msg)) return;
859
+ if (hasAlreadyProcessedMessage(msg, model)) return;
860
860
  if (isLegacyEnabled(options)) {
861
861
  return {action: 'play_pause'};
862
862
  } else {
@@ -869,7 +869,7 @@ const fromZigbee = {
869
869
  type: 'commandStep',
870
870
  options: [exposes.options.legacy()],
871
871
  convert: (model, msg, publish, options, meta) => {
872
- if (hasAlreadyProcessedMessage(msg)) return;
872
+ if (hasAlreadyProcessedMessage(msg, model)) return;
873
873
  if (isLegacyEnabled(options)) {
874
874
  const direction = msg.data.stepmode === 1 ? 'backward' : 'forward';
875
875
  return {
@@ -887,7 +887,7 @@ const fromZigbee = {
887
887
  type: 'commandMove',
888
888
  options: [exposes.options.legacy(), exposes.options.simulated_brightness(' Note: will only work when legacy: false is set.')],
889
889
  convert: (model, msg, publish, options, meta) => {
890
- if (hasAlreadyProcessedMessage(msg)) return;
890
+ if (hasAlreadyProcessedMessage(msg, model)) return;
891
891
  if (isLegacyEnabled(options)) {
892
892
  ictcg1(model, msg, publish, options, 'move');
893
893
  const direction = msg.data.movemode === 1 ? 'left' : 'right';
@@ -916,7 +916,7 @@ const fromZigbee = {
916
916
  type: 'commandStop',
917
917
  options: [exposes.options.legacy()],
918
918
  convert: (model, msg, publish, options, meta) => {
919
- if (hasAlreadyProcessedMessage(msg)) return;
919
+ if (hasAlreadyProcessedMessage(msg, model)) return;
920
920
  if (isLegacyEnabled(options)) {
921
921
  const value = ictcg1(model, msg, publish, options, 'stop');
922
922
  return {action: `rotate_stop`, brightness: value};
package/lib/utils.js CHANGED
@@ -66,7 +66,8 @@ function mapNumberRange(value, fromLow, fromHigh, toLow, toHigh, precision=0) {
66
66
  }
67
67
 
68
68
  const transactionStore = {};
69
- function hasAlreadyProcessedMessage(msg, ID=null, key=null) {
69
+ function hasAlreadyProcessedMessage(msg, model, ID=null, key=null) {
70
+ if (model.meta && model.meta.publishDuplicateTransaction) return false;
70
71
  const currentID = ID !== null ? ID : msg.meta.zclTransactionSequenceNumber;
71
72
  key = key || msg.device.ieeeAddr;
72
73
  if (transactionStore[key] === currentID) return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.654",
3
+ "version": "14.0.656",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -38,7 +38,7 @@
38
38
  "buffer-crc32": "^0.2.13",
39
39
  "https-proxy-agent": "^5.0.1",
40
40
  "tar-stream": "^2.2.0",
41
- "zigbee-herdsman": "^0.14.66"
41
+ "zigbee-herdsman": "^0.14.67"
42
42
  },
43
43
  "devDependencies": {
44
44
  "eslint": "*",