[RELEASE] Tuya Zigbee Thermostats and TRVs driver

EndpointId 02 is unusual for Tuya Thermostats…. This explains why nothing is working with this device.

I will need the full info for all endpoints - can you follow the instructions in this post :

The output should be similar to the screenshot on the next post in the link.

Endpoint 01 endpointId: 01
Endpoint 01 idAsInt: 1
Endpoint 01 inClusters: 0000,0001,0003,0004,0005,0006,000A,000B,0201,0B05,FC80,FC81
Endpoint 01 initialized: true
Endpoint 01 model: TRV602WZ
Endpoint 01 outClusters: 0003,000A,0019
Endpoint 01 profileId: 0104
Endpoint 01 stage: 4
Endpoint 02 endpointId: 02
Endpoint 02 idAsInt: 2
Endpoint 02 inClusters: 0000
Endpoint 02 initialized: true
Endpoint 02 outClusters: 0019
Endpoint 02 profileId: 0104
Endpoint 02 stage: 4
1 Like

Thank you.

This device is not built on a typical Tuya-OS platform… Did you try the HE inbuilt Generic Zigbee Thermostat driver?

1 Like

That's the first thing I tried, HE seems to be able to read the battery level, but nothing else happens.

Ok, I have some ideas to try when I have the time next days, but I can’t promise anything. Unfortunately, I can’t find any information for this device on GitHub for now.

I'm just outside of the return window, but I guess I could get different thermostats if necessary. I was pretty surprised this is an entirely new implementation rather than another clone of something else.

The problem with this device is very similar to this one :

My idea is to use the same approach/workaround with this device. Is it this one? Amazon

Yes, this is the one, I got it from here: IMOU Radiator thermostat, ZigBee | Radiator thermostats at very favourable prices | reichelt elektronik

I tried the Aqara W100 driver, and it shows the battery level but no commands seem to go through, and there's nothing in the logs when I operate the knob.

That's a very good price, and this IOT-TRV1-EU looks very promising!
Keep it, we should be able to make it work in HE.

It's also very very quiet, so if we can make this work, that's awesome. Do you need to get your hands on one, or is there more data I can collect?

1 Like

I've already ordered one.. (I need a replacement for one of my Moes BRT-100 TRVs, so I will use it in practice during the upcoming winter season).

You can try the new driver update ver. 3.5.3 2025-10-03 (dev. branch)

  • added TS0601 _TZE204_lpedvtvr into a new 'MOES_RING_THERMOSTAT' profile

Update the driver manually, then select 'LOAD ALL DEFAULTS' from the Configure command.
Refresh the device web page.

While waiting for my IMOU IOT-TRV1-EU to arrive (the shipping is not confirmed yet), you can help with preparing the driver in advance providing the data from this great magic tool :

On line line 14 change the boolean to true for best experience :

@Field static final Boolean ENGLISH_PLEASE = true

See a sample of the device intervew data genarated here.

Let me know if this has what you need

KA Imou TRV1

Node Descriptor

▸ Logical Type = Zigbee End Device (ZED)
▸ Complex Descriptor Available = No
▸ User Descriptor Available = No
▸ Fragmentation Supported (R23) = No
▸ Frequency Band = Reserved
▸ Alternate PAN Coordinator = No
▸ Device Type = Reduced Function Device (RFD)
▸ Mains Power Source = No
▸ Receiver On When Idle = No (conserve power during idle periods)
▸ Security Capability = No
▸ Allocate Address = Yes
▸ Manufacturer Code = 0x1329
▸ Maximum Buffer Size = 82 bytes
▸ Maximum Incoming Transfer Size = 82 bytes
▸ Primary Trust Center = No
▸ Backup Trust Center = No
▸ Primary Binding Table Cache = Yes
▸ Backup Binding Table Cache = No
▸ Primary Discovery Cache = Yes
▸ Backup Discovery Cache = Yes
▸ Network Manager = Yes
▸ Maximum Outgoing Transfer Size = 82 bytes
▸ Extended Active Endpoint List Available = No
▸ Extended Simple Descriptor List Available = No

Power Descriptor

▸ Current Power Mode = Same as "Receiver On When Idle" from "Node Descriptor" section above
▸ Available Power Sources = [Constant (mains) power]
▸ Current Power Sources = [Constant (mains) power]
▸ Current Power Source Level = 100%

Endpoint 0x01

Out Cluster: 0x0003 (Identify Cluster)

▸ 0x00 | Identify | req

Out Cluster: 0x000A (Time Cluster)

▸ No generated commands

Out Cluster: 0x0019 (OTA Upgrade Cluster)

