From d18f537b9152ffc274bf5cc7af7d88ce5fdfa003 Mon Sep 17 00:00:00 2001 From: Marcel Hoog Antink <51886888+marcelhoogantink@users.noreply.github.com> Date: Tue, 28 Nov 2023 19:59:41 +0100 Subject: [PATCH] feat: Support schedule for Avatto ZWT198 and other improvements (#6571) * Added New Avatto ZWT198 updat including scedule, reset and brightness in devices/tuya.ts; lib/exposes.ts end lib.tuya.ts * Final changes, got rid of composite() * Update exposes.ts * Update tuya.ts * Update tuya.ts --------- Co-authored-by: Koen Kanters --- src/devices/tuya.ts | 90 ++++++++++++++++++++++++++++++--------------- src/lib/tuya.ts | 90 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 30 deletions(-) diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index 5363759d9b1ca..0402dc0988c95 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -1299,7 +1299,7 @@ const definitions: Definition[] = [ [5, 'max_brightness', tuya.valueConverter.scale0_254to0_1000], [6, 'countdown', tuya.valueConverter.countdown], [14, 'power_on_behavior', tuya.valueConverter.powerOnBehavior], - [21, 'backlight_mode', tuya.valueConverter.backlightMode], + [21, 'backlight_mode', tuya.valueConverter.backlightModeOffNormalInverted], ], }, whiteLabel: [ @@ -1339,7 +1339,7 @@ const definitions: Definition[] = [ [11, 'max_brightness_l2', tuya.valueConverter.scale0_254to0_1000], [12, 'countdown_l2', tuya.valueConverter.countdown], [14, 'power_on_behavior', tuya.valueConverter.powerOnBehaviorEnum], - [21, 'backlight_mode', tuya.valueConverter.backlightMode], + [21, 'backlight_mode', tuya.valueConverter.backlightModeOffNormalInverted], ], }, endpoint: (device) => { @@ -1387,7 +1387,7 @@ const definitions: Definition[] = [ [19, 'max_brightness_l3', tuya.valueConverter.scale0_254to0_1000], [20, 'countdown_l3', tuya.valueConverter.countdown], [14, 'power_on_behavior', tuya.valueConverter.powerOnBehaviorEnum], - [21, 'backlight_mode', tuya.valueConverter.backlightMode], + [21, 'backlight_mode', tuya.valueConverter.backlightModeOffNormalInverted], ], }, endpoint: (device) => { @@ -4169,65 +4169,97 @@ const definitions: Definition[] = [ fingerprint: tuya.fingerprint('TS0601', ['_TZE200_viy9ihs7']), model: 'ZWT198', vendor: 'TuYa', - description: 'AVATTO battery wall-mount thermostat', - fromZigbee: [tuya.fz.datapoints, fz.ignore_tuya_set_time], - toZigbee: [tuya.tz.datapoints], + description: 'Avatto wall thermostat', onEvent: tuya.onEvent({timeStart: '1970'}), + fromZigbee: [tuya.fz.datapoints], + toZigbee: [tuya.tz.datapoints], configure: tuya.configureMagicPacket, exposes: [ + e.binary('factory_reset', ea.STATE_SET, 'ON', 'OFF') + .withDescription('Full factory reset, use with caution!'), e.child_lock(), e.climate() .withSystemMode(['off', 'heat'], ea.STATE_SET) - .withPreset(['auto', 'manual', 'temporary program']) + .withPreset(['auto', 'manual', 'temporary_manual']) .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET) .withRunningState(['idle', 'heat'], ea.STATE) .withLocalTemperature(ea.STATE) .withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET), e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF') .withDescription('Antifreeze function'), - e.numeric('upper_temp', ea.STATE_SET).withUnit('°C').withValueMax(95) - .withValueMin(15).withValueStep(1).withPreset('default', 60, 'Default value') + e.max_temperature_limit() + .withUnit('°C') + .withValueMin(15) + .withValueMax(90) + .withValueStep(0.5) + .withPreset('default', 60, 'Default value') .withDescription('Maximum upper temperature'), - e.numeric('deadzone_temperature', ea.STATE_SET).withUnit('°C').withValueMax(10) - .withValueMin(0.5).withValueStep(0.5).withPreset('default', 1, 'Default value') - .withDescription('The delta between local_temperature and current_heating_setpoint to trigger Heat'), - // Not working yet: - // e.enum('schedule_mode', ea.STATE_SET, ['disabled','weekday/sat+sun','weekday+sat/sun','7day']) - // .withDescription('Schedule mode') + e.numeric('deadzone_temperature', ea.STATE_SET) + .withUnit('°C') + .withValueMax(10) + .withValueMin(0.5) + .withValueStep(0.5) + .withPreset('default', 1, 'Default value') + .withDescription('The delta between local_temperature (5 40C, 8:10 --> 17C, 11:40 --> 17C, 12:40 --> 17C, 17:10 --> 23C, 22:10 --> 17C // Sat-Sun: 8:05 --> 20C, 23:00 --> 16C - // I wasn't able to find a proper converter or to create one, so i commented it out - // [109, 'dp109', tuya.valueConverter.raw], - - // unmapped DPs, still need to figure out what they do - // [103, 'dp103', tuya.valueConverter.trueFalse1], - // [105, 'dp105', tuya.valueConverter.trueFalse1], - // [110, 'dp110', tuya.valueConverter.raw], - // [111, 'dp111', tuya.valueConverter.trueFalse1] ], }, }, diff --git a/src/lib/tuya.ts b/src/lib/tuya.ts index 4635da4f676d5..7120440cd3c92 100644 --- a/src/lib/tuya.ts +++ b/src/lib/tuya.ts @@ -445,7 +445,8 @@ export const valueConverter = { powerOnBehavior: valueConverterBasic.lookup({'off': 0, 'on': 1, 'previous': 2}), powerOnBehaviorEnum: valueConverterBasic.lookup({'off': new Enum(0), 'on': new Enum(1), 'previous': new Enum(2)}), switchType: valueConverterBasic.lookup({'momentary': new Enum(0), 'toggle': new Enum(1), 'state': new Enum(2)}), - backlightMode: valueConverterBasic.lookup({'off': new Enum(0), 'normal': new Enum(1), 'inverted': new Enum(2)}), + backlightModeOffNormalInverted: valueConverterBasic.lookup({'off': new Enum(0), 'normal': new Enum(1), 'inverted': new Enum(2)}), + backlightModeOffLowMediumHigh: valueConverterBasic.lookup({'off': new Enum(0), 'low': new Enum(1), 'medium': new Enum(2), 'high': new Enum(3)}), lightType: valueConverterBasic.lookup({'led': 0, 'incandescent': 1, 'halogen': 2}), countdown: valueConverterBasic.raw(), scale0_254to0_1000: valueConverterBasic.scale(0, 254, 0, 1000), @@ -459,6 +460,7 @@ export const valueConverter = { switchMode: valueConverterBasic.lookup({'switch': new Enum(0), 'scene': new Enum(1)}), lightMode: valueConverterBasic.lookup({'normal': new Enum(0), 'on': new Enum(1), 'off': new Enum(2), 'flash': new Enum(3)}), raw: valueConverterBasic.raw(), + workingDay: valueConverterBasic.lookup({'disabled': new Enum(0), '6-1': new Enum(1), '5-2': new Enum(2), '7': new Enum(3)}), localTemperatureCalibration: { from: (value: number) => value > 4000 ? value - 4096 : value, to: (value: number) => value < 0 ? 4096 + value : value, @@ -566,6 +568,17 @@ export const valueConverter = { return v; }, }, + localTempCalibration3: { + from: (v: number) => { + if (v > 0x7FFFFFFF) v -= 0x100000000; + return v / 10; + }, + to: (v: number) => { + if (v > 0) return v * 10; + if (v < 0) return v * 10 + 0x100000000; + return v; + }, + }, thermostatHolidayStartStop: { from: (v: string) => { const start = { @@ -729,6 +742,81 @@ export const valueConverter = { }, }; }, + ZWT198_schedule: { + from: (value: number[], meta: Fz.Meta, options: KeyValue) => { + const programmingMode = []; + + for (let i=0; i<8; i++) { + const start=i*4; + + const time = value[start].toString().padStart(2, '0')+':'+ value[start+1].toString().padStart(2, '0'); + const temp = (value[start+2]*256+value[start+3])/10; + const tempStr = temp.toFixed(1)+'°C'; + programmingMode.push(time+'/'+tempStr); + } + return { + schedule_weekday: programmingMode.slice(0, 6).join(' '), + schedule_holiday: programmingMode.slice(6, 8).join(' '), + }; + }, + to: async (v: string, meta: Tz.Meta) => { + const dpId = 109; + const payload:number[] = []; + let weekdayFormat: string; + let holidayFormat: string; + + if (meta.message.hasOwnProperty('schedule_weekday')) { + weekdayFormat = v; + holidayFormat = meta.state['schedule_holiday'] as string; + } else { + weekdayFormat = meta.state['schedule_weekday'] as string; + holidayFormat = v; + } + + function scheduleToRaw(key: string, input: string, number: number, payload: number[], meta: Tz.Meta) { + const items = input.trim().split(/\s+/); + + if (items.length != number) { + throw new Error('Wrong number of items for '+ key +' :' + items.length); + } else { + for (let i = 0; i < number; i++) { + const timeTemperature = items[i].split('/'); + if (timeTemperature.length != 2) { + throw new Error('Invalid schedule: wrong transition format: ' + items[i]); + } + const hourMinute = timeTemperature[0].split(':', 2); + const hour = parseInt(hourMinute[0]); + const minute = parseInt(hourMinute[1]); + const temperature = parseFloat(timeTemperature[1]); + + if (!utils.isNumber(hour) || !utils.isNumber(temperature) || !utils.isNumber(minute) || + hour < 0 || hour >= 24 || + minute < 0 || minute >= 60 || + temperature < 5 || temperature >= 35) { + throw new Error('Invalid hour, minute or temperature (5> 8) & 0xFF, + temperature10 & 0xFF, + ); + } + } + return; + } + + scheduleToRaw('schedule_weekday', weekdayFormat, 6, payload, meta); + scheduleToRaw('schedule_holiday', holidayFormat, 2, payload, meta); + + const entity = meta.device.endpoints[0]; + const sendCommand = utils.getMetaValue(entity, meta.mapped, 'tuyaSendCommand', undefined, 'dataRequest'); + await sendDataPointRaw(entity, dpId, payload, sendCommand, 1); + }, + }, TV02SystemMode: { to: async (v: number, meta: Tz.Meta) => { const entity = meta.device.endpoints[0];