From 412185eb97a4213dba89982eee7fb3dcf107ff70 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 9 Jan 2025 19:53:28 +0100 Subject: [PATCH 1/2] First draft Tuya TRV706 --- src/devices/tuya.ts | 82 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/tuya.ts | 63 ++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index 41523f8293813..0f33b5b3c8673 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -5451,6 +5451,88 @@ const definitions: DefinitionWithExtend[] = [ ], }, }, + { + fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE284_ltwbm23f' }], + model: 'TRV-706Z', + vendor: 'Tuya', + description: 'Thermostat Radiator Valve', fromZigbee: [tuya.fz.datapoints], + toZigbee: [tuya.tz.datapoints], + onEvent: tuya.onEventSetLocalTime, + configure: tuya.configureMagicPacket, + exposes: [ + e.battery(), + e.child_lock(), + e.position(), + e.window_detection(), + e.binary('window', ea.STATE, 'OPEN', 'CLOSE').withDescription('Window status closed or open '), + e.binary('valve_state', ea.STATE, 'OPEN', 'CLOSE').withDescription('Valve status closed or opened '), + e.climate() + .withLocalTemperatureCalibration(-5, 5, 0.1, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withSetpoint('current_heating_setpoint', 5, 30, 0.5, ea.STATE_SET) + .withPreset( ['off', 'antifrost', 'eco', 'comfort', 'program', 'valve_open'], + 'Off deactivates valve operation, ' + + 'the other modes presets when setting on 5º (antifrost), from 15º (eco) and from 20º (comfort). ' + + 'It is better to set directly the temperature to deactivate off mode.'), + e.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF').withDescription('Antifreeze function'), + e.enum('valve_status', ea.STATE_SET, ['valve_closed', 'valve_open']).withDescription('Off deactivates valve operation, ' + + 'the other modes presets when setting on 5º (antifrost), from 15º (eco) and from 20º (comfort). ' + + 'It is better to set directly the temperature to deactivate off mode.'), + ...tuya.exposes.scheduleAllDays(ea.STATE_SET, 'HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C HH:MM/C'), + e.comfort_temperature().withValueMin(15.5).withValueMax(30.0).withValueStep(0.5).withDescription('Comfort mode temperature'), + e.eco_temperature().withValueMin(5.5).withValueMax(20).withValueStep(0.5).withDescription('Eco mode temperature'), + e.holiday_temperature().withValueMin(5).withValueMax(15).withValueStep(0.5).withDescription('Antifreeze mode temperature'), + e.enum('display_brightness', ea.STATE_SET, ['high', 'medium', 'low']).withDescription('Display brightness'), + e.enum('screen_orientation', ea.STATE_SET, ['up', 'down']).withDescription('Screen orientation'), + e.enum('mode', ea.STATE_SET, ['comfort', 'eco']).withDescription( + 'Hysteresis - comfort > switches off/on exactly at reached ' + + 'temperature with valve smooth from 0 to 100%, eco > 0.5 degrees above or below, valve either 0 or 100%', + ), + ], + meta: { + tuyaDatapoints: [ + [2, 'preset', tuya.valueConverterBasic.lookup({ + 'off': tuya.enum(0), + 'antifrost': tuya.enum(1), + 'eco': tuya.enum(2), + 'comfort': tuya.enum(3), + 'program': tuya.enum(4), + 'valve_open': tuya.enum(5), + })], + [2, 'valve_status', tuya.valueConverterBasic.lookup({ + 'valve_closed': tuya.enum(0), + 'auto': tuya.enum(1), + 'auto': tuya.enum(2), + 'auto': tuya.enum(4), + 'auto': tuya.enum(3), + 'valve_open': tuya.enum(5), + }, 'auto')], + [3, 'valve_state', tuya.valueConverterBasic.lookup({ CLOSE: tuya.enum(0), OPEN: tuya.enum(1) })], + [4, 'current_heating_setpoint', tuya.valueConverter.divideBy10], + [5, 'local_temperature', tuya.valueConverter.divideBy10], + [6, 'battery', tuya.valueConverter.raw], + [7, 'child_lock', tuya.valueConverter.lockUnlock], + [14, 'window_detection', tuya.valueConverter.onOff], + [15, 'window', tuya.valueConverterBasic.lookup({ CLOSE: tuya.enum(0), OPEN: tuya.enum(1) })], + [47, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration1], + [102, 'schedule_monday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(1)], + [103, 'schedule_tuesday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(2)], + [104, 'schedule_wednesday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(3)], + [105, 'schedule_thursday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(4)], + [106, 'schedule_friday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(5)], + [107, 'schedule_saturday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(6)], + [108, 'schedule_sunday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(7)], + [111, 'display_brightness', tuya.valueConverterBasic.lookup({high: tuya.enum(0), medium: tuya.enum(1), low: tuya.enum(2)})], + [113, 'screen_orientation', tuya.valueConverterBasic.lookup({ up: tuya.enum(0), down: tuya.enum(1) })], + [114, 'position', tuya.valueConverter.divideBy10], + [119, 'comfort_temperature', tuya.valueConverter.divideBy10], //-- + [120, 'eco_temperature', tuya.valueConverter.divideBy10], //-- + [121, 'holiday_temperature', tuya.valueConverter.divideBy10], //-- + [122, 'frost_protection', tuya.valueConverter.onOff], //-- + [127, 'mode', tuya.valueConverterBasic.lookup({comfort: tuya.enum(0), eco: tuya.enum(1)})], + ], + }, + }, { zigbeeModel: ['TS0121'], model: 'TS0121_plug', diff --git a/src/lib/tuya.ts b/src/lib/tuya.ts index d5683dbb45fd6..e80247841c342 100644 --- a/src/lib/tuya.ts +++ b/src/lib/tuya.ts @@ -1106,6 +1106,69 @@ export const valueConverter = { }, }; }, + thermostatScheduleDayMultiDP_TRV706Z: { + from: (v: string) => { + const schedule = []; + for (let index = 1; index < 24; index = index + 4) { + const firstMinutesByte = (parseInt(v[index]) & 0x0f) << 8; + const secondMinutesByte = parseInt(v[index + 1]); + const minutesSinceMidnight = firstMinutesByte | secondMinutesByte; + + const firstTemperatureByte = (parseInt(v[index + 2]) & 0x0f) << 8; + const secondTemperatureByte = parseInt(v[index + 3]); + const temperature = (firstTemperatureByte | secondTemperatureByte) / 10.0; + + const hour = Math.floor(minutesSinceMidnight / 60); + const minutes = minutesSinceMidnight % 60; + + schedule.push( + String(hour).padStart(2, '0') + ':' + String(minutes).padStart(2, '0') + '/' + temperature.toFixed(1), + ); + } + return schedule.join(' '); + }, + to: (v: string) => { + const payload = []; + const transitions = v.split(' '); + if (transitions.length != 6) { + throw new Error('Invalid schedule: there should be 6 transitions'); + } + for (const transition of transitions) { + const timeTemp = transition.split('/'); + if (timeTemp.length != 2) { + throw new Error('Invalid schedule: wrong transition format: ' + transition); + } + const hourMin = timeTemp[0].split(':'); + const hour = parseInt(hourMin[0]); + const min = parseInt(hourMin[1]); + const temperature = Math.floor(parseFloat(timeTemp[1]) * 10); + if (hour < 0 || hour > 24 || min < 0 || min > 60 || temperature < 50 || temperature > 300) { + throw new Error('Invalid hour, minute or temperature of: ' + transition); + } + + const minutesSinceMidnight = hour * 60 + min; + + const firstByte = 0xa0 | (minutesSinceMidnight >> 8); + const secondByte = minutesSinceMidnight & 0xff; + const thirdByte = 0x40 | (temperature >> 8); + const fourthByte = temperature & 0xff; + + payload.push(firstByte, secondByte, thirdByte, fourthByte); + } + return payload; + }, + }, + thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber: (dayNum: number) => { + return { + from: (v: string) => valueConverter.thermostatScheduleDayMultiDP_TRV706Z.from(v), + to: (v: string) => { + const data = valueConverter.thermostatScheduleDayMultiDP_TRV706Z.to(v); + data.unshift(dayNum); + return data; + }, + }; + }, + tv02Preset: () => { return { from: (v: number) => { From 812dc0919844df6d114bf6b2a6ec9d036c71ac18 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 11 Jan 2025 15:38:22 +0100 Subject: [PATCH 2/2] Rename TRV-706Z to TRV-705ZB --- src/devices/tuya.ts | 19 +++++++++++-------- src/lib/tuya.ts | 8 ++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/devices/tuya.ts b/src/devices/tuya.ts index 0f33b5b3c8673..fff29749e76f2 100644 --- a/src/devices/tuya.ts +++ b/src/devices/tuya.ts @@ -5453,8 +5453,11 @@ const definitions: DefinitionWithExtend[] = [ }, { fingerprint: [{ modelID: 'TS0601', manufacturerName: '_TZE284_ltwbm23f' }], - model: 'TRV-706Z', + model: 'TRV-705ZB', vendor: 'Tuya', + // whiteLabel: [ + // {vendor: 'Tuya', model: 'TRV-706Z'}, + // ], description: 'Thermostat Radiator Valve', fromZigbee: [tuya.fz.datapoints], toZigbee: [tuya.tz.datapoints], onEvent: tuya.onEventSetLocalTime, @@ -5515,13 +5518,13 @@ const definitions: DefinitionWithExtend[] = [ [14, 'window_detection', tuya.valueConverter.onOff], [15, 'window', tuya.valueConverterBasic.lookup({ CLOSE: tuya.enum(0), OPEN: tuya.enum(1) })], [47, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration1], - [102, 'schedule_monday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(1)], - [103, 'schedule_tuesday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(2)], - [104, 'schedule_wednesday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(3)], - [105, 'schedule_thursday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(4)], - [106, 'schedule_friday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(5)], - [107, 'schedule_saturday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(6)], - [108, 'schedule_sunday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber(7)], + [102, 'schedule_monday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV705ZB_WithDayNumber(1)], + [103, 'schedule_tuesday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV705ZB_WithDayNumber(2)], + [104, 'schedule_wednesday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV705ZB_WithDayNumber(3)], + [105, 'schedule_thursday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV705ZB_WithDayNumber(4)], + [106, 'schedule_friday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV705ZB_WithDayNumber(5)], + [107, 'schedule_saturday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV705ZB_WithDayNumber(6)], + [108, 'schedule_sunday', tuya.valueConverter.thermostatScheduleDayMultiDP_TRV705ZB_WithDayNumber(7)], [111, 'display_brightness', tuya.valueConverterBasic.lookup({high: tuya.enum(0), medium: tuya.enum(1), low: tuya.enum(2)})], [113, 'screen_orientation', tuya.valueConverterBasic.lookup({ up: tuya.enum(0), down: tuya.enum(1) })], [114, 'position', tuya.valueConverter.divideBy10], diff --git a/src/lib/tuya.ts b/src/lib/tuya.ts index e80247841c342..cf266c67f9bad 100644 --- a/src/lib/tuya.ts +++ b/src/lib/tuya.ts @@ -1106,7 +1106,7 @@ export const valueConverter = { }, }; }, - thermostatScheduleDayMultiDP_TRV706Z: { + thermostatScheduleDayMultiDP_TRV705ZB: { from: (v: string) => { const schedule = []; for (let index = 1; index < 24; index = index + 4) { @@ -1158,11 +1158,11 @@ export const valueConverter = { return payload; }, }, - thermostatScheduleDayMultiDP_TRV706Z_WithDayNumber: (dayNum: number) => { + thermostatScheduleDayMultiDP_TRV705ZB_WithDayNumber: (dayNum: number) => { return { - from: (v: string) => valueConverter.thermostatScheduleDayMultiDP_TRV706Z.from(v), + from: (v: string) => valueConverter.thermostatScheduleDayMultiDP_TRV705ZB.from(v), to: (v: string) => { - const data = valueConverter.thermostatScheduleDayMultiDP_TRV706Z.to(v); + const data = valueConverter.thermostatScheduleDayMultiDP_TRV705ZB.to(v); data.unshift(dayNum); return data; },