▸ No generated commands

In Cluster: 0x0000 (Basic Cluster)

▸ 0x0000 | ZCL Version | req | r-- | uint8 | -- | --
▸ 0x0001 | Application Version | opt | r-- | uint8 | -- | --
▸ 0x0003 | HW Version | opt | r-- | uint8 | -- | --
▸ 0x0004 | Manufacturer Name | opt | r-- | string | -- | --
▸ 0x0005 | Model Identifier | opt | r-- | string | -- | --
▸ 0x0006 | Date Code | req | r-- | string | -- | --
▸ 0x0007 | Power Source | opt | r-- | enum8 | -- | --
▸ 0x000A | Product Code | opt | r-- | octstr | -- | --
▸ 0x0012 | Device Enabled | opt | rw- | bool | -- | --
▸ 0x4000 | SW Build ID | opt | r-- | string | -- | --
▸ 0xFFFD | Cluster Revision | req | r-- | uint16 | -- | --

▸ No received commands

In Cluster: 0x0001 (Power Configuration Cluster)

▸ No attributes

▸ No received commands

In Cluster: 0x0003 (Identify Cluster)

▸ No attributes

▸ No received commands

In Cluster: 0x0004 (Groups Cluster)

▸ No attributes

▸ No received commands

In Cluster: 0x0005 (Scenes Cluster)

▸ No attributes

▸ No received commands

In Cluster: 0x0006 (On/Off Cluster)

▸ No attributes

▸ No received commands

In Cluster: 0x000A (Time Cluster)

▸ No attributes

▸ No received commands

In Cluster: 0x000B (RSSI Cluster)

▸ 0x0000 | Location Type | req | rw- | -- | 00 | --
▸ 0x0001 | Location Method | req | rw- | -- | 01 | --
▸ 0x0010 | Coordinate 1 | opt | rw- | -- | 0064 | --
▸ 0x0011 | Coordinate 2 | opt | rw- | -- | 0064 | --
▸ 0x0012 | Coordinate 3 | opt | rw- | -- | 0064 | --
▸ 0x0013 | Power | req | rw- | -- | 0001 | --
▸ 0x0014 | Path Loss Exponent | req | rw- | -- | 0001 | --
▸ 0x0017 | Number RSSI Measurements | opt | rw- | -- | 01 | --
▸ 0xFFFD | Cluster Revision | req | r-- | [33] | 0001 = 1 | --

▸ 0x00 | Set Absolute Location | req
▸ 0x01 | Set Device Configuration | req
▸ 0x02 | Get Device Configuration | req
▸ 0x03 | Get Location Data | req
▸ 0x04 | RSSI Response | opt
▸ 0x05 | Send Pings | opt
▸ 0x06 | Anchor Node Announce | opt

In Cluster: 0x0201 (Thermostat Cluster)

▸ 0x0000 | Local Temperature | req | r-p | int16 | 073A = 18.5°C | --
▸ 0x0012 | Occupied Heating Setpoint | req | rws | int16 | 0640 = 16°C | --
▸ 0x0015 | Min Heat Setpoint Limit | opt | rw- | int16 | 01F4 = 5°C | --
▸ 0x0016 | Max Heat Setpoint Limit | opt | rw- | int16 | 0DAC = 35°C | --
▸ 0x001B | Control Sequence Of Operation | req | rw- | enum8 | 02 = Heating Only | --
▸ 0x001C | System Mode | req | rws | enum8 | 04 = Heat | --
▸ 0x001E | Thermostat Running Mode | opt | r-- | enum8 | 00 = Off | --
▸ 0xFFFD | Cluster Revision | req | r-- | uint16 | 0003 = 3 | --

▸ 0x00 | Setpoint Raise/Lower | req
▸ 0x01 | Set Weekly Schedule | opt
▸ 0x02 | Get Weekly Schedule | opt
▸ 0x03 | Clear Weekly Schedule | opt
▸ 0x04 | Get Relay Status Log | opt

In Cluster: 0x0B05 (Diagnostics Cluster)

▸ 0x011C | Last Message LQI | opt | r-- | uint8 | 88 = 136 | --
▸ 0x011D | Last Message RSSI | opt | r-- | int8 | B4 = -52 | --
▸ 0xFFFD | Cluster Revision | req | r-- | uint16 | 0003 = 3 | --

▸ No received commands

In Cluster: 0xFC80 (Unknown Cluster)

▸ No attributes

▸ No received commands

In Cluster: 0xFC81 (Unknown Cluster)

▸ No attributes

▸ No received commands

Endpoint 0x02

Out Cluster: 0x0019 (OTA Upgrade Cluster)

