zigbee-herdsman-converters 14.0.646 → 14.0.647

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.
@@ -4090,160 +4090,6 @@ const converters = {
4090
4090
  }
4091
4091
  },
4092
4092
  },
4093
- tvtwo_thermostat: {
4094
- cluster: 'manuSpecificTuya',
4095
- type: ['commandDataResponse', 'commandDataReport'],
4096
- convert: (model, msg, publish, options, meta) => {
4097
- const dpValue = tuya.firstDpValue(msg, meta, 'tvtwo_thermostat');
4098
- const dp = dpValue.dp;
4099
- const value = tuya.getDataValue(dpValue);
4100
- const presetLookup = {0: 'auto', 1: 'manual', 2: 'holiday'};
4101
- switch (dp) {
4102
- case tuya.dataPoints.tvMode:
4103
- return {preset: presetLookup[value]};
4104
- case tuya.dataPoints.tvChildLock:
4105
- return {child_lock: value ? 'LOCK' : 'UNLOCK'};
4106
- case tuya.dataPoints.tvHolidayTemp:
4107
- return {holiday_temperature: (value / 10).toFixed(1)};
4108
- case tuya.dataPoints.tvHeatingSetpoint:
4109
- return {current_heating_setpoint: (value / 10).toFixed(1)};
4110
- case tuya.dataPoints.tvFrostDetection:
4111
- return {frost_protection: value ? 'ON' : 'OFF'};
4112
- case tuya.dataPoints.tvWindowDetection:
4113
- return {open_window: value ? 'ON' : 'OFF'};
4114
- case tuya.dataPoints.tvHeatingStop:
4115
- return {heating_stop: value ? 'ON' : 'OFF'};
4116
- case tuya.dataPoints.tvLocalTemp:
4117
- return {local_temperature: (value / 10).toFixed(1)};
4118
- case tuya.dataPoints.tvBattery:
4119
- return {battery_low: value === 0 ? true : false};
4120
- case tuya.dataPoints.tvTempCalibration:
4121
- return {local_temperature_calibration: value > 55 ?
4122
- ((value - 0x100000000)/10).toFixed(1): (value/ 10).toFixed(1)};
4123
- case tuya.dataPoints.tvBoostTime:
4124
- // Setting minimum 0 - maximum 465 seconds
4125
- return {boost_timeset_countdown: value};
4126
- case tuya.dataPoints.tvComfortTemp:
4127
- return {comfort_temperature: (value / 10).toFixed(1)};
4128
- case tuya.dataPoints.tvEcoTemp:
4129
- return {eco_temperature: (value / 10).toFixed(1)};
4130
- case tuya.dataPoints.tvOpenWindowTemp:
4131
- return {open_window_temperature: (value / 10).toFixed(1)};
4132
- case tuya.dataPoints.tvErrorStatus:
4133
- return {fault_alarm: value};
4134
- case tuya.dataPoints.tvHolidayMode: {
4135
- const sy = value.slice(0, 4); const sm = value.slice(4, 6); const sd = value.slice(6, 8);
4136
- const sh = value.slice(8, 10); const smi = value.slice(10, 12); const ey = value.slice(12, 16);
4137
- const em = value.slice(16, 18); const ed = value.slice(18, 20); const eh = value.slice(20, 22);
4138
- const emi = value.slice(22, 24);
4139
- const hMode = 'start --> ' + sy + ' - ' + sm + ' - ' + sd + ' ' + sh + ' : ' + smi +
4140
- ' stop --> ' + ey + ' - ' + em + ' - ' + ed + ' ' + eh + ' : ' + emi;
4141
- return {holiday_start_stop: hMode};
4142
- }
4143
- case tuya.dataPoints.tvBoostMode:
4144
- // 115 online / Is the device online
4145
- return {online: value ? 'ON' : 'OFF'};
4146
- case tuya.dataPoints.tvWorkingDay:
4147
- // DP-31, Send and Report, ENUM, Week select 0 - 5 days, 1 - 6 days, 2 - 7 days
4148
- return {working_day: value};
4149
- case tuya.dataPoints.tvMondaySchedule:
4150
- return {schedule_monday:
4151
- ' ' + value[0] / 6 + 'h:' + value[1] + 'm ' + value[2] / 10 + '°C' +
4152
- ', ' + value[3] / 6 + 'h:' + value[4] + 'm ' + value[5] / 10 + '°C' +
4153
- ', ' + value[6] / 6 + 'h:' + value[7] + 'm ' + value[8] / 10 + '°C' +
4154
- ', ' + value[9] / 6 + 'h:' + value[10] + 'm ' + value[11] / 10 + '°C' +
4155
- ', ' + value[12] / 6 + 'h:' + value[13] + 'm ' + value[14] / 10 + '°C ' +
4156
- ', ' + value[15] / 6 + 'h:' + value[16] + 'm ' + value[17] / 10 + '°C ' +
4157
- ', ' + value[18] / 6 + 'h:' + value[19] + 'm ' + value[20] / 10 + '°C ' +
4158
- ', ' + value[21] / 6 + 'h:' + value[22] + 'm ' + value[23] / 10 + '°C ' +
4159
- ', ' + value[24] / 6 + 'h:' + value[25] + 'm ' + value[26] / 10 + '°C ' +
4160
- ', ' + value[27] / 6 + 'h:' + value[28] + 'm ' + value[29] / 10 + '°C ',
4161
- };
4162
- case tuya.dataPoints.tvTuesdaySchedule:
4163
- return {schedule_tuesday:
4164
- ' ' + value[0] / 6 + 'h:' + value[1] + 'm ' + value[2] / 10 + '°C' +
4165
- ', ' + value[3] / 6 + 'h:' + value[4] + 'm ' + value[5] / 10 + '°C' +
4166
- ', ' + value[6] / 6 + 'h:' + value[7] + 'm ' + value[8] / 10 + '°C' +
4167
- ', ' + value[9] / 6 + 'h:' + value[10] + 'm ' + value[11] / 10 + '°C' +
4168
- ', ' + value[12] / 6 + 'h:' + value[13] + 'm ' + value[14] / 10 + '°C ' +
4169
- ', ' + value[15] / 6 + 'h:' + value[16] + 'm ' + value[17] / 10 + '°C ' +
4170
- ', ' + value[18] / 6 + 'h:' + value[19] + 'm ' + value[20] / 10 + '°C ' +
4171
- ', ' + value[21] / 6 + 'h:' + value[22] + 'm ' + value[23] / 10 + '°C ' +
4172
- ', ' + value[24] / 6 + 'h:' + value[25] + 'm ' + value[26] / 10 + '°C ' +
4173
- ', ' + value[27] / 6 + 'h:' + value[28] + 'm ' + value[29] / 10 + '°C ',
4174
- };
4175
- case tuya.dataPoints.tvWednesdaySchedule:
4176
- return {schedule_wednesday:
4177
- ' ' + value[0] / 6 + 'h:' + value[1] + 'm ' + value[2] / 10 + '°C' +
4178
- ', ' + value[3] / 6 + 'h:' + value[4] + 'm ' + value[5] / 10 + '°C' +
4179
- ', ' + value[6] / 6 + 'h:' + value[7] + 'm ' + value[8] / 10 + '°C' +
4180
- ', ' + value[9] / 6 + 'h:' + value[10] + 'm ' + value[11] / 10 + '°C' +
4181
- ', ' + value[12] / 6 + 'h:' + value[13] + 'm ' + value[14] / 10 + '°C ' +
4182
- ', ' + value[15] / 6 + 'h:' + value[16] + 'm ' + value[17] / 10 + '°C ' +
4183
- ', ' + value[18] / 6 + 'h:' + value[19] + 'm ' + value[20] / 10 + '°C ' +
4184
- ', ' + value[21] / 6 + 'h:' + value[22] + 'm ' + value[23] / 10 + '°C ' +
4185
- ', ' + value[24] / 6 + 'h:' + value[25] + 'm ' + value[26] / 10 + '°C ' +
4186
- ', ' + value[27] / 6 + 'h:' + value[28] + 'm ' + value[29] / 10 + '°C ',
4187
- };
4188
- case tuya.dataPoints.tvThursdaySchedule:
4189
- return {schedule_thursday:
4190
- ' ' + value[0] / 6 + 'h:' + value[1] + 'm ' + value[2] / 10 + '°C' +
4191
- ', ' + value[3] / 6 + 'h:' + value[4] + 'm ' + value[5] / 10 + '°C' +
4192
- ', ' + value[6] / 6 + 'h:' + value[7] + 'm ' + value[8] / 10 + '°C' +
4193
- ', ' + value[9] / 6 + 'h:' + value[10] + 'm ' + value[11] / 10 + '°C' +
4194
- ', ' + value[12] / 6 + 'h:' + value[13] + 'm ' + value[14] / 10 + '°C ' +
4195
- ', ' + value[15] / 6 + 'h:' + value[16] + 'm ' + value[17] / 10 + '°C ' +
4196
- ', ' + value[18] / 6 + 'h:' + value[19] + 'm ' + value[20] / 10 + '°C ' +
4197
- ', ' + value[21] / 6 + 'h:' + value[22] + 'm ' + value[23] / 10 + '°C ' +
4198
- ', ' + value[24] / 6 + 'h:' + value[25] + 'm ' + value[26] / 10 + '°C ' +
4199
- ', ' + value[27] / 6 + 'h:' + value[28] + 'm ' + value[29] / 10 + '°C ',
4200
- };
4201
- case tuya.dataPoints.tvFridaySchedule:
4202
- return {schedule_friday:
4203
- ' ' + value[0] / 6 + 'h:' + value[1] + 'm ' + value[2] / 10 + '°C' +
4204
- ', ' + value[3] / 6 + 'h:' + value[4] + 'm ' + value[5] / 10 + '°C' +
4205
- ', ' + value[6] / 6 + 'h:' + value[7] + 'm ' + value[8] / 10 + '°C' +
4206
- ', ' + value[9] / 6 + 'h:' + value[10] + 'm ' + value[11] / 10 + '°C' +
4207
- ', ' + value[12] / 6 + 'h:' + value[13] + 'm ' + value[14] / 10 + '°C ' +
4208
- ', ' + value[15] / 6 + 'h:' + value[16] + 'm ' + value[17] / 10 + '°C ' +
4209
- ', ' + value[18] / 6 + 'h:' + value[19] + 'm ' + value[20] / 10 + '°C ' +
4210
- ', ' + value[21] / 6 + 'h:' + value[22] + 'm ' + value[23] / 10 + '°C ' +
4211
- ', ' + value[24] / 6 + 'h:' + value[25] + 'm ' + value[26] / 10 + '°C ' +
4212
- ', ' + value[27] / 6 + 'h:' + value[28] + 'm ' + value[29] / 10 + '°C ',
4213
- };
4214
- case tuya.dataPoints.tvSaturdaySchedule:
4215
- return {schedule_saturday:
4216
- ' ' + value[0] / 6 + 'h:' + value[1] + 'm ' + value[2] / 10 + '°C' +
4217
- ', ' + value[3] / 6 + 'h:' + value[4] + 'm ' + value[5] / 10 + '°C' +
4218
- ', ' + value[6] / 6 + 'h:' + value[7] + 'm ' + value[8] / 10 + '°C' +
4219
- ', ' + value[9] / 6 + 'h:' + value[10] + 'm ' + value[11] / 10 + '°C' +
4220
- ', ' + value[12] / 6 + 'h:' + value[13] + 'm ' + value[14] / 10 + '°C ' +
4221
- ', ' + value[15] / 6 + 'h:' + value[16] + 'm ' + value[17] / 10 + '°C ' +
4222
- ', ' + value[18] / 6 + 'h:' + value[19] + 'm ' + value[20] / 10 + '°C ' +
4223
- ', ' + value[21] / 6 + 'h:' + value[22] + 'm ' + value[23] / 10 + '°C ' +
4224
- ', ' + value[24] / 6 + 'h:' + value[25] + 'm ' + value[26] / 10 + '°C ' +
4225
- ', ' + value[27] / 6 + 'h:' + value[28] + 'm ' + value[29] / 10 + '°C ',
4226
- };
4227
- case tuya.dataPoints.tvSundaySchedule:
4228
- return {schedule_sunday:
4229
- ' ' + value[0] / 6 + 'h:' + value[1] + 'm ' + value[2] / 10 + '°C' +
4230
- ', ' + value[3] / 6 + 'h:' + value[4] + 'm ' + value[5] / 10 + '°C' +
4231
- ', ' + value[6] / 6 + 'h:' + value[7] + 'm ' + value[8] / 10 + '°C' +
4232
- ', ' + value[9] / 6 + 'h:' + value[10] + 'm ' + value[11] / 10 + '°C' +
4233
- ', ' + value[12] / 6 + 'h:' + value[13] + 'm ' + value[14] / 10 + '°C ' +
4234
- ', ' + value[15] / 6 + 'h:' + value[16] + 'm ' + value[17] / 10 + '°C ' +
4235
- ', ' + value[18] / 6 + 'h:' + value[19] + 'm ' + value[20] / 10 + '°C ' +
4236
- ', ' + value[21] / 6 + 'h:' + value[22] + 'm ' + value[23] / 10 + '°C ' +
4237
- ', ' + value[24] / 6 + 'h:' + value[25] + 'm ' + value[26] / 10 + '°C ' +
4238
- ', ' + value[27] / 6 + 'h:' + value[28] + 'm ' + value[29] / 10 + '°C ',
4239
- };
4240
-
4241
- default:
4242
- meta.logger.warn(`zigbee-herdsman-converters:tvtwo_thermostat: NOT RECOGNIZED DP #${
4243
- dp} with data ${JSON.stringify(dpValue)}`);
4244
- }
4245
- },
4246
- },
4247
4093
  haozee_thermostat: {
4248
4094
  cluster: 'manuSpecificTuya',
4249
4095
  type: ['commandDataResponse', 'commandDataReport'],
@@ -3518,91 +3518,6 @@ const converters = {
3518
3518
  return tuya.sendDataPointRaw(entity, tuya.dataPoints.moesSschedule, payload);
3519
3519
  },
3520
3520
  },
