[RELEASE] Tuya Zigbee Thermostats and TRVs driver

Hi,

Ive attached my driver so far. It does the switch functionality but cant get it to repeatedly show temperature. Can you send me your driver too?

/**

  • Custom Driver for Tuya Zigbee TS0001 with Temperature Input (_TZE21C_dohbhb5k)
  • Author: James
  • Updated: April 2025
    */

metadata {
definition(name: "TS0001 Relay + Temp (Tuya) - temp displaying 2", namespace: "custom", author: "James") {
capability "Actuator"
capability "Switch"
capability "Temperature Measurement"
capability "Configuration"
capability "Refresh"

    fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,EB00,0003,0004,0005,0006", outClusters:"000A,0019", model:"TS0001", manufacturer:"_TZE21C_dohbhb5k"
}

}

preferences {
input name: "debugLogging", type: "bool", title: "Enable debug logging", defaultValue: true
}

def installed() {
log.debug "Installed"
initialize()
}

def updated() {
log.debug "Updated"
initialize()
}

def initialize() {
if (debugLogging) runIn(1800, logsOff)
sendEvent(name: "switch", value: "off")
}

def logsOff() {
device.updateSetting("debugLogging", [value:"false", type:"bool"])
log.warn "Debug logging disabled"
}

def parse(String description) {
if (debugLogging) log.debug "Parsing: ${description}"

if (description?.startsWith("catchall:")) {
    def descMap = zigbee.parseDescriptionAsMap(description)
    def cluster = descMap.cluster instanceof String 
        ? Integer.parseInt(descMap.cluster, 16) 
        : descMap.cluster

    // 1) ZCL On/Off commands (physical button)
    if (cluster == 0x0006) {
        def cmd = descMap.command instanceof String 
            ? Integer.parseInt(descMap.command, 16) 
            : descMap.command
        def newState = (cmd == 0x01) ? "on" : "off"
        if (debugLogging) log.debug "On/Off cluster cmd 0x${String.format('%02X', cmd)} → ${newState}"
        sendEvent(name: "switch", value: newState)
        return
    }

    // 2) Tuya manufacturer‐specific (both UI and physical, plus temp)
    if (cluster == 0xEF00) {
        def tuya = zigbee.parseTuyaDataMap(descMap)
        if (!tuya) {
            if (debugLogging) log.debug "Failed to parse Tuya map: ${descMap}"
            return
        }
        def dp    = tuya.dataPoint as Integer
        def value = tuya.data      as Integer

        switch (dp) {
            // Relay on/off (UI toggle and physical button)
            case 0x01:  // some firmwares use DP01 for manual toggles
            case 0x02:  // DP02 is also common for relay
                def state = (value == 1) ? "on" : "off"
                if (debugLogging) log.debug "Tuya DP${String.format('%02X', dp)} (relay) → ${state}"
                sendEvent(name: "switch", value: state)
                break

            // Temperature reports:
            // DP0B, DP1B or DP1C depending on firmware
            case 0x0B:
            case 0x1B:
            case 0x1C:
                def tempC = (value / 10.0).round(1)
                if (debugLogging) log.debug "Tuya DP${String.format('%02X', dp)} (temp) → ${tempC} °C"
                sendEvent(name: "temperature", value: tempC, unit: "°C")
                break

            default:
                if (debugLogging) log.debug "Unhandled Tuya DP ${String.format('%02X', dp)} → ${value}"
        }
        return
    }
}

// 3) readAttr fallback for refresh()
if (description?.startsWith("read attr -")) {
    def descMap = zigbee.parseDescriptionAsMap(description)
    if (descMap.cluster == "0006" && descMap.attrId == "0000") {
        def relayState = (descMap.value == "01") ? "on" : "off"
        if (debugLogging) log.debug "On/Off attribute read → ${relayState}"
        sendEvent(name: "switch", value: relayState)
    }
}

}

// Tuya on/off control:
def on() {
sendTuyaCommand(2, 0x04, [0x01]) // thermostatMode=heat (relay ON)
sendEvent(name: "switch", value: "on")
}

def off() {
sendTuyaCommand(2, 0x04, [0x02]) // thermostatMode=off (relay OFF)
sendEvent(name: "switch", value: "off")
}

def sendTuyaCommand(dp, dp_type, dp_value) {
def dpHex = zigbee.convertToHexString(dp, 2)
def dpTypeHex = zigbee.convertToHexString(dp_type, 2)
def dpLenHex = zigbee.convertToHexString(dp_value.size(), 4)
def dpValHex = dp_value.collect { zigbee.convertToHexString(it, 2) }.join()

// Add random transaction ID for Tuya commands
def transId = zigbee.convertToHexString(new Random().nextInt(256), 2) + zigbee.convertToHexString(new Random().nextInt(256), 2)

def commandPayload = transId + dpHex + dpTypeHex + dpLenHex + dpValHex
def cmd = "he cmd 0x${device.deviceNetworkId} 0x01 0xEF00 0x00 {${commandPayload}}"

if (debugLogging) log.debug "Sending Tuya Command: ${cmd}"
sendHubCommand(new hubitat.device.HubAction(cmd, hubitat.device.Protocol.ZIGBEE))

}

def refresh() {
if (debugLogging) log.debug "Refresh called"
return zigbee.readAttribute(0x0006, 0x0000)
}

def configure() {
if (debugLogging) log.debug "Configuring..."
return refresh()
}

1 Like