▸ No generated commands

In Cluster: 0x0000 (Basic Cluster)

▸ No attributes

▸ No received commands

Endpoint 0xF2

Bindings Table

▸ Could not retrieve data

Neighbors Table

▸ Addr:0000 | Type:Zigbee Coordinator | RxOnWhenIdle:Yes | Rel:Parent | Permit Joining:Unknown | Depth:0

Routing Table

▸ Could not retrieve data

1 Like

That's exactly the data needed! :slight_smile: Thank you.

Tried the driver and found some easy bugs:

  • DataPoint 01 is missing the DataType '01', which prevents from turning ON - OFF
  • DP:2 preset gives an error about ThermostatMode not present and modes are not working. Simply changing the attribute name to thermostatMode makes the mode changing functional.

Corrected lines if you want to correct in the next release:

693 [dp:1, name:'systemMode', type:'enum', dt: '01', rw: 'rw', defVal:'1', map:[0: 'off', 1: 'heat'], title:'Thermostat Switch', description:'Thermostat switch (system mode)'],
694 [dp:2, name:'thermostatMode', type:'enum', rw: 'rw', defVal:'0', map:[0:'heat', 1:'temporary manual', 2:'auto', 3:'eco'], description:'Preset mode'],

Thank you for the addition!

2 Likes

Hi,

I bought one of these Tuya Thermostats (wanted a battery Zigbee thermostat). I'm using it to control a ventilation system to cool a room down using outside air (therefore the thermostat is set to 'Cool' mode rather than 'Heat'):

BOT-R15W-Zigbee

I have the driver working successfully in terms of setting the temperature, reading the battery level, temperature, etc. however I'm struggling to get the thermostatMode changes to be reflected (e.g. when I select 'Off' on the GUI, the stat doesn't turn off and the same for selecting 'Cool'), how can I help troubleshoot this please.

Below is the Driver code snippet that I've adapted for this so far:

    // BOT-R15W-Zigbee (_TZE284_agcxaw3f)
	// Based on Z2M DPs: https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/src/devices/beok.ts
	'BOT_R15W_ZB_THERMOSTAT' : [
    description   : 'Beok BOT-R15W Zigbee Thermostat',
    device        : [models: ['TS0601'], type: 'Thermostat', powerSource: 'battery', isSleepy:false],
    capabilities  : ['ThermostatCoolingSetpoint': true, 'ThermostatMode': true, 'ThermostatSetpoint': true, 'TemperatureMeasurement': true],
    preferences   : ['childLock':'26'],
    fingerprints  : [
        [profileId:'0104', endpointId:'01', inClusters:'0000,0004,0005,EF00,ED00', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE284_agcxaw3f', deviceJoinName: 'Beok BOT-R15W Thermostat']
    ],
    commands      : ['sendSupportedThermostatModes':'sendSupportedThermostatModes', 'setCoolingSetpoint':'setCoolingSetpoint', 'resetStats':'resetStats', 'refresh':'refresh', 'initialize':'initialize', 'updateAllPreferences': 'updateAllPreferences', 'resetPreferencesToDefaults':'resetPreferencesToDefaults', 'validateAndFixPreferences':'validateAndFixPreferences'],
    tuyaDPs       : [
        [dp:2,   name:'coolingSetpoint',    type:'decimal', rw: 'rw', min:5.0, max:60.0, defVal:20.0, step:0.5, scale:10, unit:'°C', title:'<b>Cooling Setpoint</b>', description:'Target temperature'],
        [dp:3,   name:'temperature',        type:'decimal', rw: 'ro', min:0, max:60.0, defVal:20.0, step:0.1, scale:10, unit:'°C', description:'Current room temperature'],
		[dp:1, 	 name:'thermostatMode',  	type:'enum',  	rw: 'rw', min:0, max:1, defVal:'1', step:1, scale:1, map:[0:'off', 1:'cool'], unit:'', title:'<b>Thermostat Mode</b>', description:'Thermostat mode (cool-only remapped)'],
        [dp:19,  name:'preset',             type:'enum',    rw: 'rw', min:0, max:1, defVal:'0', step:1, scale:1, map:[0:'manual', 1:'program'], unit:'', description:'Preset mode'],
		[dp:101, name:'thermostatOperatingState', type:'enum', rw:'ro', map:[0:'idle', 1:'cooling'], description:'Thermostat operating state (idle or actively cooling)'],
        [dp:26,  name:'childLock',          type:'enum',    rw: 'rw', min:0, max:1, defVal:'0', step:1, scale:1, map:[0:'off', 1:'on'], unit:'', title:'<b>Child Lock</b>', description:'Child lock'],
        [dp:27,  name:'schedule',           type:'raw',     rw: 'rw', defVal:null, description:'Weekly Schedule'],
		[dp:113, name:'battery', type:'number', rw:'ro', min:0, max:100, defVal:100, step:1, scale:1, unit:'%', description:'Battery Level'],
    ],
    supportedThermostatModes: ['off', 'cool'],
    refresh: ['pollTuya'],
    configuration : [:]
    ],

Best wishes,
Guy

1 Like

For anyone still trying to get this to work, this is the working driver code below that I have working to get the temp. Picture below of my unit below too

/**
 *  Tuya TS000F Temperature Relay (Multi-Endpoint EF00 Decoder)
 */

metadata {
    definition(name: "TS000F Temp Relay (Multi-EP)", namespace: "chatgpt.custom", author: "ChatGPT") {

        capability "Switch"
        capability "Temperature Measurement"
        capability "Refresh"
        capability "Configuration"

        // MAIN fingerprint
        fingerprint profileId:"0104",
            inClusters:"0000,0003,0004,0005,0006,E001,E000,EF00",
            outClusters:"000A,0019",
            manufacturer:"_TZ3218_7fiyo3kv",
            model:"TS000F"

        // Extra fingerprints to ensure Hubitat binds EF00 to this driver
        fingerprint endpointId:"02", profileId:"0104",
            inClusters:"0000,0003,0004,0005,0006,E001,E000,EF00",
            outClusters:"000A,0019",
            manufacturer:"_TZ3218_7fiyo3kv",
            model:"TS000F"

        fingerprint endpointId:"03", profileId:"0104",
            inClusters:"0000,0003,0004,0005,0006,E001,E000,EF00",
            outClusters:"000A,0019",
            manufacturer:"_TZ3218_7fiyo3kv",
            model:"TS000F"
    }
}

preferences {
    input "debugLogging", "bool", title:"Enable debug logging", defaultValue:true
    input "useF", "bool", title:"Use Fahrenheit?", defaultValue:false
}

def logDebug(msg){ if(debugLogging) log.debug msg }

def parse(String desc) {
    logDebug "RAW: ${desc}"

    // Do NOT filter EF00 by endpoint — accept everything
    if (!desc.contains("EF00")) return

    def map = zigbee.parseDescriptionAsMap(desc)
    if (!map?.data) return

    def raw = map.data
    if (raw.size() < 6) return

    //---------------------------------------------------------
    // Decode Temperature from last 2 bytes
    //---------------------------------------------------------
    try {
        def tail = raw[-4..-1].join()
        def tempRaw = Integer.parseInt(tail, 16)
        def tempC = tempRaw / 10.0

        def out = tempC
        def unit = "°C"
        if (useF) {
            out = (tempC * 1.8 + 32).toDouble().round(1)
            unit = "°F"
        }

        sendEvent(name:"temperature", value: out, unit: unit)
        logDebug "Temperature decoded → ${out}${unit}"
    } catch (e) {
        logDebug "Temp decode failed: ${e}"
    }

    //---------------------------------------------------------
    // Relay state based on last byte
    //---------------------------------------------------------
    def lastByte = raw[-1]
    if (lastByte == "01") {
        sendEvent(name:"switch", value:"on")
        logDebug "Relay → ON"
    }
    if (lastByte == "00") {
        sendEvent(name:"switch", value:"off")
        logDebug "Relay → OFF"
    }
}

def on()  { sendRelayCmd(1) }
def off() { sendRelayCmd(0) }

def sendRelayCmd(val) {
    def payload = "00001F010001" + zigbee.convertToHexString(val, 2)
    logDebug "Send Relay Cmd: ${payload}"
    sendHubCommand(new hubitat.device.HubAction(
        "he cmd 0x${device.deviceNetworkId} 0x01 0xEF00 0x00 {${payload}}",
        hubitat.device.Protocol.ZIGBEE))
}

def refresh() {
    logDebug "Refresh"
    zigbee.readAttribute(0x0006, 0x0000)
}

def configure() {
    logDebug "Configure"
}
1 Like

Hi, I have the Moes 006 Series electrical floor thermostat. It won't heat from command, but I can set the temperature. On the unit itself I have to push the leaf button to turn it off then on again before it recognises that te set temperature is above the current temperature and starts heating.

I am now looking at other thermostats. Is there a list with working wall thermostat models on Zigbee I can best choose from?
This one looks good TGM50-ZB-EP and so does the Moes Star Ring Smart Knob Thermostat and the Aqara W500. Thank you in advance.