Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New device support]: Tuya _TZE204_eekpf0ft TS0601 TR-M3Z ZigBee Radiator valve with thermostat #24706

Open
RainoPikkarainen opened this issue Nov 10, 2024 · 4 comments
Labels
new device support New device support request

Comments

@RainoPikkarainen
Copy link

RainoPikkarainen commented Nov 10, 2024

Link

https://www.aliexpress.com/item/1005007412184036.html

Database entry

{"id":40,"type":"EndDevice","ieeeAddr":"0xa4c138e61f406c8e","nwkAddr":27112,"manufId":4417,"manufName":"_TZE204_eekpf0ft","powerSource":"Battery","modelId":"TS0601","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":81,"inClusterList":[0,4,5,61184],"outClusterList":[25,10],"clusters":{"genBasic":{"attributes":{"65487":14400,"65503":"�z�.i","65506":56,"65508":0,"65534":0,"stackVersion":0,"dateCode":"","appVersion":74,"modelId":"TS0601","manufacturerName":"_TZE204_eekpf0ft","powerSource":3,"zclVersion":3,"hwVersion":1}}},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":74,"stackVersion":0,"hwVersion":1,"dateCode":"","zclVersion":3,"interviewCompleted":true,"meta":{"configured":332242049},"lastSeen":1731239207933}

Zigbee2MQTT version

1.41.0

Comments

Ordered from AliExress, didn't work out of the box, so tried converters from https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/src/devices/tuya.ts.

External definition based on "fingerprint: tuya.fingerprint('TS0601', ['_TZE204_pcdmj88b']), model: 'TS0601_thermostat_4'"

External definition

const exposes = require('zigbee-herdsman-converters/lib/exposes');
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const e = exposes.presets;
const ea = exposes.access;

const fixedValueConverter = {
    thermostatScheduleDayMultiDP: (numberOfSchedules = 4) => {
        return {
            from: (v) => {
                const schedule = [];
                for (let index = 1; index < 4 * numberOfSchedules + 1; index = index + 4) {
                    schedule.push(
                        String(parseInt(v[index+0])).padStart(2, '0') + ':' +
                        String(parseInt(v[index+1])).padStart(2, '0') + '/' +
                        // @ts-ignore
                        (parseFloat((v[index+2] << 8) + v[index+3]) / 10.0).toFixed(1),
                    );
                }
                return schedule.join(' ');
            },
            to: (v) => {
                const payload = [0];
                const transitions = v.split(' ');
                if (transitions.length != numberOfSchedules) {
                    throw new Error(`Invalid schedule: there should be ${numberOfSchedules} 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);
                    }
                    payload.push(
                        hour,
                        min,
                        (temperature & 0xff00) >> 8,
                        temperature & 0xff,
                    );
                }
                return payload;
            },
        }
    },
    thermostatScheduleDayMultiDPWithDayNumber: (dayNum, numberOfSchedules = 4) => {
        return {
            from: (v) => fixedValueConverter.thermostatScheduleDayMultiDP(numberOfSchedules).from(v),
            to: (v) => {
                const data = fixedValueConverter.thermostatScheduleDayMultiDP(numberOfSchedules).to(v);
                data[0] = dayNum;
                return data;
            },
        };
    }
};


const definition = {
    zigbeeModel: ['TS0601'],
    model: 'TR-M3Z', // Update this with the real model of the device (written on the device itself or product page)
    vendor: 'Tuya', // Update this with the real vendor of the device (written on the device itself or product page)
    description: 'Tuya ZigBee3.0 Thermostatic Radiator Valve Actuator', // Description of the device, copy from vendor site. (only used for documentation and startup logging)
    extend: [],
    fromZigbee: [tuya.fz.datapoints], // We will add this later
    toZigbee: [tuya.tz.datapoints], // Should be empty, unless device can be controlled (e.g. lights, switches).
    onEvent: tuya.onEventSetTime,
    configure: tuya.configureMagicPacket,
    exposes: [
        e.child_lock(),
        e.battery(),
        e.battery_low(),
        e
            .climate()
            .withSetpoint('current_heating_setpoint', 5, 35, 0.5, ea.STATE_SET)
            .withLocalTemperature(ea.STATE)
            .withPreset(['manual', 'schedule', 'eco', 'comfort', 'frost_protection' ,'holiday', 'off'])
            .withSystemMode(['off', 'heat'], ea.STATE)
            .withLocalTemperatureCalibration(-3, 3, 1, ea.STATE_SET),
        ...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.holiday_temperature().withValueMin(5).withValueMax(30),
        e.comfort_temperature().withValueMin(5).withValueMax(30),
        e.eco_temperature().withValueMin(5).withValueMax(30),
        e
            .binary('scale_protection', ea.STATE_SET, 'ON', 'OFF')
            .withDescription(
                'If the heat sink is not fully opened within ' +
                    'two weeks or is not used for a long time, the valve will be blocked due to silting up and the heat sink will not be ' +
                    'able to be used. To ensure normal use of the heat sink, the controller will automatically open the valve fully every ' +
                    'two weeks. It will run for 30 seconds per time with the screen displaying "Ad", then return to its normal working state ' +
                    'again.',
            ),
        e
            .binary('frost_protection', ea.STATE_SET, 'ON', 'OFF')
            .withDescription(
                'When the room temperature is lower than 5 °C, the valve opens; when the temperature rises to 8 °C, the valve closes.',
            ),
        e.numeric('error', ea.STATE).withDescription('If NTC is damaged, "Er" will be on the TRV display.'),
        e.binary('boost_heating', ea.STATE_SET, 'ON', 'OFF').withDescription('Boost Heating: the device will enter the boost heating mode.'),
    ], // Defines what this device exposes, used for e.g. Home Assistant discovery and in the frontend
    meta: {
        tuyaDatapoints: [
            [
                2,
                'preset',
                tuya.valueConverterBasic.lookup({
                    manual: tuya.enum(0),
                    schedule: tuya.enum(1),
                    eco: tuya.enum(2),
                    comfort: tuya.enum(3),
                    frost_protection: tuya.enum(4),
                    holiday: tuya.enum(5),
                    off: tuya.enum(6),
                }),
            ],
            [4, 'current_heating_setpoint', tuya.valueConverter.divideBy10],
            [5, 'local_temperature', tuya.valueConverter.divideBy10],
            [6, 'battery', tuya.valueConverter.raw],
            [7, 'child_lock', tuya.valueConverter.lockUnlock],
            [21, 'holiday_temperature', tuya.valueConverter.divideBy10],
            [24, 'comfort_temperature', tuya.valueConverter.divideBy10],
            [25, 'eco_temperature', tuya.valueConverter.divideBy10],
            [28, 'schedule_monday', fixedValueConverter.thermostatScheduleDayMultiDPWithDayNumber(1, 6)],
            [29, 'schedule_tuesday', fixedValueConverter.thermostatScheduleDayMultiDPWithDayNumber(2, 6)],
            [30, 'schedule_wednesday', fixedValueConverter.thermostatScheduleDayMultiDPWithDayNumber(3, 6)],
            [31, 'schedule_thursday', fixedValueConverter.thermostatScheduleDayMultiDPWithDayNumber(4, 6)],
            [32, 'schedule_friday', fixedValueConverter.thermostatScheduleDayMultiDPWithDayNumber(5, 6)],
            [33, 'schedule_saturday', fixedValueConverter.thermostatScheduleDayMultiDPWithDayNumber(6, 6)],
            [34, 'schedule_sunday', fixedValueConverter.thermostatScheduleDayMultiDPWithDayNumber(7, 6)],
            [35, 'fault_alarm', tuya.valueConverter.errorOrBatteryLow],
            [36, 'frost_protection', tuya.valueConverter.onOff],
            [37, 'boost_heating', tuya.valueConverter.onOff],
            [39, 'scale_protection', tuya.valueConverter.onOff],
            [47, 'local_temperature_calibration', tuya.valueConverter.localTempCalibration2],
            [49, 'system_mode', tuya.valueConverterBasic.lookup({off: tuya.enum(0), heat: tuya.enum(1)})],
        ],
    },
};

module.exports = definition;

What does/doesn't work with the external definition?

Works:

  • child lock
  • battery
  • current heating setpoint
  • local temperature
  • preset ('manual', 'schedule', 'eco', 'comfort', 'frost_protection' ,'holiday', 'off')
  • schedules (6 schedule points per day)
  • preset holiday temperature
  • preset confort temperature
  • preset eco temperature

Untested:

  • boost heating
  • frost protection
  • scale protection
  • local temperature calibration
@RainoPikkarainen RainoPikkarainen added the new device support New device support request label Nov 10, 2024
@RainoPikkarainen
Copy link
Author

TZE204_eekpf0ft_512x512_bg

@boboc5
Copy link

boboc5 commented Nov 18, 2024

Hello Raino ! did you manage to make it to fully work ? I also bought the same thermostat valve 2 weeks ago and I tried it in ZHA but is not working correctly. With an external quirk (ME167) I managed to make it to show me the current temperature and the target temperature, and the child lock was working, but nothing more. I ordered another zigbee coordinator to be able to use it with zigbee2mqtt and I'm waiting to see if it will be ok.

@RainoPikkarainen
Copy link
Author

I believe most features available in this device are working. Certainly current_heating_setpoint and local_temperature are working, as are the scheduled times/temperatures and other presets.

  • The boost heating is not working
    • or maybe that feature is just not there in the device, no mention of it in the manual
    • I think this feature would just turn the valve open regardless of any target or ambient temperatures
    • for use in automations, I would just set the current_heating_setpoint to 35 when "on" and 5 (or preset "off") when "off"
  • running_state in the state seems to always be "idle"
  • system_mode can be set to "heat"/"off" via a climate control card
    • but it has no effect, as far as I know
    • eg. if in preset "manual", changing current_heating_setpoint will trigger the valve even when system_mode is "off"

Once it gets a bit colder outside, I could test the "window detection", and if I could find an extra radiator valve, I could try connecting it to the device and test the "frost protection" (I think the device wants to calibrate with the valve before functioning, so it cannot just be tossed in a freezer).

@RainoPikkarainen
Copy link
Author

TR-M3Z-manual

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new device support New device support request
Projects
None yet
Development

No branches or pull requests

2 participants