3521
- tvtwo_thermostat: {
3522
- key: [
3523
- 'child_lock', 'open_window', 'open_window_temperature', 'frost_protection', 'heating_stop',
3524
- 'current_heating_setpoint', 'local_temperature_calibration', 'preset', 'boost_timeset_countdown',
3525
- 'holiday_start_stop', 'holiday_temperature', 'comfort_temperature', 'eco_temperature',
3526
- 'working_day', 'week_schedule_programming', 'online', 'holiday_mode_date',
3527
- ],
3528
- convertSet: async (entity, key, value, meta) => {
3529
- switch (key) {
3530
- case 'preset': {
3531
- const presetLookup = {'auto': 0, 'manual': 1, 'holiday': 3};
3532
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.tvMode, presetLookup[value]);
3533
- return {state: {preset: value}};}
3534
- case 'heating_stop':
3535
- if (value == 'ON') {
3536
- await tuya.sendDataPointBool(entity, tuya.dataPoints.tvHeatingStop, 1);
3537
- } else {
3538
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.tvMode, 1 /* manual */);
3539
- }
3540
- break;
3541
- case 'frost_protection':
3542
- if (value == 'ON') {
3543
- await tuya.sendDataPointBool(entity, tuya.dataPoints.tvFrostDetection, 1);
3544
- } else {
3545
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.tvMode, 1 /* manual */);
3546
- }
3547
- break;
3548
- case 'open_window':
3549
- await tuya.sendDataPointBool(entity, tuya.dataPoints.tvWindowDetection, value === 'ON');
3550
- break;
3551
- case 'child_lock':
3552
- await tuya.sendDataPointBool(entity, tuya.dataPoints.tvChildLock, value === 'LOCK');
3553
- break;
3554
- case 'local_temperature_calibration':
3555
- if (value > 0) value = value*10;
3556
- if (value < 0) value = value*10 + 0x100000000;
3557
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvTempCalibration, value);
3558
- break;
3559
- case 'current_heating_setpoint':
3560
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvHeatingSetpoint, value * 10);
3561
- await utils.sleep(500);
3562
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.tvMode, 1 /* manual */);
3563
- break;
3564
- case 'holiday_temperature':
3565
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvHolidayTemp, value * 10);
3566
- break;
3567
- case 'comfort_temperature':
3568
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvComfortTemp, value * 10);
3569
- break;
3570
- case 'eco_temperature':
3571
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvEcoTemp, value * 10);
3572
- break;
3573
- case 'boost_timeset_countdown':
3574
- // set min 0 - max 465 sec boost time
3575
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvBoostTime, value);
3576
- break;
3577
- case 'open_window_temperature':
3578
- await tuya.sendDataPointValue(entity, tuya.dataPoints.tvOpenWindowTemp, value * 10);
3579
- break;
3580
- case 'holiday_start_stop': {
3581
- const numberPattern = /\d+/g;
3582
- value = value.match(numberPattern).join([]).toString();
3583
- return tuya.sendDataPointStringBuffer(entity, tuya.dataPoints.tvHolidayMode, value);
3584
- }
3585
- case 'online':
3586
- // 115 online / Is the device online
3587
- await tuya.sendDataPointBool(entity, tuya.dataPoints.tvBoostMode, value === 'ON');
3588
- break;
3589
- case 'working_day': {
3590
- // DP-31, Send and Report, ENUM, Week select 0 - 5 days, 1 - 6 days, 2 - 7 days
3591
- const workLookup = {'0': 0, '1': 1, '2': 2, '3': 3};
3592
- const workDay = workLookup[value];
3593
- await tuya.sendDataPointEnum(entity, tuya.dataPoints.tvWorkingDay, workDay);
3594
- return {state: {working_day: value}};
3595
- }
3596
- case 'week_schedule_programming':
3597
- // DP-106, Send Only, raw, week_program3_day
3598
- await tuya.sendDataPointRaw(entity, tuya.dataPoints.tvWeekSchedule, value);
3599
- break;
3600
-
3601
- default: // Unknown key
3602
- meta.logger.warn(`toZigbee.tvtwo_thermostat: Unhandled key ${key}`);
3603
- }
3604
- },
3605
- },
3606
3521
  haozee_thermostat_system_mode: {
3607
3522
  key: ['preset'],
3608
3523
  convertSet: async (entity, key, value, meta) => {
package/devices/awox.js CHANGED
@@ -155,7 +155,7 @@ module.exports = [
155
155
  model: '33957',
156
156
  vendor: 'AwoX',
157
157
  description: 'LED light with color temperature',
158
- extend: extend.light_onoff_brightness_colortemp({colorTempRange: [153, 370]}),
158
+ extend: extend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
159
159
  whiteLabel: [{vendor: 'EGLO', model: '12239'}],
160
160
  },
161
161
  {
@@ -25,6 +25,33 @@ const develcoLedControlMap = {
25
25
  0xFF: 'both',
26
26
  };
27
27
 
28
+
29
+ const fwOnEvent = async (type, data, device, options, state) => {
30
+ /**
31
+ * Develco devices have a manufacturer specific field on genBasic
32
+ * for sw and hw versions.
33
+ *
34
+ * We read those during deviceInterview
35
+ * so that this information is usable during configure() calls
36
+ * to skip some features on older devices that might cause
37
+ * timeouts and other issues.
38
+ */
39
+ if (type === 'deviceInterview') { // WARN: also reading on deviceAnnounce hits a timeout!
40
+ try {
41
+ const data = await device.endpoints[0].read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'],
42
+ manufacturerOptions);
43
+
44
+ if (data.hasOwnProperty('develcoPrimarySwVersion')) {
45
+ device.softwareBuildID = data.develcoPrimarySwVersion.join('.');
46
+ }
47
+
48
+ if (data.hasOwnProperty('develcoPrimaryHwVersion')) {
49
+ device.hardwareVersion = data.develcoPrimaryHwVersion.join('.');
50
+ }
51
+ } catch (error) {/* catch timeouts of sleeping devices */}
52
+ }
53
+ };
54
+
28
55
  // develco specific convertors
29
56
  const develco = {
30
57
  fz: {
@@ -86,24 +113,6 @@ const develco = {
86
113
  return result;
87
114
  },
88
115
  },
89
- firmware_version: {
90
- cluster: 'genBasic',
91
- type: ['attributeReport', 'readResponse'],
92
- convert: (model, msg, publish, options, meta) => {
93
- const result = {};
94
- if (msg.data.hasOwnProperty('develcoPrimarySwVersion')) {
95
- const firmware = msg.data.develcoPrimarySwVersion.join('.');
96
- result.current_firmware = firmware;
97
- meta.device.softwareBuildID = firmware;
98
- }
99
-
100
- if (msg.data.hasOwnProperty('develcoPrimaryHwVersion')) {
101
- meta.device.hardwareVersion = msg.data.develcoPrimaryHwVersion.join('.');
102
- }
103
-
104
- return result;
105
- },
106
- },
107
116
  fault_status: {
108
117
  cluster: 'genBinaryInput',
109
118
  type: ['attributeReport', 'readResponse'],
@@ -374,12 +383,11 @@ module.exports = [
374
383
  model: 'EMIZB-132',
375
384
  vendor: 'Develco',
376
385
  description: 'Wattle AMS HAN power-meter sensor',
377
- fromZigbee: [develco.fz.metering, develco.fz.electrical_measurement, develco.fz.firmware_version],
386
+ fromZigbee: [develco.fz.metering, develco.fz.electrical_measurement],
378
387
  toZigbee: [tz.EMIZB_132_mode],
379
388
  ota: ota.zigbeeOTA,
380
389
  configure: async (device, coordinatorEndpoint, logger) => {
381
390
  const endpoint = device.getEndpoint(2);
382
- await endpoint.read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'], manufacturerOptions);
383
391
  await reporting.bind(endpoint, coordinatorEndpoint, ['haElectricalMeasurement', 'seMetering']);
384
392
 
385
393
  try {
@@ -402,6 +410,7 @@ module.exports = [
402
410
  exposes: [e.power(), e.energy(), e.current(), e.voltage(), e.current_phase_b(), e.voltage_phase_b(), e.current_phase_c(),
403
411
  e.voltage_phase_c()],
404
412
  onEvent: async (type, data, device) => {
413
+ fwOnEvent(type, data, device);
405
414
  if (type === 'message' && data.type === 'attributeReport' && data.cluster === 'seMetering' && data.data['divisor']) {
406
415
  // Device sends wrong divisior (512) while it should be fixed to 1000
407
416
  // https://github.com/Koenkk/zigbee-herdsman-converters/issues/3066
@@ -415,16 +424,16 @@ module.exports = [
415
424
  vendor: 'Develco',
416
425
  description: 'Smoke detector with siren',
417
426
  fromZigbee: [fz.temperature, fz.battery, fz.ias_smoke_alarm_1_develco, fz.ignore_basic_report,
418
- develco.fz.firmware_version, fz.ias_enroll, fz.ias_wd, develco.fz.fault_status],
427
+ fz.ias_enroll, fz.ias_wd, develco.fz.fault_status],
419
428
  toZigbee: [tz.warning, tz.ias_max_duration, tz.warning_simple],
420
429
  ota: ota.zigbeeOTA,
430
+ onEvent: fwOnEvent,
421
431
  meta: {battery: {voltageToPercentage: '3V_2500'}},
422
432
  configure: async (device, coordinatorEndpoint, logger) => {
423
433
  const endpoint = device.getEndpoint(35);
424
434
 
425
435
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'ssIasZone', 'ssIasWd', 'genBasic', 'genBinaryInput']);
426
436
  await reporting.batteryVoltage(endpoint);
427
- await endpoint.read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'], manufacturerOptions);
428
437
  await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
429
438
  await endpoint.read('genBinaryInput', ['reliability', 'statusFlags']);
430
439
  await endpoint.read('ssIasWd', ['maxDuration']);
@@ -449,15 +458,15 @@ module.exports = [
449
458
  vendor: 'Develco',
450
459
  description: 'Fire detector with siren',
451
460
  fromZigbee: [fz.temperature, fz.battery, fz.ias_smoke_alarm_1_develco, fz.ignore_basic_report,
452
- develco.fz.firmware_version, fz.ias_enroll, fz.ias_wd, develco.fz.fault_status],
461
+ fz.ias_enroll, fz.ias_wd, develco.fz.fault_status],
453
462
  toZigbee: [tz.warning, tz.ias_max_duration, tz.warning_simple],
454
463
  meta: {battery: {voltageToPercentage: '3V_2500'}},
464
+ onEvent: fwOnEvent,
455
465
  configure: async (device, coordinatorEndpoint, logger) => {
456
466
  const endpoint = device.getEndpoint(35);
457
467
 
458
468
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'ssIasZone', 'ssIasWd', 'genBasic', 'genBinaryInput']);
459
469
  await reporting.batteryVoltage(endpoint);
460
- await endpoint.read('genBasic', ['develcoPrimarySwVersion'], manufacturerOptions);
461
470
  await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
462
471
  await endpoint.read('genBinaryInput', ['reliability', 'statusFlags']);
463
472
  await endpoint.read('ssIasWd', ['maxDuration']);
@@ -519,16 +528,29 @@ module.exports = [
519
528
  description: 'Motion sensor',
520
529
  fromZigbee: [
521
530
  fz.temperature, fz.illuminance, fz.ias_occupancy_alarm_1, fz.battery,
522
- develco.fz.led_control, develco.fz.ias_occupancy_timeout, develco.fz.firmware_version,
531
+ develco.fz.led_control, develco.fz.ias_occupancy_timeout,
523
532
  ],
524
533
  toZigbee: [develco.tz.led_control, develco.tz.ias_occupancy_timeout],
525
- exposes: [
526
- e.occupancy(), e.battery(), e.battery_low(),
527
- e.tamper(), e.temperature(), e.illuminance_lux(),
528
- exposes.enum('led_control', ea.ALL, ['off', 'fault_only', 'motion_only', 'both']).
529
- withDescription('Control LED indicator usage.'),
530
- exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(20).withValueMax(65535),
531
- ],
534
+ onEvent: fwOnEvent,
535
+ exposes: (device, options) => {
536
+ const dynExposes = [];
537
+ dynExposes.push(e.occupancy());
538
+ if (device && device.softwareBuildID && device.softwareBuildID.split('.')[0] >= 3) {
539
+ dynExposes.push(exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').
540
+ withValueMin(20).withValueMax(65535));
541
+ }
542
+ dynExposes.push(e.temperature());
543
+ dynExposes.push(e.illuminance_lux());
544
+ dynExposes.push(e.tamper());
545
+ dynExposes.push(e.battery_low());
546
+ dynExposes.push(e.battery());
547
+ if (device && device.softwareBuildID && device.softwareBuildID.split('.')[0] >= 4) {
548
+ dynExposes.push(exposes.enum('led_control', ea.ALL, ['off', 'fault_only', 'motion_only', 'both']).
549
+ withDescription('Control LED indicator usage.'));
550
+ }
551
+ dynExposes.push(e.linkquality());
552
+ return dynExposes;
553
+ },
532
554
  meta: {battery: {voltageToPercentage: '3V_2500'}},
533
555
  endpoint: (device) => {
534
556
  return {default: 35};
@@ -547,12 +569,14 @@ module.exports = [
547
569
  const endpoint35 = device.getEndpoint(35);
548
570
  await reporting.bind(endpoint35, coordinatorEndpoint, ['genPowerCfg']);
549
571
  await reporting.batteryVoltage(endpoint35, {min: constants.repInterval.HOUR, max: 43200, change: 100});
550
- await endpoint35.read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'], manufacturerOptions);
551
572
 
552
- try {
573
+ // zigbee2mqtt#14277 some features are not available on older firmwares
574
+ if (device && device.softwareBuildID && device.softwareBuildID.split('.')[0] >= 3) {
553
575
  await endpoint35.read('ssIasZone', ['develcoAlarmOffDelay'], manufacturerOptions);
576
+ }
577
+ if (device && device.softwareBuildID && device.softwareBuildID.split('.')[0] >= 4) {
554
578
  await endpoint35.read('genBasic', ['develcoLedControl'], manufacturerOptions);
555
- } catch (error) {/* some reports of timeouts on reading these */}
579
+ }
556
580
  },
557
581
  },
558
582
  {
@@ -677,14 +701,14 @@ module.exports = [
677
701
  model: 'SIRZB-110',
678
702
  vendor: 'Develco',
679
703
  description: 'Customizable siren',
680
- fromZigbee: [fz.temperature, fz.battery, fz.ias_enroll, fz.ias_wd, develco.fz.firmware_version, fz.ias_siren],
704
+ fromZigbee: [fz.temperature, fz.battery, fz.ias_enroll, fz.ias_wd, fz.ias_siren],
681
705
  toZigbee: [tz.warning, tz.warning_simple, tz.ias_max_duration, tz.squawk],
706
+ onEvent: fwOnEvent,
682
707
  meta: {battery: {voltageToPercentage: '3V_2500'}},
683
708
  configure: async (device, coordinatorEndpoint, logger) => {
684
709
  const endpoint = device.getEndpoint(43);
685
710
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'ssIasZone', 'ssIasWd', 'genBasic']);
686
711
  await reporting.batteryVoltage(endpoint);
687
- await endpoint.read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'], manufacturerOptions);
688
712
  await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
689
713
  await endpoint.read('ssIasWd', ['maxDuration']);
690
714
 
@@ -743,8 +767,9 @@ module.exports = [
743
767
  model: 'IOMZB-110',
744
768
  vendor: 'Develco',
745
769
  description: 'IO module',
746
- fromZigbee: [fz.on_off, develco.fz.input, develco.fz.firmware_version],
770
+ fromZigbee: [fz.on_off, develco.fz.input],
747
771
  toZigbee: [tz.on_off, develco.tz.input],
772
+ onEvent: fwOnEvent,
748
773
  meta: {multiEndpoint: true},
749
774
  exposes: [
750
775
  exposes.binary('input', ea.STATE_GET, true, false).withEndpoint('l1').withDescription('State of input 1'),
@@ -755,12 +780,9 @@ module.exports = [
755
780
  exposes.switch().withState('state', true, 'On/off state of switch 2').withEndpoint('l12'),
756
781
  ],
757
782
  configure: async (device, coordinatorEndpoint, logger) => {
758
- const options = {manufacturerCode: 4117};
759
-
760
783
  const ep2 = device.getEndpoint(112);
761
784
  await reporting.bind(ep2, coordinatorEndpoint, ['genBinaryInput', 'genBasic']);
762
785
  await reporting.presentValue(ep2, {min: 0});
763
- await ep2.read('genBasic', ['develcoPrimarySwVersion', 'develcoPrimaryHwVersion'], options);
764
786
 
765
787
  const ep3 = device.getEndpoint(113);
766
788
  await reporting.bind(ep3, coordinatorEndpoint, ['genBinaryInput']);
@@ -84,7 +84,10 @@ module.exports = [
84
84
  exposes: [
85
85
  exposes.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']).withEndpoint('left'),
86
86
  exposes.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']).withEndpoint('right'),
87
- exposes.numeric('calibration_time', ea.STATE).withUnit('S').withDescription('Calibration time'),
87
+ exposes.numeric('calibration_time', ea.STATE).withUnit('S').withDescription('Calibration time')
88
+ .withEndpoint('left'),
89
+ exposes.numeric('calibration_time', ea.STATE).withUnit('S').withDescription('Calibration time')
90
+ .withEndpoint('right'),
88
91
  e.cover_position().withEndpoint('left'), exposes.binary('calibration', ea.ALL, 'ON', 'OFF')
89
92
  .withEndpoint('left'), exposes.binary('motor_reversal', ea.ALL, 'ON', 'OFF').withEndpoint('left'),
90
93
  e.cover_position().withEndpoint('right'), exposes.binary('calibration', ea.ALL, 'ON', 'OFF')
package/devices/moes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const exposes = require('../lib/exposes');
2
- const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/legacy').fromZigbee};
2
+ const fz = require('../converters/fromZigbee');
3
3
  const tz = require('../converters/toZigbee');
4
4
  const ota = require('../lib/ota');
5
5
  const tuya = require('../lib/tuya');
@@ -26,11 +26,13 @@ module.exports = [
26
26
  model: 'ZP-LZ-FR2U',
27
27
  vendor: 'Moes',
28
28
  description: 'Zigbee 3.0 dual USB wireless socket plug',
29
- fromZigbee: [fz.on_off, fz.tuya_switch_power_outage_memory, fz.ts011f_plug_child_mode],
30
- toZigbee: [tz.on_off, tz.tuya_switch_power_outage_memory, tz.ts011f_plug_child_mode],
29
+ fromZigbee: [fz.on_off, fz.tuya_switch_power_outage_memory, fz.ts011f_plug_indicator_mode, fz.ts011f_plug_child_mode],
30
+ toZigbee: [tz.on_off, tz.tuya_switch_power_outage_memory, tz.ts011f_plug_indicator_mode, tz.ts011f_plug_child_mode],
31
31
  exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'),
32
32
  exposes.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore'])
33
- .withDescription('Recover state after power outage'), e.child_lock()],
33
+ .withDescription('Recover state after power outage'),
34
+ exposes.enum('indicator_mode', ea.ALL, ['off', 'off/on', 'on/off', 'on'])
35
+ .withDescription('Plug LED indicator mode'), e.child_lock()],
34
36
  endpoint: (device) => {
35
37
  return {'l1': 1, 'l2': 2};
36
38
  },
@@ -932,7 +932,7 @@ module.exports = [
932
932
  extend: hueExtend.light_onoff_brightness_colortemp_color(),
933
933
  },
934
934
  {
935
- zigbeeModel: ['LCL001'],
935
+ zigbeeModel: ['LCL001', 'LCL006'],
936
936
  model: '8718699703424',
937
937
  vendor: 'Philips',
938
938
  description: 'Hue white and color ambiance LightStrip plus',
package/devices/tuya.js CHANGED
@@ -1079,7 +1079,7 @@ module.exports = [
1079
1079
  },
1080
1080
  },
1081
1081
  {
1082
- fingerprint: [{modelID: 'TS0202', manufacturerName: '_TZ3000_mcxw5ehu'}],
1082
+ fingerprint: tuya.fingerprint('TS0202', ['_TZ3000_mcxw5ehu', '_TZ3040_6ygjfyll']),
1083
1083
  model: 'IH012-RT01',
1084
1084
  vendor: 'TuYa',
1085
1085
  description: 'Motion sensor',
@@ -2014,7 +2014,6 @@ module.exports = [
2014
2014
  {modelID: 'TS0601', manufacturerName: '_TZE200_cpmgn2cf'},
2015
2015
  {modelID: 'TS0601', manufacturerName: '_TZE200_4eeyebrt'}, // Immax 07732B
2016
2016
  {modelID: 'TS0601', manufacturerName: '_TZE200_8whxpsiw'}, // EVOLVEO
2017
- {modelID: 'TS0601', manufacturerName: '_TZE200_bvu2wnxz'}, /* model: 'TRV06', vendor: 'AVATTO' */
2018
2017
  ],
2019
2018
  model: 'TS0601_thermostat',
2020
2019
  vendor: 'TuYa',
@@ -2059,15 +2058,16 @@ module.exports = [
2059
2058
  .withFeature(exposes.text('holidays_schedule', ea.STATE_SET))],
2060
2059
  },
2061
2060
  {
2062
- fingerprint: [
2063
- {modelID: 'TS0601', manufacturerName: '_TZE200_hue3yfsn'}, /* model: 'TV02-Zigbee', vendor: 'TuYa' */
2064
- {modelID: 'TS0601', manufacturerName: '_TZE200_e9ba97vf'}, /* model: 'TV01-ZB', vendor: 'Moes' */
2065
- {modelID: 'TS0601', manufacturerName: '_TZE200_husqqvux'}, /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */
2066
- {modelID: 'TS0601', manufacturerName: '_TZE200_lllliz3p'}, /* model: 'TV02-Zigbee', vendor: 'TuYa' */
2067
- {modelID: 'TS0601', manufacturerName: '_TZE200_mudxchsu'}, /* model: 'TV05-ZG curve', vendor: 'TuYa' */
2068
- {modelID: 'TS0601', manufacturerName: '_TZE200_7yoranx2'}, /* model: 'TV01-ZB', vendor: 'Moes' */
2069
- {modelID: 'TS0601', manufacturerName: '_TZE200_kds0pmmv'}, /* model: 'TV01-ZB', vendor: 'Moes' */
2070
- ],
2061
+ fingerprint: tuya.fingerprint('TS0601', [
2062
+ '_TZE200_hue3yfsn', /* model: 'TV02-Zigbee', vendor: 'TuYa' */
2063
+ '_TZE200_e9ba97vf', /* model: 'TV01-ZB', vendor: 'Moes' */
2064
+ '_TZE200_husqqvux', /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */
2065
+ '_TZE200_lnbfnyxd', /* model: 'TSL-TRV-TV01ZG', vendor: 'Tesla Smart' */
2066
+ '_TZE200_lllliz3p', /* model: 'TV02-Zigbee', vendor: 'TuYa' */
2067
+ '_TZE200_mudxchsu', /* model: 'TV05-ZG curve', vendor: 'TuYa' */
2068
+ '_TZE200_7yoranx2', /* model: 'TV01-ZB', vendor: 'Moes' */
2069
+ '_TZE200_kds0pmmv', /* model: 'TV01-ZB', vendor: 'Moes' */
2070
+ ]),
2071
2071
  model: 'TV02-Zigbee',
2072
2072
  vendor: 'TuYa',
2073
2073
  description: 'Thermostat radiator valve',
@@ -2078,30 +2078,49 @@ module.exports = [
2078
2078
  {vendor: 'Unknown/id3.pl', model: 'GTZ08'},
2079
2079
  ],
2080
2080
  ota: ota.zigbeeOTA,
2081
- fromZigbee: [fz.ignore_basic_report, fz.ignore_tuya_set_time, fz.tvtwo_thermostat],
2082
- toZigbee: [tz.tvtwo_thermostat],
2081
+ fromZigbee: [tuya.fzDataPoints],
2082
+ toZigbee: [tuya.tzDataPoints],
2083
2083
  onEvent: tuya.onEventSetLocalTime,
2084
+ configure: tuya.configureMagicPacket,
2084
2085
  exposes: [
2085
- e.battery_low(), e.child_lock(), e.open_window(), e.open_window_temperature().withValueMin(0).withValueMax(30),
2086
- e.comfort_temperature().withValueMin(0).withValueMax(30), e.eco_temperature().withValueMin(0).withValueMax(30),
2087
- exposes.climate().withPreset(['auto', 'manual', 'holiday']).withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET)
2088
- .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 0, 30, 0.5, ea.STATE_SET),
2086
+ e.battery(), e.child_lock(), e.open_window(), e.open_window_temperature().withValueMin(5).withValueMax(30),
2087
+ e.comfort_temperature().withValueMin(5).withValueMax(30), e.eco_temperature().withValueMin(5).withValueMax(30),
2088
+ exposes.climate().withSystemMode(['off', 'heat'], ea.STATE_SET, 'When switched to the "off" mode, the device will display ' +
2089
+ '"HS" and the valve will be fully closed. Press the pair button to cancel or switch back to "heat" mode. Battery life ' +
2090
+ 'can be prolonged by switching the heating off. After switching to `heat` mode, `preset` will be reset to `auto` and ' +
2091
+ 'after changing `preset` to `manual` temperature setpoint will be 20 degrees.').withPreset(['auto', 'manual', 'holiday'],
2092
+ '`auto` uses schedule properties, check them. `manual` allows you to control the device, `holiday` uses ' +
2093
+ '`holiday_start_stop` and `holiday_temperature` properties.').withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET)
2094
+ .withLocalTemperature(ea.STATE).withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET),
2089
2095
  exposes.numeric('boost_timeset_countdown', ea.STATE_SET).withUnit('second').withDescription('Setting '+
2090
2096
  'minimum 0 - maximum 465 seconds boost time. The boost (♨) function is activated. The remaining '+
2091
2097
  'time for the function will be counted down in seconds ( 465 to 0 ).').withValueMin(0).withValueMax(465),
2092
2098
  exposes.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF').withDescription('When Anti-Freezing function'+
2093
2099
  ' is activated, the temperature in the house is kept at 8 °C, the device display "AF".press the '+
2094
2100
  'pair button to cancel.'),
2095
- exposes.binary('heating_stop', ea.STATE_SET, 'ON', 'OFF').withDescription('Battery life can be prolonged'+
2096
- ' by switching the heating off. To achieve this, the valve is closed fully. To activate the '+
2097
- 'heating stop, the device display "HS", press the pair button to cancel.'),
2098
- e.holiday_temperature(),
2099
- exposes.text('holiday_start_stop', ea.STATE_SET).withDescription('The holiday mode( ) will '+
2100
- 'automatically start at the set time starting point and run the holiday temperature.'),
2101
- // exposes.enum('working_day', ea.STATE_SET, ['0', '1', '2', '3']),
2102
- exposes.composite('schedule')/* .withFeature(exposes.text('week_schedule_programming', ea.STATE_SET)) */
2103
- .withDescription('week_schedule').withDescription('Auto Mode ⏱ - In this mode, '+
2104
- 'the device executes a preset week programming temperature time and temperature. '),
2101
+ exposes.binary('heating_stop', ea.STATE_SET, 'ON', 'OFF').withDescription('Same as `system_mode`. Left for compatibility.'),
2102
+ exposes.numeric('holiday_temperature', ea.STATE_SET).withUnit('°C').withDescription('Holiday temperature')
2103
+ .withValueMin(5).withValueMax(30),
2104
+ exposes.text('holiday_start_stop', ea.STATE_SET).withDescription('The holiday mode will automatically start ' +
2105
+ 'at the set time starting point and run the holiday temperature. Can be defined in the following format: ' +
2106
+ '`startYear/startMonth/startDay startHours:startMinutes | endYear/endMonth/endDay endHours:endMinutes`. ' +
2107
+ 'For example: `2022/10/01 16:30 | 2022/10/21 18:10`. After the end of holiday mode, it switches to "auto" ' +
2108
+ 'mode and uses schedule.'),
2109
+ exposes.enum('working_day', ea.STATE_SET, ['mon_sun', 'mon_fri+sat+sun', 'separate']).withDescription('`mon_sun` ' +
2110
+ '- schedule for Monday used for each day (define it only for Monday). `mon_fri+sat+sun` - schedule for ' +
2111
+ 'workdays used from Monday (define it only for Monday), Saturday and Sunday are defined separately. `separate` ' +
2112
+ '- schedule for each day is defined separately.'),
2113
+ exposes.composite('schedule', 'schedule').withFeature(exposes.enum('week_day', ea.SET, ['monday', 'tuesday',
2114
+ 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'])).withFeature(exposes.text('schedule', ea.SET))
2115
+ .withDescription('Schedule will work with "auto" preset. In this mode, the device executes ' +
2116
+ 'a preset week programming temperature time and temperature. Before using these properties, check `working_day` ' +
2117
+ 'property. Each day can contain up to 10 segments. At least 1 segment should be defined. Different count of segments ' +
2118
+ 'can be defined for each day, e.g., 3 segments for Monday, 5 segments for Thursday, etc. It should be defined in the ' +
2119
+ 'following format: `hours:minutes/temperature`. Minutes can be only tens, i.e., 00, 10, 20, 30, 40, 50. Segments should ' +
2120
+ 'be divided by space symbol. Each day should end with the last segment of 24:00. Examples: `04:00/20 08:30/22 10:10/18 ' +
2121
+ '18:40/24 22:50/19.5`; `06:00/21.5 17:20/26 24:00/18`. The temperature will be set from the beginning/start of one ' +
2122
+ 'period and until the next period, e.g., `04:00/20 24:00/22` means that from 00:00 to 04:00 temperature will be 20 ' +
2123
+ 'degrees and from 04:00 to 00:00 temperature will be 22 degrees.'),
2105
2124
  exposes.text('schedule_monday', ea.STATE),
2106
2125
  exposes.text('schedule_tuesday', ea.STATE),
2107
2126
  exposes.text('schedule_wednesday', ea.STATE),
@@ -2109,9 +2128,44 @@ module.exports = [
2109
2128
  exposes.text('schedule_friday', ea.STATE),
2110
2129
  exposes.text('schedule_saturday', ea.STATE),
2111
2130
  exposes.text('schedule_sunday', ea.STATE),
2112
- exposes.binary('online', ea.STATE_SET, 'ON', 'OFF').withDescription('Is the device online'),
2131
+ exposes.binary('online', ea.STATE_SET, 'ON', 'OFF').withDescription('Turn on this property to poll current data from the ' +
2132
+ 'device. It can be used to periodically fetch a new local temperature since the device doesn\'t update itself. ' +
2133
+ 'Setting this property doesn\'t turn on the display.'),
2113
2134
  exposes.numeric('error_status', ea.STATE).withDescription('Error status'),
2114
2135
  ],
2136
+ meta: {
2137
+ tuyaDatapoints: [
2138
+ [2, 'preset', tuya.valueConverterBasic.lookup({'auto': tuya.enum(0), 'manual': tuya.enum(1), 'holiday': tuya.enum(3)})],
2139
+ [8, 'open_window', tuya.valueConverter.onOff],
2140
+ [10, 'frost_protection', tuya.valueConverter.onOff],
2141
+ [16, 'current_heating_setpoint', tuya.valueConverter.divideBy10],
2142
+ [24, 'local_temperature', tuya.valueConverter.divideBy10],
2143
+ [27, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration],
2144
+ [31, 'working_day', tuya.valueConverterBasic.lookup({'mon_sun': tuya.enum(0), 'mon_fri+sat+sun': tuya.enum(1),
2145
+ 'separate': tuya.enum(2)})],
2146
+ [32, 'holiday_temperature', tuya.valueConverter.divideBy10],
2147
+ [35, 'battery_low', tuya.valueConverter.true0ElseFalse],
2148
+ [40, 'child_lock', tuya.valueConverter.lockUnlock],
2149
+ [45, 'error_status', tuya.valueConverter.raw],
2150
+ [46, 'holiday_start_stop', tuya.valueConverter.thermostatHolidayStartStop],
2151
+ [101, 'boost_timeset_countdown', tuya.valueConverter.raw],
2152
+ [102, 'open_window_temperature', tuya.valueConverter.divideBy10],
2153
+ [104, 'comfort_temperature', tuya.valueConverter.divideBy10],
2154
+ [105, 'eco_temperature', tuya.valueConverter.divideBy10],
2155
+ [106, 'schedule', tuya.valueConverter.thermostatScheduleDay],
2156
+ [107, null, tuya.valueConverter.TV02SystemMode],
2157
+ [107, 'system_mode', tuya.valueConverterBasic.lookup({'heat': false, 'off': true})],
2158
+ [107, 'heating_stop', tuya.valueConverter.onOff],
2159
+ [115, 'online', tuya.valueConverter.onOffNotStrict],
2160
+ [108, 'schedule_monday', tuya.valueConverter.thermostatScheduleDay],
2161
+ [112, 'schedule_tuesday', tuya.valueConverter.thermostatScheduleDay],
2162
+ [109, 'schedule_wednesday', tuya.valueConverter.thermostatScheduleDay],
2163
+ [113, 'schedule_thursday', tuya.valueConverter.thermostatScheduleDay],
2164
+ [110, 'schedule_friday', tuya.valueConverter.thermostatScheduleDay],
2165
+ [114, 'schedule_saturday', tuya.valueConverter.thermostatScheduleDay],
2166
+ [111, 'schedule_sunday', tuya.valueConverter.thermostatScheduleDay],
2167
+ ],
2168
+ },
2115
2169
  },
2116
2170
  {
2117
2171
  fingerprint: [
package/lib/tuya.js CHANGED
@@ -1178,6 +1178,27 @@ const fingerprint = (modelID, manufacturerNames) => {
1178
1178
  });
1179
1179
  };
1180
1180
 
1181
+ class Base {
1182
+ constructor(value) {
1183
+ this.value = value;
1184
+ }
1185
+
1186
+ valueOf() {
1187
+ return this.value;
1188
+ }
1189
+ }
1190
+ class Enum extends Base {
1191
+ constructor(value) {
1192
+ super(value);
1193
+ }
1194
+ }
1195
+
1196
+ class Bitmap extends Base {
1197
+ constructor(value) {
1198
+ super(value);
1199
+ }
1200
+ }
1201
+
1181
1202
  const valueConverterBasic = {
1182
1203
  lookup: (map) => {
1183
1204
  return {
@@ -1186,7 +1207,7 @@ const valueConverterBasic = {
1186
1207
  return map[v];
1187
1208
  },
1188
1209
  from: (v) => {
1189
- const value = Object.entries(map).find((i) => i[1] === v);
1210
+ const value = Object.entries(map).find((i) => i[1].valueOf() === v);
1190
1211
  if (!value) throw new Error(`Value '${v}' is not allowed, expected one of ${Object.values(map)}`);
1191
1212
  return value[0];
1192
1213
  },
@@ -1222,11 +1243,140 @@ const valueConverter = {
1222
1243
  },
1223
1244
  true0ElseFalse: {from: (v) => v === 0},
1224
1245
  selfTestResult: valueConverterBasic.lookup({'checking': 0, 'success': 1, 'failure': 2, 'others': 3}),
1246
+ lockUnlock: valueConverterBasic.lookup({'LOCK': true, 'UNLOCK': false}),
1247
+ localTempCalibration: {
1248
+ from: (v) => {
1249
+ if (v > 55) v -= 0x100000000;
1250
+ return v / 10;
1251
+ },
1252
+ to: (v) => {
1253
+ if (v > 0) return v * 10;
1254
+ if (v < 0) return v * 10 + 0x100000000;
1255
+ return v;
1256
+ },
1257
+ },
1258
+ thermostatHolidayStartStop: {
1259
+ from: (v) => {
1260
+ const start = {
1261
+ year: v.slice(0, 4), month: v.slice(4, 6), day: v.slice(6, 8),
1262
+ hours: v.slice(8, 10), minutes: v.slice(10, 12),
1263
+ };
1264
+ const end = {
1265
+ year: v.slice(12, 16), month: v.slice(16, 18), day: v.slice(18, 20),
1266
+ hours: v.slice(20, 22), minutes: v.slice(22, 24),
1267
+ };
1268
+ const startStr = `${start.year}/${start.month}/${start.day} ${start.hours}:${start.minutes}`;
1269
+ const endStr = `${end.year}/${end.month}/${end.day} ${end.hours}:${end.minutes}`;
1270
+ return `${startStr} | ${endStr}`;
1271
+ },
1272
+ to: (v) => {
1273
+ const numberPattern = /\d+/g;
1274
+ return v.match(numberPattern).join([]).toString();
1275
+ },
1276
+ },
1277
+ thermostatScheduleDay: {
1278
+ from: (v) => {
1279
+ // day splitted to 10 min segments = total 144 segments
1280
+ const maxPeriodsInDay = 10;
1281
+ const periodSize = 3;
1282
+ const schedule = [];
1283
+
1284
+ for (let i = 0; i < maxPeriodsInDay; i++) {
1285
+ const time = v[i * periodSize];
1286
+ const totalMinutes = time * 10;
1287
+ const hours = totalMinutes / 60;
1288
+ const rHours = Math.floor(hours);
1289
+ const minutes = (hours - rHours) * 60;
1290
+ const rMinutes = Math.round(minutes);
1291
+ const strHours = rHours.toString().padStart(2, '0');
1292
+ const strMinutes = rMinutes.toString().padStart(2, '0');
1293
+ const temp = parseFloat(v[i * periodSize + 2] / 10);
1294
+ schedule.push(`${strHours}:${strMinutes}/${temp}`);
1295
+ if (rHours === 24) break;
1296
+ }
1297
+
1298
+ return schedule.join(' ');
1299
+ },
1300
+ to: (v, meta) => {
1301
+ const dayByte = {
1302
+ monday: 1, tuesday: 2, wednesday: 4, thursday: 8,
1303
+ friday: 16, saturday: 32, sunday: 64,
1304
+ };
1305
+ const weekDay = v.week_day;
1306
+ if (Object.keys(dayByte).indexOf(weekDay) === -1) {
1307
+ throw new Error('Invalid "week_day" property value: ' + weekDay);
1308
+ }
1309
+ let weekScheduleType;
1310
+ if (meta.state && meta.state.working_day) weekScheduleType = meta.state.working_day;
1311
+ const payload = [];
1312
+
1313
+ switch (weekScheduleType) {
1314
+ case 'mon_sun':
1315
+ payload.push(127);
1316
+ break;
1317
+ case 'mon_fri+sat+sun':
1318
+ if (['saturday', 'sunday'].indexOf(weekDay) === -1) {
1319
+ payload.push(31);
1320
+ break;
1321
+ }
1322
+ payload.push(dayByte[weekDay]);
1323
+ break;
1324
+ case 'separate':
1325
+ payload.push(dayByte[weekDay]);
1326
+ break;
1327
+ default:
1328
+ throw new Error('Invalid "working_day" property, need to set it before');
1329
+ }
1330
+
1331
+ // day splitted to 10 min segments = total 144 segments
1332
+ const maxPeriodsInDay = 10;
1333
+ const schedule = v.schedule.split(' ');
1334
+ const schedulePeriods = schedule.length;
1335
+ if (schedulePeriods > 10) throw new Error('There cannot be more than 10 periods in the schedule: ' + v);
1336
+ if (schedulePeriods < 2) throw new Error('There cannot be less than 2 periods in the schedule: ' + v);
1337
+ let prevHour;
1338
+
1339
+ for (const period of schedule) {
1340
+ const timeTemp = period.split('/');
1341
+ const hm = timeTemp[0].split(':', 2);
1342
+ const h = parseInt(hm[0]);
1343
+ const m = parseInt(hm[1]);
1344
+ const temp = parseFloat(timeTemp[1]);
1345
+ if (h < 0 || h > 24 || m < 0 || m >= 60 || m % 10 !== 0 || temp < 5 || temp > 30 || temp % 0.5 !== 0) {
1346
+ throw new Error('Invalid hour, minute or temperature of: ' + period);
1347
+ } else if (prevHour > h) {
1348
+ throw new Error(`The hour of the next segment can't be less than the previous one: ${prevHour} > ${h}`);
1349
+ }
1350
+ prevHour = h;
1351
+ const segment = (h * 60 + m) / 10;
1352
+ payload.push(segment, 0, temp * 10);
1353
+ }
1354
+
1355
+ // Add "technical" periods to be valid payload
1356
+ for (let i = 0; i < maxPeriodsInDay - schedulePeriods; i++) {
1357
+ // by default it sends 9000b2, it's 24 hours and 18 degrees
1358
+ payload.push(144, 0, 180);
1359
+ }
1360
+
1361
+ return payload;
1362
+ },
1363
+ },
1364
+ TV02SystemMode: {
1365
+ from: (v) => {
1366
+ return {system_mode: v === false ? 'heat' : 'off', heating_stop: v === false ? 'OFF' : 'ON'};
1367
+ },
1368
+ },
1369
+ onOffNotStrict: {from: (v) => v ? 'ON' : 'OFF', to: (v) => v === 'ON'},
1225
1370
  };
1226
1371
 
1227
1372
  const tzDataPoints = {
1228
- key: ['state', 'brightness', 'min_brightness', 'max_brightness', 'power_on_behavior',
1229
- 'countdown', 'light_type', 'silence', 'self_test'],
1373
+ key: [
1374
+ 'state', 'brightness', 'min_brightness', 'max_brightness', 'power_on_behavior',
1375
+ 'countdown', 'light_type', 'silence', 'self_test', 'child_lock', 'open_window', 'open_window_temperature', 'frost_protection',
1376
+ 'system_mode', 'heating_stop', 'current_heating_setpoint', 'local_temperature_calibration', 'preset', 'boost_timeset_countdown',
1377
+ 'holiday_start_stop', 'holiday_temperature', 'comfort_temperature', 'eco_temperature', 'working_day', 'week_schedule_programming',
1378
+ 'online', 'holiday_mode_date', 'schedule',
1379
+ ],
1230
1380
  convertSet: async (entity, key, value, meta) => {
1231
1381
  // A set converter is only called once; therefore we need to loop
1232
1382
  const state = {};
@@ -1240,11 +1390,19 @@ const tzDataPoints = {
1240
1390
  }
1241
1391
  if (dpEntry[3] && dpEntry[3].skip && dpEntry[3].skip(meta)) continue;
1242
1392
  const dpId = dpEntry[0];
1243
- const convertedValue = dpEntry[2].to(value);
1393
+ const convertedValue = dpEntry[2].to(value, meta);
1244
1394
  if (typeof convertedValue === 'boolean') {
1245
1395
  await sendDataPointBool(entity, dpId, convertedValue, 'dataRequest', 1);
1246
1396
  } else if (typeof convertedValue === 'number') {
1247
1397
  await sendDataPointValue(entity, dpId, convertedValue, 'dataRequest', 1);
1398
+ } else if (typeof convertedValue === 'string') {
1399
+ await sendDataPointStringBuffer(entity, dpId, convertedValue, 'dataRequest', 1);
1400
+ } else if (Array.isArray(convertedValue)) {
1401
+ await sendDataPointRaw(entity, dpId, convertedValue, 'dataRequest', 1);
1402
+ } else if (convertedValue instanceof Enum) {
1403
+ await sendDataPointEnum(entity, dpId, convertedValue.valueOf(), 'dataRequest', 1);
1404
+ } else if (convertedValue instanceof Bitmap) {
1405
+ await sendDataPointBitmap(entity, dpId, convertedValue.valueOf(), 'dataRequest', 1);
1248
1406
  } else {
1249
1407
  throw new Error(`Don't know how to send type '${typeof convertedValue}'`);
1250
1408
  }
@@ -1267,9 +1425,9 @@ const fzDataPoints = {
1267
1425
  if (dpEntry) {
1268
1426
  const value = getDataValue(dpValue);
1269
1427
  if (dpEntry[1]) {
1270
- result[dpEntry[1]] = dpEntry[2].from(value);
1428
+ result[dpEntry[1]] = dpEntry[2].from(value, meta);
1271
1429
  } else if (dpEntry[2]) {
1272
- result = {...result, ...dpEntry[2].from(value)};
1430
+ result = {...result, ...dpEntry[2].from(value, meta)};
1273
1431
  }
1274
1432
  } else {
1275
1433
  meta.logger.warn(`Datapoint ${dpId} not defined for '${meta.device.manufacturerName}' ` +
@@ -1285,6 +1443,8 @@ module.exports = {
1285
1443
  skip,
1286
1444
  configureMagicPacket,
1287
1445
  fingerprint,
1446
+ enum: (value) => new Enum(value),
1447
+ bitmap: (value) => new Bitmap(value),
1288
1448
  valueConverter,
1289
1449
  valueConverterBasic,
1290
1450
  tzDataPoints,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.646",
3
+ "version": "14.0.647",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [