zigbee-herdsman-converters 14.0.396 → 14.0.400

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.
@@ -1042,7 +1042,7 @@ const converters = {
1042
1042
  cluster: 'genScenes',
1043
1043
  type: 'commandRecall',
1044
1044
  convert: (model, msg, publish, options, meta) => {
1045
- if (hasAlreadyProcessedMessage(msg)) return;
1045
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1046
1046
  const payload = {action: postfixWithEndpointName(`recall_${msg.data.sceneid}`, msg, model)};
1047
1047
  addActionGroup(payload, msg, model);
1048
1048
  return payload;
@@ -1052,7 +1052,7 @@ const converters = {
1052
1052
  cluster: 'ssIasAce',
1053
1053
  type: 'commandPanic',
1054
1054
  convert: (model, msg, publish, options, meta) => {
1055
- if (hasAlreadyProcessedMessage(msg)) return;
1055
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1056
1056
  const payload = {action: postfixWithEndpointName(`panic`, msg, model)};
1057
1057
  addActionGroup(payload, msg, model);
1058
1058
  return payload;
@@ -1062,7 +1062,7 @@ const converters = {
1062
1062
  cluster: 'ssIasAce',
1063
1063
  type: 'commandArm',
1064
1064
  convert: (model, msg, publish, options, meta) => {
1065
- if (hasAlreadyProcessedMessage(msg)) return;
1065
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1066
1066
  const payload = {
1067
1067
  action: postfixWithEndpointName(constants.armMode[msg.data['armmode']], msg, model),
1068
1068
  action_code: msg.data.code,
@@ -1086,7 +1086,7 @@ const converters = {
1086
1086
  cluster: 'closuresWindowCovering',
1087
1087
  type: 'commandStop',
1088
1088
  convert: (model, msg, publish, options, meta) => {
1089
- if (hasAlreadyProcessedMessage(msg)) return;
1089
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1090
1090
  const payload = {action: postfixWithEndpointName('stop', msg, model)};
1091
1091
  addActionGroup(payload, msg, model);
1092
1092
  return payload;
@@ -1096,7 +1096,7 @@ const converters = {
1096
1096
  cluster: 'closuresWindowCovering',
1097
1097
  type: 'commandUpOpen',
1098
1098
  convert: (model, msg, publish, options, meta) => {
1099
- if (hasAlreadyProcessedMessage(msg)) return;
1099
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1100
1100
  const payload = {action: postfixWithEndpointName('open', msg, model)};
1101
1101
  addActionGroup(payload, msg, model);
1102
1102
  return payload;
@@ -1106,7 +1106,7 @@ const converters = {
1106
1106
  cluster: 'closuresWindowCovering',
1107
1107
  type: 'commandDownClose',
1108
1108
  convert: (model, msg, publish, options, meta) => {
1109
- if (hasAlreadyProcessedMessage(msg)) return;
1109
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1110
1110
  const payload = {action: postfixWithEndpointName('close', msg, model)};
1111
1111
  addActionGroup(payload, msg, model);
1112
1112
  return payload;
@@ -1116,7 +1116,7 @@ const converters = {
1116
1116
  cluster: 'genOnOff',
1117
1117
  type: 'commandOn',
1118
1118
  convert: (model, msg, publish, options, meta) => {
1119
- if (hasAlreadyProcessedMessage(msg)) return;
1119
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1120
1120
  const payload = {action: postfixWithEndpointName('on', msg, model)};
1121
1121
  addActionGroup(payload, msg, model);
1122
1122
  return payload;
@@ -1126,7 +1126,7 @@ const converters = {
1126
1126
  cluster: 'genOnOff',
1127
1127
  type: 'commandOff',
1128
1128
  convert: (model, msg, publish, options, meta) => {
1129
- if (hasAlreadyProcessedMessage(msg)) return;
1129
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1130
1130
  const payload = {action: postfixWithEndpointName('off', msg, model)};
1131
1131
  addActionGroup(payload, msg, model);
1132
1132
  return payload;
@@ -1136,7 +1136,7 @@ const converters = {
1136
1136
  cluster: 'genOnOff',
1137
1137
  type: 'commandOffWithEffect',
1138
1138
  convert: (model, msg, publish, options, meta) => {
1139
- if (hasAlreadyProcessedMessage(msg)) return;
1139
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1140
1140
  const payload = {action: postfixWithEndpointName(`off`, msg, model)};
1141
1141
  addActionGroup(payload, msg, model);
1142
1142
  return payload;
@@ -1146,7 +1146,7 @@ const converters = {
1146
1146
  cluster: 'genOnOff',
1147
1147
  type: 'commandToggle',
1148
1148
  convert: (model, msg, publish, options, meta) => {
1149
- if (hasAlreadyProcessedMessage(msg)) return;
1149
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1150
1150
  const payload = {action: postfixWithEndpointName('toggle', msg, model)};
1151
1151
  addActionGroup(payload, msg, model);
1152
1152
  return payload;
@@ -1157,7 +1157,7 @@ const converters = {
1157
1157
  type: ['commandMoveToLevel', 'commandMoveToLevelWithOnOff'],
1158
1158
  options: [exposes.options.simulated_brightness()],
1159
1159
  convert: (model, msg, publish, options, meta) => {
1160
- if (hasAlreadyProcessedMessage(msg)) return;
1160
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1161
1161
  const payload = {
1162
1162
  action: postfixWithEndpointName(`brightness_move_to_level`, msg, model),
1163
1163
  action_level: msg.data.level,
@@ -1179,7 +1179,7 @@ const converters = {
1179
1179
  type: ['commandMove', 'commandMoveWithOnOff'],
1180
1180
  options: [exposes.options.simulated_brightness()],
1181
1181
  convert: (model, msg, publish, options, meta) => {
1182
- if (hasAlreadyProcessedMessage(msg)) return;
1182
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1183
1183
  const direction = msg.data.movemode === 1 ? 'down' : 'up';
1184
1184
  const action = postfixWithEndpointName(`brightness_move_${direction}`, msg, model);
1185
1185
  const payload = {action, action_rate: msg.data.rate};
@@ -1215,7 +1215,7 @@ const converters = {
1215
1215
  type: ['commandStep', 'commandStepWithOnOff'],
1216
1216
  options: [exposes.options.simulated_brightness()],
1217
1217
  convert: (model, msg, publish, options, meta) => {
1218
- if (hasAlreadyProcessedMessage(msg)) return;
1218
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1219
1219
  const direction = msg.data.stepmode === 1 ? 'down' : 'up';
1220
1220
  const payload = {
1221
1221
  action: postfixWithEndpointName(`brightness_step_${direction}`, msg, model),
@@ -1242,7 +1242,7 @@ const converters = {
1242
1242
  type: ['commandStop', 'commandStopWithOnOff'],
1243
1243
  options: [exposes.options.simulated_brightness()],
1244
1244
  convert: (model, msg, publish, options, meta) => {
1245
- if (hasAlreadyProcessedMessage(msg)) return;
1245
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1246
1246
  if (options.simulated_brightness) {
1247
1247
  clearInterval(globalStore.getValue(msg.endpoint, 'simulated_brightness_timer'));
1248
1248
  globalStore.putValue(msg.endpoint, 'simulated_brightness_timer', undefined);
@@ -1257,7 +1257,7 @@ const converters = {
1257
1257
  cluster: 'lightingColorCtrl',
1258
1258
  type: ['commandMoveColorTemp'],
1259
1259
  convert: (model, msg, publish, options, meta) => {
1260
- if (hasAlreadyProcessedMessage(msg)) return;
1260
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1261
1261
  const direction = msg.data.movemode === 1 ? 'down' : 'up';
1262
1262
  const action = postfixWithEndpointName(`color_temperature_move_${direction}`, msg, model);
1263
1263
  const payload = {action, action_rate: msg.data.rate, action_minimum: msg.data.minimum, action_maximum: msg.data.maximum};
@@ -1269,7 +1269,7 @@ const converters = {
1269
1269
  cluster: 'lightingColorCtrl',
1270
1270
  type: 'commandStepColorTemp',
1271
1271
  convert: (model, msg, publish, options, meta) => {
1272
- if (hasAlreadyProcessedMessage(msg)) return;
1272
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1273
1273
  const direction = msg.data.stepmode === 1 ? 'up' : 'down';
1274
1274
  const payload = {
1275
1275
  action: postfixWithEndpointName(`color_temperature_step_${direction}`, msg, model),
@@ -1288,7 +1288,7 @@ const converters = {
1288
1288
  cluster: 'lightingColorCtrl',
1289
1289
  type: 'commandEnhancedMoveToHueAndSaturation',
1290
1290
  convert: (model, msg, publish, options, meta) => {
1291
- if (hasAlreadyProcessedMessage(msg)) return;
1291
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1292
1292
  const payload = {
1293
1293
  action: postfixWithEndpointName(`enhanced_move_to_hue_and_saturation`, msg, model),
1294
1294
  action_enhanced_hue: msg.data.enhancehue,
@@ -1305,7 +1305,7 @@ const converters = {
1305
1305
  cluster: 'lightingColorCtrl',
1306
1306
  type: ['commandStepHue'],
1307
1307
  convert: (model, msg, publish, options, meta) => {
1308
- if (hasAlreadyProcessedMessage(msg)) return;
1308
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1309
1309
  const direction = msg.data.stepmode === 1 ? 'up' : 'down';
1310
1310
  const payload = {
1311
1311
  action: postfixWithEndpointName(`color_hue_step_${direction}`, msg, model),
@@ -1320,7 +1320,7 @@ const converters = {
1320
1320
  cluster: 'lightingColorCtrl',
1321
1321
  type: ['commandStepSaturation'],
1322
1322
  convert: (model, msg, publish, options, meta) => {
1323
- if (hasAlreadyProcessedMessage(msg)) return;
1323
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1324
1324
  const direction = msg.data.stepmode === 1 ? 'up' : 'down';
1325
1325
  const payload = {
1326
1326
  action: postfixWithEndpointName(`color_saturation_step_${direction}`, msg, model),
@@ -1335,7 +1335,7 @@ const converters = {
1335
1335
  cluster: 'lightingColorCtrl',
1336
1336
  type: 'commandColorLoopSet',
1337
1337
  convert: (model, msg, publish, options, meta) => {
1338
- if (hasAlreadyProcessedMessage(msg)) return;
1338
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1339
1339
  const updateFlags = msg.data.updateflags;
1340
1340
  const actionLookup = {
1341
1341
  0x00: 'deactivate',
@@ -1365,7 +1365,7 @@ const converters = {
1365
1365
  cluster: 'lightingColorCtrl',
1366
1366
  type: 'commandMoveToColorTemp',
1367
1367
  convert: (model, msg, publish, options, meta) => {
1368
- if (hasAlreadyProcessedMessage(msg)) return;
1368
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1369
1369
  const payload = {
1370
1370
  action: postfixWithEndpointName(`color_temperature_move`, msg, model),
1371
1371
  action_color_temperature: msg.data.colortemp,
@@ -1379,7 +1379,7 @@ const converters = {
1379
1379
  cluster: 'lightingColorCtrl',
1380
1380
  type: 'commandMoveToColor',
1381
1381
  convert: (model, msg, publish, options, meta) => {
1382
- if (hasAlreadyProcessedMessage(msg)) return;
1382
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1383
1383
  const payload = {
1384
1384
  action: postfixWithEndpointName(`color_move`, msg, model),
1385
1385
  action_color: {
@@ -1396,7 +1396,7 @@ const converters = {
1396
1396
  cluster: 'lightingColorCtrl',
1397
1397
  type: 'commandMoveHue',
1398
1398
  convert: (model, msg, publish, options, meta) => {
1399
- if (hasAlreadyProcessedMessage(msg)) return;
1399
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1400
1400
  const movestop = msg.data.movemode == 1 ? 'move' : 'stop';
1401
1401
  const action = postfixWithEndpointName(`hue_${movestop}`, msg, model);
1402
1402
  const payload = {action, action_rate: msg.data.rate};
@@ -1408,7 +1408,7 @@ const converters = {
1408
1408
  cluster: 'lightingColorCtrl',
1409
1409
  type: 'commandMoveToSaturation',
1410
1410
  convert: (model, msg, publish, options, meta) => {
1411
- if (hasAlreadyProcessedMessage(msg)) return;
1411
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1412
1412
  const payload = {
1413
1413
  action: postfixWithEndpointName('move_to_saturation', msg, model),
1414
1414
  action_saturation: msg.data.saturation,
@@ -1422,7 +1422,7 @@ const converters = {
1422
1422
  cluster: 'lightingColorCtrl',
1423
1423
  type: 'commandMoveToHue',
1424
1424
  convert: (model, msg, publish, options, meta) => {
1425
- if (hasAlreadyProcessedMessage(msg)) return;
1425
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1426
1426
  const payload = {
1427
1427
  action: postfixWithEndpointName(`move_to_hue`, msg, model),
1428
1428
  action_hue: msg.data.hue,
@@ -1437,7 +1437,7 @@ const converters = {
1437
1437
  cluster: 'ssIasAce',
1438
1438
  type: 'commandEmergency',
1439
1439
  convert: (model, msg, publish, options, meta) => {
1440
- if (hasAlreadyProcessedMessage(msg)) return;
1440
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1441
1441
  const payload = {action: postfixWithEndpointName(`emergency`, msg, model)};
1442
1442
  addActionGroup(payload, msg, model);
1443
1443
  return payload;
@@ -1447,7 +1447,7 @@ const converters = {
1447
1447
  cluster: 'genOnOff',
1448
1448
  type: 'commandOn',
1449
1449
  convert: (model, msg, publish, options, meta) => {
1450
- if (hasAlreadyProcessedMessage(msg)) return;
1450
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1451
1451
  const property = postfixWithEndpointName('state', msg, model);
1452
1452
  return {[property]: 'ON'};
1453
1453
  },
@@ -1456,7 +1456,7 @@ const converters = {
1456
1456
  cluster: 'genOnOff',
1457
1457
  type: 'commandOff',
1458
1458
  convert: (model, msg, publish, options, meta) => {
1459
- if (hasAlreadyProcessedMessage(msg)) return;
1459
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
1460
1460
  const property = postfixWithEndpointName('state', msg, model);
1461
1461
  return {[property]: 'OFF'};
1462
1462
  },
@@ -2181,7 +2181,7 @@ const converters = {
2181
2181
  cluster: 'ssIasZone',
2182
2182
  type: 'commandStatusChangeNotification',
2183
2183
  convert: (model, msg, publish, options, meta) => {
2184
- if (hasAlreadyProcessedMessage(msg)) return;
2184
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
2185
2185
  const lookup = {1: 'pressed'};
2186
2186
  const zoneStatus = msg.data.zonestatus;
2187
2187
  return {
@@ -4045,7 +4045,8 @@ const converters = {
4045
4045
  exposes.options.precision('humidity'), exposes.options.calibration('humidity'),
4046
4046
  exposes.options.precision('co2'), exposes.options.calibration('co2'),
4047
4047
  exposes.options.precision('voc'), exposes.options.calibration('voc'),
4048
- exposes.options.precision('formaldehyd'), exposes.options.calibration('formaldehyd')],
4048
+ exposes.options.precision('formaldehyd'), exposes.options.calibration('formaldehyd'),
4049
+ exposes.options.precision('pm25'), exposes.options.calibration('pm25')],
4049
4050
  convert: (model, msg, publish, options, meta) => {
4050
4051
  const dpValue = tuya.firstDpValue(msg, meta, 'tuya_air_quality');
4051
4052
  const dp = dpValue.dp;
@@ -4062,6 +4063,8 @@ const converters = {
4062
4063
  case tuya.dataPoints.tuyaSabFormaldehyd:
4063
4064
  // Not sure which unit this is, supposedly mg/m³, but the value seems way too high.
4064
4065
  return {formaldehyd: calibrateAndPrecisionRoundOptions(value, options, 'formaldehyd')};
4066
+ case tuya.dataPoints.tuyaSahkMP25:
4067
+ return {pm25: calibrateAndPrecisionRoundOptions(value, options, 'pm25')};
4065
4068
  default:
4066
4069
  meta.logger.warn(`zigbee-herdsman-converters:TuyaSmartAirBox: Unrecognized DP #${
4067
4070
  dp} with data ${JSON.stringify(dpValue)}`);
@@ -4515,7 +4518,7 @@ const converters = {
4515
4518
  cluster: 'genScenes',
4516
4519
  type: 'commandTradfriArrowSingle',
4517
4520
  convert: (model, msg, publish, options, meta) => {
4518
- if (hasAlreadyProcessedMessage(msg)) return;
4521
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
4519
4522
  if (msg.data.value === 2) {
4520
4523
  // This is send on toggle hold, ignore it as a toggle_hold is already handled above.
4521
4524
  return;
@@ -4529,7 +4532,7 @@ const converters = {
4529
4532
  cluster: 'genScenes',
4530
4533
  type: 'commandTradfriArrowHold',
4531
4534
  convert: (model, msg, publish, options, meta) => {
4532
- if (hasAlreadyProcessedMessage(msg)) return;
4535
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
4533
4536
  const direction = msg.data.value === 3329 ? 'left' : 'right';
4534
4537
  globalStore.putValue(msg.endpoint, 'direction', direction);
4535
4538
  return {action: `arrow_${direction}_hold`};
@@ -4540,7 +4543,7 @@ const converters = {
4540
4543
  type: 'commandTradfriArrowRelease',
4541
4544
  options: [exposes.options.legacy()],
4542
4545
  convert: (model, msg, publish, options, meta) => {
4543
- if (hasAlreadyProcessedMessage(msg)) return;
4546
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
4544
4547
  const direction = globalStore.getValue(msg.endpoint, 'direction');
4545
4548
  if (direction) {
4546
4549
  globalStore.clearValue(msg.endpoint, 'direction');
@@ -5250,9 +5253,11 @@ const converters = {
5250
5253
  else if (index === 5) {
5251
5254
  if (['JT-BZ-01AQ/A'].includes(model.model)) payload.power_outage_count = value;
5252
5255
  } else if (index === 100) {
5253
- if (['QBKG19LM', 'QBKG20LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM'].includes(model.model)) {
5256
+ if (['QBKG20LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM'].includes(model.model)) {
5254
5257
  const mapping = model.model === 'QBCZ15LM' ? 'relay' : 'left';
5255
5258
  payload[`state_${mapping}`] = value === 1 ? 'ON' : 'OFF';
5259
+ } else if (['WXKG14LM', 'WXKG16LM', 'WXKG17LM'].includes(model.model)) {
5260
+ payload.click_mode = {1: 'fast', 2: 'multi'}[value];
5256
5261
  } else {
5257
5262
  payload.state = value === 1 ? 'ON' : 'OFF';
5258
5263
  }
@@ -5260,10 +5265,17 @@ const converters = {
5260
5265
  if (['QBKG19LM', 'QBKG20LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM'].includes(model.model)) {
5261
5266
  const mapping = model.model === 'QBCZ15LM' ? 'usb' : 'right';
5262
5267
  payload[`state_${mapping}`] = value === 1 ? 'ON' : 'OFF';
5268
+ } else if (['QBKG25LM', 'QBKG34LM'].includes(model.model)) {
5269
+ payload.state_center = value === 1 ? 'ON' : 'OFF';
5263
5270
  } else if (['RTCGQ12LM'].includes(model.model)) {
5264
5271
  payload.illuminance = calibrateAndPrecisionRoundOptions(value, options, 'illuminance');
5265
5272
  }
5266
- } else if (index === 149) {
5273
+ } else if (index ===102 ) {
5274
+ if (['QBKG25LM', 'QBKG34LM'].includes(model.model)) {
5275
+ payload.state_right = value === 1 ? 'ON' : 'OFF';
5276
+ }
5277
+ } else if (index === 105) payload.motion_sensitivity = {1: 'low', 2: 'medium', 3: 'high'}[value]; // RTCGQ13LM
5278
+ else if (index === 149) {
5267
5279
  payload.energy = precisionRound(value, 2); // 0x95
5268
5280
  // Consumption is deprecated
5269
5281
  payload.consumption = payload.energy;
@@ -5379,7 +5391,7 @@ const converters = {
5379
5391
  cluster: 'genMultistateInput',
5380
5392
  type: ['attributeReport'],
5381
5393
  convert: (model, msg, publish, options, meta) => {
5382
- if (hasAlreadyProcessedMessage(msg)) return;
5394
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5383
5395
  let actionLookup = {0: 'hold', 1: 'single', 2: 'double', 3: 'triple', 255: 'release'};
5384
5396
  if (model.model === 'WXKG12LM') {
5385
5397
  actionLookup = {...actionLookup, 16: 'hold', 17: 'release', 18: 'shake'};
@@ -5525,7 +5537,7 @@ const converters = {
5525
5537
  `increase it with this option (value is in ms).`),
5526
5538
  ],
5527
5539
  convert: (model, msg, publish, options, meta) => {
5528
- if (hasAlreadyProcessedMessage(msg)) return;
5540
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5529
5541
  const state = msg.data['onOff'];
5530
5542
 
5531
5543
  // 0 = click down, 1 = click up, else = multiple clicks
@@ -5831,7 +5843,7 @@ const converters = {
5831
5843
  cluster: 'genMultistateInput',
5832
5844
  type: ['attributeReport', 'readResponse'],
5833
5845
  convert: (model, msg, publish, options, meta) => {
5834
- if (hasAlreadyProcessedMessage(msg)) return;
5846
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5835
5847
  const actionLookup = {0: 'hold', 255: 'release', 1: 'single', 2: 'double', 3: 'triple', 5: 'quintuple', 6: 'many'};
5836
5848
  const button = msg.endpoint.ID;
5837
5849
  const value = msg.data.presentValue;
@@ -5854,7 +5866,7 @@ const converters = {
5854
5866
  cluster: 'genOnOff',
5855
5867
  type: 'commandOn',
5856
5868
  convert: (model, msg, publish, options, meta) => {
5857
- if (hasAlreadyProcessedMessage(msg)) return;
5869
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5858
5870
  return {action: 'button_2_single'};
5859
5871
  },
5860
5872
  },
@@ -5862,7 +5874,7 @@ const converters = {
5862
5874
  cluster: 'genOnOff',
5863
5875
  type: 'commandOff',
5864
5876
  convert: (model, msg, publish, options, meta) => {
5865
- if (hasAlreadyProcessedMessage(msg)) return;
5877
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5866
5878
  return {action: 'button_1_single'};
5867
5879
  },
5868
5880
  },
@@ -5870,7 +5882,7 @@ const converters = {
5870
5882
  cluster: 'genLevelCtrl',
5871
5883
  type: 'commandStep',
5872
5884
  convert: (model, msg, publish, options, meta) => {
5873
- if (hasAlreadyProcessedMessage(msg)) return;
5885
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5874
5886
  const button = msg.data.stepmode === 0 ? '4' : '3';
5875
5887
  return {action: `button_${button}_single`};
5876
5888
  },
@@ -5880,7 +5892,7 @@ const converters = {
5880
5892
  type: 'commandStop',
5881
5893
  options: [exposes.options.legacy()],
5882
5894
  convert: (model, msg, publish, options, meta) => {
5883
- if (hasAlreadyProcessedMessage(msg)) return;
5895
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5884
5896
  if (globalStore.hasValue(msg.endpoint, 'button')) {
5885
5897
  const value = globalStore.getValue(msg.endpoint, 'button');
5886
5898
  const duration = Date.now() - value.start;
@@ -5894,7 +5906,7 @@ const converters = {
5894
5906
  cluster: 'genLevelCtrl',
5895
5907
  type: 'commandMove',
5896
5908
  convert: (model, msg, publish, options, meta) => {
5897
- if (hasAlreadyProcessedMessage(msg)) return;
5909
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5898
5910
  const button = msg.data.movemode === 0 ? '4' : '3';
5899
5911
  globalStore.putValue(msg.endpoint, 'button', {button, start: Date.now()});
5900
5912
  return {action: `button_${button}_hold`};
@@ -5904,7 +5916,7 @@ const converters = {
5904
5916
  cluster: 'lightingColorCtrl',
5905
5917
  type: 'commandStepColorTemp',
5906
5918
  convert: (model, msg, publish, options, meta) => {
5907
- if (hasAlreadyProcessedMessage(msg)) return;
5919
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5908
5920
  let action;
5909
5921
  if (model.model === 'WXCJKG12LM') {
5910
5922
  // for WXCJKG12LM model it's double click event on buttons 3 and 4
@@ -5921,7 +5933,7 @@ const converters = {
5921
5933
  type: 'commandMoveColorTemp',
5922
5934
  options: [exposes.options.legacy()],
5923
5935
  convert: (model, msg, publish, options, meta) => {
5924
- if (hasAlreadyProcessedMessage(msg)) return;
5936
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
5925
5937
  const stop = msg.data.movemode === 0;
5926
5938
  let result = null;
5927
5939
  if (stop) {
@@ -7228,6 +7240,26 @@ const converters = {
7228
7240
  return {occupancy: (zoneStatus & 1) > 0, tamper: (zoneStatus & 4) > 0};
7229
7241
  },
7230
7242
  },
7243
+ ZM35HQ_attr: {
7244
+ cluster: 'ssIasZone',
7245
+ type: ['attributeReport', 'readResponse'],
7246
+ convert: (model, msg, publish, options, meta) => {
7247
+ let result = {};
7248
+ const data = msg.data;
7249
+ if (data && data.hasOwnProperty('zoneStatus')) {
7250
+ result = converters.ias_occupancy_alarm_1_report.convert(model, msg, publish, options, meta);
7251
+ }
7252
+ if (data && data.hasOwnProperty('currentZoneSensitivityLevel')) {
7253
+ const senslookup = {'0': 'low', '1': 'medium', '2': 'high'};
7254
+ result.sensitivity = senslookup[data.currentZoneSensitivityLevel];
7255
+ }
7256
+ if (data && data.hasOwnProperty('61441')) {
7257
+ const keeptimelookup = {'0': 30, '1': 60, '2': 120};
7258
+ result.keep_time = keeptimelookup[data['61441']];
7259
+ }
7260
+ return result;
7261
+ },
7262
+ },
7231
7263
  tuya_gas: {
7232
7264
  cluster: 'manuSpecificTuya',
7233
7265
  type: ['commandDataResponse'],
@@ -7327,7 +7359,7 @@ const converters = {
7327
7359
  cluster: 'wiserDeviceInfo',
7328
7360
  type: 'attributeReport',
7329
7361
  convert: (model, msg, publish, options, meta) => {
7330
- if (hasAlreadyProcessedMessage(msg)) return;
7362
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
7331
7363
 
7332
7364
  const data = msg.data['deviceInfo'].split(',');
7333
7365
  if (data[0] === 'UI' && data[1]) {
@@ -8105,7 +8137,7 @@ const converters = {
8105
8137
  convert: (model, msg, publish, options, meta) => {
8106
8138
  // commandStopMove without params
8107
8139
  if (msg.data[2] !== 71) return;
8108
- if (hasAlreadyProcessedMessage(msg)) return;
8140
+ if ((!model.meta || !model.meta.handleDuplicateTransaction) && hasAlreadyProcessedMessage(msg)) return;
8109
8141
  const movestop = 'stop';
8110
8142
  const action = postfixWithEndpointName(`hue_${movestop}`, msg, model);
8111
8143
  const payload = {action};
@@ -5961,6 +5961,29 @@ const converters = {
5961
5961
  }
5962
5962
  },
5963
5963
  },
5964
+ ZM35HQ_attr: {
5965
+ key: [
5966
+ 'sensitivity', 'keep_time',
5967
+ ],
5968
+ convertSet: async (entity, key, value, meta) => {
5969
+ switch (key) {
5970
+ case 'sensitivity':
5971
+ await entity.write('ssIasZone', {currentZoneSensitivityLevel: {'low': 0, 'medium': 1, 'high': 2}[value]},
5972
+ {sendWhen: 'active'});
5973
+ break;
5974
+ case 'keep_time':
5975
+ await entity.write('ssIasZone', {61441: {value: {'30': 0, '60': 1, '120': 2}[value], type: 0x20}}, {sendWhen: 'active'});
5976
+ break;
5977
+ default: // Unknown key
5978
+ throw new Error(`Unhandled key ${key}`);
5979
+ }
5980
+ },
5981
+ convertGet: async (entity, key, meta) => {
5982
+ // Apparently, reading values may interfere with a commandStatusChangeNotification for changed occupancy.
5983
+ // Therefore, read "zoneStatus" as well.
5984
+ await entity.read('ssIasZone', ['currentZoneSensitivityLevel', 61441, 'zoneStatus'], {sendWhen: 'active'});
5985
+ },
5986
+ },
5964
5987
  TS0210_sensitivity: {
5965
5988
  key: ['sensitivity'],
5966
5989
  convertSet: async (entity, key, value, meta) => {
@@ -353,4 +353,26 @@ module.exports = [
353
353
  },
354
354
  exposes: [e.soil_moisture(), e.battery(), e.illuminance(), e.temperature(), e.humidity()],
355
355
  },
356
+ {
357
+ zigbeeModel: ['EFEKTA_eON29wz'],
358
+ model: 'EFEKTA_eON29wz',
359
+ vendor: 'Custom devices (DiY)',
360
+ description: '[Mini weather station, barometer, forecast, charts, temperature, humidity, light](http://efektalab.com/eON290wz)',
361
+ fromZigbee: [fz.temperature, fz.humidity, fz.pressure, fz.illuminance, fz.battery],
362
+ toZigbee: [tz.factory_reset],
363
+ configure: async (device, coordinatorEndpoint, logger) => {
364
+ const endpoint = device.getEndpoint(1);
365
+ await reporting.bind(endpoint, coordinatorEndpoint, [
366
+ 'genPowerCfg', 'msTemperatureMeasurement', 'msRelativeHumidity', 'msPressureMeasurement', 'msIlluminanceMeasurement']);
367
+ const overides = {min: 0, max: 21600, change: 0};
368
+ await reporting.batteryVoltage(endpoint, overides);
369
+ await reporting.batteryPercentageRemaining(endpoint, overides);
370
+ await reporting.temperature(endpoint, overides);
371
+ await reporting.humidity(endpoint, overides);
372
+ await reporting.illuminance(endpoint, overides);
373
+ await reporting.pressureExtended(endpoint, overides);
374
+ await endpoint.read('msPressureMeasurement', ['scale']);
375
+ },
376
+ exposes: [e.battery(), e.illuminance(), e.temperature(), e.humidity(), e.pressure()],
377
+ },
356
378
  ];
package/devices/ecodim.js CHANGED
@@ -98,7 +98,6 @@ module.exports = [
98
98
  model: 'ED-10014',
99
99
  vendor: 'EcoDim',
100
100
  description: 'Zigbee 8 button wall switch - white',
101
- supports: '',
102
101
  fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery],
103
102
  exposes: [e.battery(), e.action(['on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1',
104
103
  'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', 'on_3', 'off_3',
@@ -112,7 +111,6 @@ module.exports = [
112
111
  model: 'ED-10015',
113
112
  vendor: 'EcoDim',
114
113
  description: 'Zigbee 8 button wall switch - black',
115
- supports: '',
116
114
  fromZigbee: [fz.command_on, fz.command_off, fz.command_move, fz.command_stop, fz.battery],
117
115
  exposes: [e.battery(), e.action(['on_1', 'off_1', 'brightness_move_up_1', 'brightness_move_down_1', 'brightness_stop_1',
118
116
  'on_2', 'off_2', 'brightness_move_up_2', 'brightness_move_down_2', 'brightness_stop_2', 'on_3', 'off_3', 'brightness_move_up_3',
package/devices/edp.js CHANGED
@@ -16,8 +16,10 @@ module.exports = [
16
16
  configure: async (device, coordinatorEndpoint, logger) => {
17
17
  const endpoint = device.getEndpoint(85);
18
18
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'seMetering']);
19
+ // await reporting.readMeteringMultiplierDivisor(endpoint);
20
+ // Fix for UNSUPPORTED_ATTRIBUTE
21
+ endpoint.saveClusterAttributeKeyValue('seMetering', {divisor: 1000, multiplier: 1});
19
22
  await reporting.onOff(endpoint);
20
- await reporting.readMeteringMultiplierDivisor(endpoint);
21
23
  await reporting.instantaneousDemand(endpoint);
22
24
  },
23
25
  exposes: [e.switch(), e.power(), e.energy()],
package/devices/ikea.js CHANGED
@@ -746,10 +746,10 @@ module.exports = [
746
746
  extend: tradfriExtend.light_onoff_brightness_colortemp_color(),
747
747
  },
748
748
  {
749
- zigbeeModel: ['TRADFRIbulbE14WWclear250lm'],
749
+ zigbeeModel: ['TRADFRIbulbE14WWclear250lm', 'TRADFRIbulbE12WWclear250lm'],
750
750
  model: 'LED1935C3',
751
751
  vendor: 'IKEA',
752
- description: 'TRADFRI LED bulb E14 WW clear 250 lumen, dimmable',
752
+ description: 'TRADFRI LED bulb E12/E14 WW clear 250 lumen, dimmable',
753
753
  extend: tradfriExtend.light_onoff_brightness(),
754
754
  },
755
755
  {
package/devices/jxuan.js CHANGED
@@ -38,7 +38,6 @@ module.exports = [
38
38
  model: 'SPZ01',
39
39
  vendor: 'J.XUAN',
40
40
  description: 'plug',
41
- supports: 'plug',
42
41
  fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering],
43
42
  exposes: [e.switch(), e.power(), e.power_outage_memory().withAccess(ea.STATE_SET)],
44
43
  toZigbee: [tz.on_off, tz.SPZ01_power_outage_memory],
@@ -111,7 +111,7 @@ module.exports = [
111
111
  exposes: [e.battery(), e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up',
112
112
  'brightness_move_down', 'brightness_stop'])],
113
113
  toZigbee: [],
114
- meta: {battery: {voltageToPercentage: '3V_2500'}},
114
+ meta: {handleDuplicateTransaction: true, battery: {voltageToPercentage: '3V_2500'}},
115
115
  configure: async (device, coordinatorEndpoint, logger) => {
116
116
  const endpoint = device.getEndpoint(1);
117
117
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'genOnOff', 'genLevelCtrl']);
@@ -127,7 +127,7 @@ module.exports = [
127
127
  exposes: [e.battery(),
128
128
  e.action(['identify', 'on', 'off', 'toggle', 'brightness_move_up', 'brightness_move_down', 'brightness_stop'])],
129
129
  toZigbee: [],
130
- meta: {multiEndpoint: true, battery: {voltageToPercentage: '3V_2500'}},
130
+ meta: {handleDuplicateTransaction: true, multiEndpoint: true, battery: {voltageToPercentage: '3V_2500'}},
131
131
  configure: async (device, coordinatorEndpoint, logger) => {
132
132
  const endpoint = device.getEndpoint(1);
133
133
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'genOnOff', 'genLevelCtrl']);
package/devices/moes.js CHANGED
@@ -303,7 +303,6 @@ module.exports = [
303
303
  model: 'MS-108ZR',
304
304
  vendor: 'Moes',
305
305
  description: 'Zigbee + RF curtain switch module',
306
- supports: 'open, close, stop, position',
307
306
  fromZigbee: [fz.tuya_cover_options, fz.cover_position_tilt],
308
307
  toZigbee: [tz.cover_state, tz.moes_cover_calibration, tz.cover_position_tilt, tz.tuya_cover_reversal],
309
308
  exposes: [e.cover_position(), exposes.numeric('calibration_time', ea.ALL).withValueMin(0).withValueMax(100),
package/devices/owon.js CHANGED
@@ -27,7 +27,6 @@ module.exports = [
27
27
  model: 'CB432',
28
28
  vendor: 'OWON',
29
29
  description: '32A/63A power circuit breaker',
30
- supports: 'on/off, power measurement',
31
30
  fromZigbee: [fz.on_off, fz.metering, fz.electrical_measurement],
32
31
  toZigbee: [tz.on_off],
33
32
  exposes: [e.switch(), e.power(), e.energy()],
@@ -40,7 +40,6 @@ module.exports = [
40
40
  description: 'Motion sensor',
41
41
  fromZigbee: [fz.battery, fz.ias_occupancy_alarm_1],
42
42
  toZigbee: [],
43
- meta: {battery: {dontDividePercentage: true}},
44
43
  exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery()],
45
44
  configure: async (device, coordinatorEndpoint, logger) => {
46
45
  const endpoint = device.getEndpoint(1);
@@ -563,6 +563,15 @@ module.exports = [
563
563
  extend: hueExtend.light_onoff_brightness(),
564
564
  ota: ota.zigbeeOTA,
565
565
  },
566
+ {
567
+ zigbeeModel: ['LWO002'],
568
+ model: '9290022415',
569
+ vendor: 'Philips',
570
+ description: 'Hue White G25 E26 Edison Filament Globe Bluetooth',
571
+ meta: {turnsOffAtBrightness1: true},
572
+ extend: hueExtend.light_onoff_brightness(),
573
+ ota: ota.zigbeeOTA,
574
+ },
566
575
  {
567
576
  zigbeeModel: ['LWA004'],
568
577
  model: '8718699688820',
@@ -15,6 +15,13 @@ module.exports = [
15
15
  description: 'E27 filament bulb dimmable',
16
16
  extend: extend.light_onoff_brightness(),
17
17
  },
18
+ {
19
+ zigbeeModel: ['PROLIGHT E27 WARM WHITE'],
20
+ model: '5412748727364',
21
+ vendor: 'Prolight',
22
+ description: 'E27 bulb dimmable',
23
+ extend: extend.light_onoff_brightness(),
24
+ },
18
25
  {
19
26
  zigbeeModel: ['PROLIGHT GU10 WHITE AND COLOUR'],
20
27
  model: '5412748727401',
@@ -186,6 +186,18 @@ module.exports = [
186
186
  await reporting.brightness(endpoint);
187
187
  },
188
188
  },
189
+ {
190
+ zigbeeModel: ['CH2AX/SWITCH/1'],
191
+ model: '41E2PBSWMZ/356PB2MBTZ',
192
+ vendor: 'Schneider Electric',
193
+ description: 'Wiser 40/300-Series module switch 2A',
194
+ extend: extend.switch(),
195
+ configure: async (device, coordinatorEndpoint, logger) => {
196
+ const endpoint = device.getEndpoint(1);
197
+ await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
198
+ await reporting.onOff(endpoint);
199
+ },
200
+ },
189
201
  {
190
202
  zigbeeModel: ['SMARTPLUG/1'],
191
203
  model: 'CCT711119',
@@ -630,4 +642,35 @@ module.exports = [
630
642
  await reporting.currentSummDelivered(endpoint, {min: 60, change: 1});
631
643
  },
632
644
  },
645
+ {
646
+ zigbeeModel: ['NHMOTION/SWITCH/1'],
647
+ model: '545D6306',
648
+ vendor: 'Schneider Electric',
649
+ description: 'LK FUGA Wiser wireless PIR with relay',
650
+ fromZigbee: [fz.on_off, fz.illuminance, fz.occupancy, fz.occupancy_timeout],
651
+ exposes: [e.switch().withEndpoint('l1'), e.occupancy(), e.illuminance_lux(), e.illuminance(),
652
+ exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(3600)
653
+ .withDescription('Time in seconds after which occupancy is cleared after detecting it')],
654
+ toZigbee: [tz.on_off, tz.occupancy_timeout],
655
+ endpoint: (device) => {
656
+ return {'default': 37, 'l1': 1, 'l2': 37};
657
+ },
658
+ meta: {multiEndpoint: true},
659
+ configure: async (device, coordinatorEndpoint, logger) => {
660
+ const endpoint1 = device.getEndpoint(1);
661
+ const binds1 = ['genBasic', 'genIdentify', 'genOnOff'];
662
+ await reporting.bind(endpoint1, coordinatorEndpoint, binds1);
663
+ await reporting.onOff(endpoint1);
664
+ // read switch state
665
+ await endpoint1.read('genOnOff', ['onOff']);
666
+
667
+ const endpoint37 = device.getEndpoint(37);
668
+ const binds37 = ['msIlluminanceMeasurement', 'msOccupancySensing'];
669
+ await reporting.bind(endpoint37, coordinatorEndpoint, binds37);
670
+ await reporting.occupancy(endpoint37);
671
+ await reporting.illuminance(endpoint37);
672
+ // read occupancy_timeout
673
+ await endpoint37.read('msOccupancySensing', ['pirOToUDelay']);
674
+ },
675
+ },
633
676
  ];
package/devices/sprut.js CHANGED
@@ -2,14 +2,18 @@ const exposes = require('../lib/exposes');
2
2
  const fz = require('../converters/fromZigbee');
3
3
  const tz = require('../converters/toZigbee');
4
4
  const reporting = require('../lib/reporting');
5
- const e = exposes.presets;
6
- const {calibrateAndPrecisionRoundOptions} = require('../lib/utils');
5
+ const ota = require('../lib/ota');
6
+ const e = exposes;
7
+ const ep = exposes.presets;
8
+ const eo = exposes.options;
9
+ const ea = exposes.access;
10
+ const {calibrateAndPrecisionRoundOptions, getOptions} = require('../lib/utils');
7
11
 
8
12
  const fzLocal = {
9
13
  temperature: {
10
14
  cluster: 'msTemperatureMeasurement',
11
15
  type: ['attributeReport', 'readResponse'],
12
- options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature')],
16
+ options: [eo.precision('temperature'), eo.calibration('temperature')],
13
17
  convert: (model, msg, publish, options, meta) => {
14
18
  const temperature = parseFloat(msg.data['measuredValue']) / 100.0;
15
19
  return {temperature: calibrateAndPrecisionRoundOptions(temperature, options, 'temperature')};
@@ -46,11 +50,78 @@ const fzLocal = {
46
50
  cluster: 'sprutNoise',
47
51
  type: ['readResponse', 'attributeReport'],
48
52
  convert: (model, msg, publish, options, meta) => {
49
- if (msg.data.hasOwnProperty('noise_detected')) {
50
- return {noise_detected: msg.data['noise_detected'] === 1};
53
+ if (msg.data.hasOwnProperty('noiseDetected')) {
54
+ return {noise_detected: msg.data['noiseDetected'] === 1};
51
55
  }
52
56
  },
53
57
  },
58
+ occupancy_timeout: {
59
+ cluster: 'msOccupancySensing',
60
+ type: ['readResponse', 'attributeReport'],
61
+ convert: (model, msg, publish, options, meta) => {
62
+ return {occupancy_timeout: msg.data.pirOToUDelay};
63
+ },
64
+ },
65
+ noise_timeout: {
66
+ cluster: 'sprutNoise',
67
+ type: ['readResponse', 'attributeReport'],
68
+ convert: (model, msg, publish, options, meta) => {
69
+ return {noise_timeout: msg.data.noiseAfterDetectDelay};
70
+ },
71
+ },
72
+ };
73
+
74
+ const tzLocal = {
75
+ sprut_ir_remote: {
76
+ key: ['play_store', 'learn_start', 'learn_stop'],
77
+ convertSet: async (entity, key, value, meta) => {
78
+ const options = {
79
+ frameType: 0, manufacturerCode: 26214, disableDefaultResponse: true,
80
+ disableResponse: true, reservedBits: 0, direction: 0, writeUndiv: false,
81
+ transactionSequenceNumber: null,
82
+ };
83
+ switch (key) {
84
+ case 'play_store':
85
+ await entity.command('sprutIrBlaster', 'playStore',
86
+ {param: value['rom']}, options);
87
+ break;
88
+ case 'learn_start':
89
+ await entity.command('sprutIrBlaster', 'learnStart',
90
+ {value: value['rom']}, options);
91
+ break;
92
+ case 'learn_stop':
93
+ await entity.command('sprutIrBlaster', 'learnStop',
94
+ {value: value['rom']}, options);
95
+ break;
96
+ }
97
+ },
98
+ },
99
+ occupancy_timeout: {
100
+ key: ['occupancy_timeout'],
101
+ convertSet: async (entity, key, value, meta) => {
102
+ value *= 1;
103
+ const endpoint = meta.device.getEndpoint(1);
104
+ await endpoint.write('msOccupancySensing', {pirOToUDelay: value}, getOptions(meta.mapped, entity));
105
+ return {state: {occupancy_timeout: value}};
106
+ },
107
+ convertGet: async (entity, key, meta) => {
108
+ const endpoint = meta.device.getEndpoint(1);
109
+ await endpoint.read('msOccupancySensing', ['pirOToUDelay']);
110
+ },
111
+ },
112
+ noise_timeout: {
113
+ key: ['noise_timeout'],
114
+ convertSet: async (entity, key, value, meta) => {
115
+ value *= 1;
116
+ const endpoint = meta.device.getEndpoint(1);
117
+ await endpoint.write('sprutNoise', {noiseAfterDetectDelay: value}, getOptions(meta.mapped, entity));
118
+ return {state: {noise_timeout: value}};
119
+ },
120
+ convertGet: async (entity, key, meta) => {
121
+ const endpoint = meta.device.getEndpoint(1);
122
+ await endpoint.read('sprutNoise', ['noiseAfterDetectDelay']);
123
+ },
124
+ },
54
125
  };
55
126
 
56
127
  module.exports = [
@@ -60,15 +131,19 @@ module.exports = [
60
131
  vendor: 'Sprut.device',
61
132
  description: 'Wall-mounted Zigbee sensor',
62
133
  fromZigbee: [fzLocal.temperature, fz.illuminance, fz.humidity, fz.occupancy, fzLocal.occupancy, fz.co2, fzLocal.voc,
63
- fzLocal.noise, fzLocal.noise_detected, fz.on_off],
64
- toZigbee: [tz.on_off],
65
- exposes: [e.temperature(), e.illuminance(), e.illuminance_lux(), e.humidity(),
66
- e.occupancy(), e.occupancy_level(), e.co2(), e.voc(), e.noise(), e.noise_detected(), e.switch().withEndpoint('l1'),
67
- e.switch().withEndpoint('l2'), e.switch().withEndpoint('default')],
134
+ fzLocal.noise, fzLocal.noise_detected, fz.on_off, fzLocal.occupancy_timeout, fzLocal.noise_timeout],
135
+ toZigbee: [tz.on_off, tzLocal.sprut_ir_remote, tzLocal.occupancy_timeout, tzLocal.noise_timeout],
136
+ exposes: [ep.temperature(), ep.illuminance(), ep.illuminance_lux(), ep.humidity(),
137
+ ep.occupancy(), ep.occupancy_level(), ep.co2(), ep.voc(), ep.noise(), ep.noise_detected(ea.STATE_GET),
138
+ ep.switch().withEndpoint('l1'), ep.switch().withEndpoint('l2'), ep.switch().withEndpoint('default'),
139
+ e.numeric('noise_timeout', ea.ALL).withValueMin(0).withValueMax(2000).withUnit('s')
140
+ .withDescription('Time in seconds after which noise is cleared after detecting it (default: 30)'),
141
+ e.numeric('occupancy_timeout', ea.ALL).withValueMin(0).withValueMax(2000).withUnit('s')
142
+ .withDescription('Time in seconds after which occupancy is cleared after detecting it (default: 30)')],
68
143
  configure: async (device, coordinatorEndpoint, logger) => {
69
144
  const endpoint1 = device.getEndpoint(1);
70
145
  const binds = ['genBasic', 'msTemperatureMeasurement', 'msIlluminanceMeasurement', 'msRelativeHumidity',
71
- 'msOccupancySensing', 'msCO2', 'sprutVoc', 'sprutNoise'];
146
+ 'msOccupancySensing', 'msCO2', 'sprutVoc', 'sprutNoise', 'sprutIrBlaster', 'genOta'];
72
147
  await reporting.bind(endpoint1, coordinatorEndpoint, binds);
73
148
 
74
149
  // led_red
@@ -80,17 +155,22 @@ module.exports = [
80
155
  // buzzer
81
156
  await device.getEndpoint(4).read('genOnOff', ['onOff']);
82
157
 
158
+ // Read settings at start
159
+ await endpoint1.read('msOccupancySensing', ['pirOToUDelay']);
160
+ await endpoint1.read('sprutNoise', ['noiseAfterDetectDelay']);
161
+
83
162
  // Read data at start
84
163
  await endpoint1.read('msTemperatureMeasurement', ['measuredValue']);
85
164
  await endpoint1.read('msIlluminanceMeasurement', ['measuredValue']);
86
165
  await endpoint1.read('msRelativeHumidity', ['measuredValue']);
87
166
  await endpoint1.read('msOccupancySensing', ['occupancy']);
88
167
  await endpoint1.read('sprutNoise', ['noise']);
89
- await endpoint1.read('sprutNoise', ['noise_detected']);
168
+ await endpoint1.read('sprutNoise', ['noiseDetected']);
90
169
  },
91
170
  endpoint: (device) => {
92
171
  return {'system': 1, 'l1': 2, 'l2': 3, 'default': 4};
93
172
  },
94
173
  meta: {multiEndpoint: true},
174
+ ota: ota.zigbeeOTA,
95
175
  },
96
176
  ];
@@ -260,7 +260,8 @@ module.exports = [
260
260
  exposes: [e.action(['press_on', 'press_off', 'hold_on', 'hold_off', 'release'])],
261
261
  },
262
262
  {
263
- fingerprint: [{modelID: 'GreenPower_2', ieeeAddr: /^0x000000005d5.....$/}],
263
+ fingerprint: [{modelID: 'GreenPower_2', ieeeAddr: /^0x000000005d5.....$/},
264
+ {modelID: 'GreenPower_2', ieeeAddr: /^0x0000000057e.....$/}],
264
265
  model: 'SR-ZGP2801K4-DIM',
265
266
  vendor: 'Sunricher',
266
267
  description: 'Pushbutton transmitter module',
package/devices/tuya.js CHANGED
@@ -89,6 +89,15 @@ module.exports = [
89
89
  toZigbee: [],
90
90
  exposes: [e.temperature(), e.humidity(), e.co2(), e.voc(), e.formaldehyd()],
91
91
  },
92
+ {
93
+ fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_dwcarsat'}],
94
+ model: 'TS0601_smart_air_house_keeper',
95
+ vendor: 'TuYa',
96
+ description: 'Smart air house keeper',
97
+ fromZigbee: [fz.tuya_air_quality],
98
+ toZigbee: [],
99
+ exposes: [e.temperature(), e.humidity(), e.co2(), e.voc(), e.formaldehyd(), e.pm25()],
100
+ },
92
101
  {
93
102
  fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_ggev5fsl'}],
94
103
  model: 'TS0601_gas_sensor',
@@ -201,6 +210,13 @@ module.exports = [
201
210
  toZigbee: utils.replaceInArray(extend.light_onoff_brightness_color().toZigbee, [tz.light_color], [tzLocal.TS0504B_color]),
202
211
  meta: {applyRedFix: true},
203
212
  },
213
+ {
214
+ fingerprint: [{modelID: 'TS0501A', manufacturerName: '_TZ3000_yeg1e5eh'}],
215
+ model: 'TS0501A',
216
+ description: 'Zigbee light',
217
+ vendor: 'TuYa',
218
+ extend: extend.light_onoff_brightness(),
219
+ },
204
220
  {
205
221
  fingerprint: [{modelID: 'TS0501B', manufacturerName: '_TZ3000_4whigl8i'},
206
222
  {modelID: 'TS0501B', manufacturerName: '_TZ3210_4whigl8i'},
@@ -223,9 +239,7 @@ module.exports = [
223
239
  {modelID: 'TS0202', manufacturerName: '_TZ3000_mmtwjmaq'},
224
240
  {modelID: 'TS0202', manufacturerName: '_TZ3000_kmh5qpmb'},
225
241
  {modelID: 'TS0202', manufacturerName: '_TYZB01_zwvaj5wy'},
226
- {modelID: 'TS0202', manufacturerName: '_TZ3000_mcxw5ehu'},
227
242
  {modelID: 'TS0202', manufacturerName: '_TZ3000_bsvqrxru'},
228
- {modelID: 'TS0202', manufacturerName: '_TZ3000_msl6wxk9'},
229
243
  {modelID: 'TS0202', manufacturerName: '_TYZB01_tv3wxhcz'},
230
244
  {modelID: 'TS0202', manufacturerName: '_TYZB01_hqbdru35'},
231
245
  {modelID: 'TS0202', manufacturerName: '_TZ3000_tiwq83wk'},
@@ -238,6 +252,19 @@ module.exports = [
238
252
  toZigbee: [],
239
253
  exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery()],
240
254
  },
255
+ {
256
+ fingerprint: [{modelID: 'TS0202', manufacturerName: '_TZ3000_mcxw5ehu'},
257
+ {modelID: 'TS0202', manufacturerName: '_TZ3000_msl6wxk9'}],
258
+ model: 'ZM-35H-Q',
259
+ vendor: 'TuYa',
260
+ description: 'Motion Sensor',
261
+ fromZigbee: [fz.ias_occupancy_alarm_1, fz.battery, fz.ignore_basic_report, fz.ZM35HQ_attr],
262
+ toZigbee: [tz.ZM35HQ_attr],
263
+ exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.battery(),
264
+ exposes.enum('sensitivity', ea.ALL, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'),
265
+ exposes.enum('keep_time', ea.ALL, ['30', '60', '120']).withDescription('PIR keep time in seconds'),
266
+ ],
267
+ },
241
268
  {
242
269
  fingerprint: [{modelID: 'TS0207', manufacturerName: '_TZ3000_m0vaazab'},
243
270
  {modelID: 'TS0207', manufacturerName: '_TZ3000_ufttklsz'},
@@ -319,8 +346,7 @@ module.exports = [
319
346
  ],
320
347
  },
321
348
  {
322
- fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_oiymh3qu'},
323
- {modelID: 'TS011F', manufacturerName: '_TZ3000_o1jzcxou'}],
349
+ fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_oiymh3qu'}],
324
350
  model: 'TS011F_socket_module',
325
351
  vendor: 'TuYa',
326
352
  description: 'Socket module',
@@ -329,12 +355,15 @@ module.exports = [
329
355
  },
330
356
  {
331
357
  fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_wxtp7c5y'},
332
- {modelID: 'TS011F', manufacturerName: '_TYZB01_mtunwanm'}],
358
+ {modelID: 'TS011F', manufacturerName: '_TYZB01_mtunwanm'},
359
+ {modelID: 'TS011F', manufacturerName: '_TZ3000_o1jzcxou'}],
333
360
  model: 'TS011F_wall_outlet',
334
361
  vendor: 'TuYa',
335
362
  description: 'In-wall outlet',
336
363
  extend: extend.switch(),
337
- whiteLabel: [{vendor: 'Teekar', model: 'SWP86-01OG'}, {vendor: 'ClickSmart+', model: 'CMA30035'}],
364
+ whiteLabel: [{vendor: 'Teekar', model: 'SWP86-01OG'},
365
+ {vendor: 'ClickSmart+', model: 'CMA30035'},
366
+ {vendor: 'BSEED', model: 'Zigbee Socket'}],
338
367
  },
339
368
  {
340
369
  fingerprint: [{modelID: 'isltm67\u0000', manufacturerName: '_TYST11_pisltm67'}],
@@ -848,6 +877,7 @@ module.exports = [
848
877
  {modelID: 'TS0601', manufacturerName: '_TZE200_fzo2pocs'},
849
878
  {modelID: 'TS0601', manufacturerName: '_TZE200_5sbebbzs'},
850
879
  {modelID: 'TS0601', manufacturerName: '_TZE200_zuz7f94z'},
880
+ {modelID: 'TS0601', manufacturerName: '_TZE200_zyrdrmno'},
851
881
  ],
852
882
  model: 'TS0601_cover',
853
883
  vendor: 'TuYa',
@@ -867,6 +897,7 @@ module.exports = [
867
897
  {vendor: 'Zemismart', model: 'M2805EGBZTN', description: 'Tubular motor'},
868
898
  {vendor: 'Zemismart', model: 'BCM500DS-TYZ', description: 'Curtain motor'},
869
899
  {vendor: 'A-OK', model: 'AM25', description: 'Tubular motor'},
900
+ {vendor: 'Alutech', model: 'AM/R-Sm', description: 'Tubular motor'},
870
901
  ],
871
902
  fromZigbee: [fz.tuya_cover, fz.ignore_basic_report],
872
903
  toZigbee: [tz.tuya_cover_control, tz.tuya_cover_options],
@@ -1434,7 +1465,8 @@ module.exports = [
1434
1465
  {
1435
1466
  fingerprint: [{modelID: 'TS0014', manufacturerName: '_TZ3000_jr2atpww'}, {modelID: 'TS0014', manufacturerName: '_TYZB01_dvakyzhd'},
1436
1467
  {modelID: 'TS0014', manufacturerName: '_TZ3210_w3hl6rao'}, {modelID: 'TS0014', manufacturerName: '_TYZB01_bagt1e4o'},
1437
- {modelID: 'TS0014', manufacturerName: '_TZ3000_r0pmi2p3'}, {modelID: 'TS0014', manufacturerName: '_TZ3000_fxjdcikv'}],
1468
+ {modelID: 'TS0014', manufacturerName: '_TZ3000_r0pmi2p3'}, {modelID: 'TS0014', manufacturerName: '_TZ3000_fxjdcikv'},
1469
+ {modelID: 'TS0014', manufacturerName: '_TZ3000_q6vxaod1'}],
1438
1470
  model: 'TS0014',
1439
1471
  vendor: 'TuYa',
1440
1472
  description: 'Smart light switch - 4 gang without neutral wire',
@@ -1580,6 +1612,7 @@ module.exports = [
1580
1612
  vendor: 'TuYa',
1581
1613
  description: 'Multiprise with 4 AC outlets and 2 USB super charging ports (16A)',
1582
1614
  extend: extend.switch(),
1615
+ fromZigbee: [fz.on_off_skip_duplicate_transaction],
1583
1616
  exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.switch().withEndpoint('l3'),
1584
1617
  e.switch().withEndpoint('l4')],
1585
1618
  whiteLabel: [{vendor: 'LEELKI', model: 'WP33-EU'}],
package/devices/xiaomi.js CHANGED
@@ -736,9 +736,6 @@ module.exports = [
736
736
  exposes.enum('operation_mode', ea.ALL, ['control_relay', 'decoupled'])
737
737
  .withDescription('Decoupled mode for left button'),
738
738
  ],
739
- configure: async (device, coordinatorEndpoint, logger) => {
740
- await device.getEndpoint(1).write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true});
741
- },
742
739
  onEvent: preventReset,
743
740
  ota: ota.zigbeeOTA,
744
741
  },
@@ -767,9 +764,6 @@ module.exports = [
767
764
  .withDescription('Decoupled mode for right button')
768
765
  .withEndpoint('right'),
769
766
  ],
770
- configure: async (device, coordinatorEndpoint, logger) => {
771
- await device.getEndpoint(1).write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true});
772
- },
773
767
  onEvent: preventReset,
774
768
  ota: ota.zigbeeOTA,
775
769
  },
@@ -804,10 +798,8 @@ module.exports = [
804
798
  .withDescription('Decoupled mode for right button')
805
799
  .withEndpoint('right'),
806
800
  ],
807
- configure: async (device, coordinatorEndpoint, logger) => {
808
- await device.getEndpoint(1).write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true});
809
- },
810
801
  onEvent: preventReset,
802
+ ota: ota.zigbeeOTA,
811
803
  },
812
804
  {
813
805
  zigbeeModel: ['lumi.sens', 'lumi.sensor_ht'],
@@ -885,6 +877,7 @@ module.exports = [
885
877
  await endpoint.read('genPowerCfg', ['batteryVoltage']);
886
878
  await endpoint.read('aqaraOpple', [0x0102], {manufacturerCode: 0x115f});
887
879
  },
880
+ ota: ota.zigbeeOTA,
888
881
  },
889
882
  {
890
883
  zigbeeModel: ['lumi.motion.agl04'],
@@ -903,6 +896,7 @@ module.exports = [
903
896
  await endpoint.read('aqaraOpple', [0x0102], {manufacturerCode: 0x115f});
904
897
  await endpoint.read('aqaraOpple', [0x010c], {manufacturerCode: 0x115f});
905
898
  },
899
+ ota: ota.zigbeeOTA,
906
900
  },
907
901
  {
908
902
  zigbeeModel: ['lumi.sensor_magnet'],
@@ -995,7 +989,7 @@ module.exports = [
995
989
  e.voltage().withAccess(ea.STATE), e.current(), e.consumer_connected(), e.led_disabled_night(),
996
990
  e.power_outage_memory(), exposes.binary('auto_off', ea.STATE_SET, true, false)
997
991
  .withDescription('Turn the device automatically off when attached device consumes less than 2W for 20 minutes'),
998
- exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2200).withUnit('W')
992
+ exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2300).withUnit('W')
999
993
  .withDescription('Maximum allowed load, turns off if exceeded')],
1000
994
  ota: ota.zigbeeOTA,
1001
995
  },
@@ -1689,7 +1683,7 @@ module.exports = [
1689
1683
  zigbeeModel: ['lumi.plug.macn01'],
1690
1684
  model: 'ZNCZ15LM',
1691
1685
  vendor: 'Xiaomi',
1692
- description: 'Aqara T1 power plug ZigBee',
1686
+ description: 'Aqara smart plug T1 (china standard)',
1693
1687
  fromZigbee: [fz.on_off, fz.xiaomi_power, fz.aqara_opple],
1694
1688
  toZigbee: [tz.on_off, tz.xiaomi_switch_power_outage_memory, tz.xiaomi_led_disabled_night,
1695
1689
  tz.xiaomi_overload_protection, tz.xiaomi_socket_button_lock],
@@ -1698,9 +1692,6 @@ module.exports = [
1698
1692
  e.power_outage_memory(), e.led_disabled_night(), e.button_lock(),
1699
1693
  exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2500).withUnit('W')
1700
1694
  .withDescription('Maximum allowed load, turns off if exceeded')],
1701
- configure: async (device, coordinatorEndpoint, logger) => {
1702
- await device.getEndpoint(1).write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true});
1703
- },
1704
1695
  ota: ota.zigbeeOTA,
1705
1696
  },
1706
1697
  {
@@ -1753,7 +1744,7 @@ module.exports = [
1753
1744
  zigbeeModel: ['lumi.plug.sacn03'],
1754
1745
  model: 'QBCZ15LM',
1755
1746
  vendor: 'Xiaomi',
1756
- description: 'Aqara smart wall outlet H1 usb',
1747
+ description: 'Aqara smart wall outlet H1 (USB)',
1757
1748
  fromZigbee: [fz.on_off, fz.xiaomi_power, fz.aqara_opple],
1758
1749
  toZigbee: [tz.on_off, tz.xiaomi_switch_power_outage_memory, tz.xiaomi_led_disabled_night,
1759
1750
  tz.xiaomi_button_switch_mode, tz.xiaomi_overload_protection, tz.xiaomi_socket_button_lock],
@@ -1769,9 +1760,6 @@ module.exports = [
1769
1760
  .withDescription('Control both relay and usb or only the relay with the physical switch button'),
1770
1761
  exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2500).withUnit('W')
1771
1762
  .withDescription('Maximum allowed load, turns off if exceeded')],
1772
- configure: async (device, coordinatorEndpoint, logger) => {
1773
- await device.getEndpoint(1).write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true});
1774
- },
1775
1763
  ota: ota.zigbeeOTA,
1776
1764
  },
1777
1765
  {
@@ -1803,9 +1791,6 @@ module.exports = [
1803
1791
  e.current(), e.power_outage_memory(), e.led_disabled_night(), e.button_lock(),
1804
1792
  exposes.numeric('overload_protection', exposes.access.ALL).withValueMin(100).withValueMax(2500).withUnit('W')
1805
1793
  .withDescription('Maximum allowed load, turns off if exceeded')],
1806
- configure: async (device, coordinatorEndpoint, logger) => {
1807
- await device.getEndpoint(1).write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true});
1808
- },
1809
1794
  ota: ota.zigbeeOTA,
1810
1795
  },
1811
1796
  {
@@ -1871,7 +1856,7 @@ module.exports = [
1871
1856
  model: 'WXKG14LM',
1872
1857
  vendor: 'Xiaomi',
1873
1858
  description: 'Aqara wireless remote switch H1 (single rocker)',
1874
- fromZigbee: [fz.xiaomi_multistate_action, fz.aqara_opple],
1859
+ fromZigbee: [fz.xiaomi_multistate_action, fz.aqara_opple, fz.command_toggle],
1875
1860
  toZigbee: [tz.xiaomi_switch_click_mode, tz.aqara_opple_operation_mode],
1876
1861
  exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'triple', 'hold']),
1877
1862
  exposes.enum('click_mode', ea.ALL, ['fast', 'multi'])
package/lib/tuya.js CHANGED
@@ -418,6 +418,9 @@ const dataPoints = {
418
418
  tuyaSabHumidity: 19,
419
419
  tuyaSabVOC: 21,
420
420
  tuyaSabFormaldehyd: 22,
421
+ // tuya Smart Air House Keeper, Multifunctionale air quality detector.
422
+ // CO2, Temp, Humidity, VOC and Formaldehyd same as Smart Air Box
423
+ tuyaSahkMP25: 20,
421
424
  lidlTimer: 5,
422
425
  // Moes MS-105 Dimmer
423
426
  moes105DimmerState1: 1,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.396",
3
+ "version": "14.0.400",
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.396",
3
+ "version": "14.0.400",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [