Tuya Zigbee water/gas valve

Hi guys!

I just bought two Tuya Zigbee water/gas valves:

As per Hubitat’s documentation, the driver for Tuya Zigbee water valve should be “Sinope water valve”. However, it does not work as expected.

The valve does not react to any command that I send from the driver and no "Current States" or "State Variables" is shown.

If anyone had any experience with this driver and/or valve, any help will be welcome!

This is log of the discovery process - the only log that was generated:

sys:12021-08-23 12:09:20.011 Zigbee Discovery Stopped

dev:132021-08-23 12:08:46.138 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:43.381 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:40.628 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:37.875 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:35.196 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:35.111 infoZigbee parsed:[raw:D94C01000026DFFF420F1D3EB628131D3EB628121E3EB62812, dni:D94C, endpoint:01, cluster:0000, size:26, attrId:FFDF, encoding:42, command:0A, value:>¶(>¶(>¶(, clusterInt:0, attrInt:65503]

dev:132021-08-23 12:08:34.826 infoZigbee parsed:[raw:D94C0100001801002044E2FF201FE4FF2000, dni:D94C, endpoint:01, cluster:0000, size:18, attrId:0001, encoding:20, command:0A, value:44, clusterInt:0, attrInt:1, additionalAttrs:[[value:1F, encoding:20, attrId:FFE2, consumedBytes:4, attrInt:65506], [value:00, encoding:20, attrId:FFE4, consumedBytes:4, attrInt:65508]]]

dev:132021-08-23 12:08:32.363 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:30.674 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 24 01 001A, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:24, direction:01, data:[00, 1A]]

dev:132021-08-23 12:08:29.613 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:26.857 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:24.093 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:132021-08-23 12:08:23.431 infofingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0004,0005,EF00", outClusters:"0019,000A", model:"TS0601", manufacturer:"_TZE200_vrjkcam9"

dev:132021-08-23 12:08:23.322 traceZCL version:03

dev:132021-08-23 12:08:23.313 traceSoftware Build Id:unknown

dev:132021-08-23 12:08:23.306 traceModel:TS0601

dev:132021-08-23 12:08:23.302 traceManufacturer:_TZE200_vrjkcam9

dev:132021-08-23 12:08:23.199 debuggetting info for unknown Zigbee device...

dev:132021-08-23 12:08:21.348 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 D94C 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:D94C, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

sys:12021-08-23 12:08:21.208 Created Unknown Zigbee Device

dev:132021-08-23 12:08:21.174 debugconfigure() called...

sys:12021-08-23 12:08:20.459 Initializing Zigbee Device 847127FFFE74820D, D94C

sys:12021-08-23 12:08:20.005 Zigbee Discovery Running

1 Like

Did you try?
https://raw.githubusercontent.com/markus-li/Hubitat/release/drivers/expanded/zigbee-tuya-valve-expanded.groovy

I have the unfancy type of valve. Yours appears to be the new 2021 style
This is mine
image

2 Likes

Also did you hit configure after saving settings?

1 Like

Yes - no success.

Using Markus' drivers there's no "configure" command. I tried the However, "refresh" - no success.

I tried the "configure" using "DEVICE" driver - no success again.

Additionally, I did not notice that the following log entries are being produced (repeatedly):

With the "DEVICE" driver:

dev:162021-08-23 15:57:35.728 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:162021-08-23 15:57:32.973 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:162021-08-23 15:57:30.223 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:162021-08-23 15:57:27.470 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:162021-08-23 15:57:24.715 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:162021-08-23 15:57:21.964 infoZigbee parsed:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

With the driver you suggested:>

dev:162021-08-23 16:06:02.349 infoUnhandled Event IGNORE THIS - description:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101 | msgMap:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:162021-08-23 16:05:59.598 infoUnhandled Event IGNORE THIS - description:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101 | msgMap:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

dev:162021-08-23 16:05:56.840 infoUnhandled Event IGNORE THIS - description:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101 | msgMap:[raw:catchall: 0104 EF00 01 01 0040 00 33D2 01 00 0000 01 01 00000101000101, profileId:0104, clusterId:EF00, clusterInt:61184, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:true, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 01, 01, 00, 01, 01]]

When I disabled the "Info Logging" option the messages stopped.

When, using Markus drivers, the "Debug logging" option is enabled:

dev:162021-08-23 16:09:14.794 debugsendZigbeeCommands(cmd=[he raw 0x33D2 1 0x01 0x0006 {10 00 00 00 00}, delay 2000])

dev:162021-08-23 16:09:14.791 debugIgnore Cluster 0006 catchall - description:catchall: 0104 0006 01 01 0040 00 33D2 00 00 0000 01 01 000086 | parseMap:[raw:catchall: 0104 0006 01 01 0040 00 33D2 00 00 0000 01 01 000086, profileId:0104, clusterId:0006, clusterInt:6, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:false, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 86]]

dev:162021-08-23 16:09:14.772 debugsendZigbeeCommands(cmd=[he raw 0x33D2 1 0x01 0x0006 {10 00 00 00 00}, delay 2000])

dev:162021-08-23 16:09:14.769 debugIgnore Cluster 0006 catchall - description:catchall: 0104 0006 01 01 0040 00 33D2 00 00 0000 01 01 000086 | parseMap:[raw:catchall: 0104 0006 01 01 0040 00 33D2 00 00 0000 01 01 000086, profileId:0104, clusterId:0006, clusterInt:6, sourceEndpoint:01, destinationEndpoint:01, options:0040, messageType:00, dni:33D2, isClusterSpecific:false, isManufacturerSpecific:false, manufacturerId:0000, command:01, direction:01, data:[00, 00, 86]]

Well, worth the try. Thanks!

I think I need to check HA forums - I saw something there.

That's too bad. Most of Markus's drivers do not have configure. With his drivers I hit "initialize" and that takes care of...something.
So even after hitting initialize, the valve will not close or report open/close? I'd ignore the debug info as well as Info Logging. Give it a few minutes to see if things work out

Nope - no sign of life ...

I disabled these options.

The strange thing is that the valve is sending a lot of messages to the hub - those messages were about incoming messages not properly handled (at least I think so ...).

I'll do.

Thanks again!

Just thought I'd try to awaken an old thread. Ever make progress with this? These are 20% the cost of the Sinope, and don't require a plumber to install. I'd like to give it a shot, but don't want to head down this road if there's nothing there.

EcoNet Controls EVC200-HCSML The Bulldog Valve Robot, Z-Wave Water Valve, Smart Home Controller Required, Easy to Install and No Plumbing Required, Valves up to 1.5" https://www.amazon.com/dp/B07DJZCFBH/ref=cm_sw_r_cp_api_glt_fabc_6X2BMR0PHE0XYE7F7PDE

Yes, those are great. I have one of those in a whole different application, and it works well. At least here in Canada, it's 4 times the price. Then again, if I value my time tinkering ...

And, as it turns out I do not value my time, I decided to take a chance on AliExpress. This valve seems to work perfectly. During pairing it was not able to figure out what it was, although it paired fine first try. I set it to the Sinope Water Valve, and it's been perfect ever since.

Probably generic valve would work as well. You should capture the fingerprint for @mike.maxwell

I'll try generic valve again when I can, but I'm pretty sure I tried it and it did not work. If someone could point me to docs on how to pull the fingerprint I'll do that for sure.

When ever you change driver, click save then click configure. As to the fingerprint, reset the device (don't delete it from HE) then re pair it with the log window open, cut and paste and tag mike maxwell

@maffpt did you succeed to get your valve working?

Hi,

I was able to make it work using the following driver:

Tuya gas/water valve

But I did some updates but, to be honest, I can’t remember right now which … sorry

This is what’s working:

/**
 *  Date: 2021-03-31
 */

import hubitat.helper.HexUtils

metadata {
	definition (name: "Zigbee - Tuya TRV", namespace: "Mark-C-uk", author: "MarkC") {
  {
//        capability "Configuration"
//        capability "TemperatureMeasurement"
//        capability "Thermostat"
//        capability "ThermostatHeatingSetpoint"
//        capability "ThermostatCoolingSetpoint"
//        capability "ThermostatSetpoint"
//        capability "Refresh"
//        capability "Battery"
        
    capability "Refresh"
    capability "Valve"
        
    attribute "valve", "enum", ["open", "closed"]
    attribute "status", "string"
      

//        attribute "valve", "String"
//        attribute "WindowOpenDetection","String"
//        attribute "autolock","String"
//        attribute "childLock","String"
        
    ///fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0004,0005,EF00", outClusters: "0019,000A", manufacturer: "_TZE200_ckud7u2l", model: "TS0601", deviceJoinName: "Zigbee - Tuya TRV" 
    /// // Moes TRV
    ///fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,000A,0004,0005,EF00", outClusters: "0019", manufacturer: "_TZE200_zion52ef", model: "TS0601", deviceJoinName: "Zigbee - Tuya TRV"
    fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0004,0005,EF00", outClusters: "0019,000A", manufacturer: "_TZE200_vrjkcam9", model: "TS0601", application: "44", deviceJoinName: "Tuya Zigbee Water/Gas Valve"
}
    
    preferences {
        //input("lock", "enum", title: "Do you want to lock your thermostat's physical keypad?", options: ["No", "Yes"], defaultValue: "No", required: false, displayDuringSetup: false)
        input name: "debugLogging", type: "bool", title: "Enable debug logging", defaultValue: true
        input name: "infoLogging", type: "bool", title: "Enable info logging", defaultValue: true
    }
}
    

ArrayList<String> parse (String description) {
    logDebug "parse $description"
    ArrayList<String> cmd = []
    Map msgMap = null

    if(description.indexOf('encoding: 42') >= 0) {
        List values = description.split("value: ")[1].split("(?<=\\G..)")
        String fullValue = values.join()
        Integer zeroIndex = values.indexOf("01")
            if(zeroIndex > -1) {
                msgMap = zigbee.parseDescriptionAsMap(description.replace(fullValue, values.take(zeroIndex).join()))
                values = values.drop(zeroIndex + 3)
                msgMap["additionalAttrs"] = [
                    ["encoding": "41",
                    "value": parseXiaomiStruct(values.join(), isFCC0=false, hasLength=true)]
                    ]
                logWarn "encoding: 42 parse 37 IF true"
            } 
            else {
                msgMap = zigbee.parseDescriptionAsMap(description) //modle name
                logWarn "encoding: 42 parse 51 ELSE true"
            }
        } 
        else {
            msgMap = zigbee.parseDescriptionAsMap(description)
        }
    
        if(msgMap.containsKey("encoding") && msgMap.containsKey("value") && msgMap["encoding"] != "41" && msgMap["encoding"] != "42") {
            logWarn "pase lin 59 used - ${description}"
            msgMap["valueParsed"] = zigbee_generic_decodeZigbeeData(msgMap["value"], msgMap["encoding"])
        }
        
        if(msgMap == [:] && description.indexOf("zone") == 0) {
            msgMap["type"] = "zone"
            java.util.regex.Matcher zoneMatcher = description =~ /.*zone.*status.*0x(?<status>([0-9a-fA-F][0-9a-fA-F])+).*extended.*status.*0x(?<statusExtended>([0-9a-fA-F][0-9a-fA-F])+).*/
            if(zoneMatcher.matches()) {
                  msgMap["parsed"] = true
                  msgMap["status"] = zoneMatcher.group("status")
                  msgMap["statusInt"] = Integer.parseInt(msgMap["status"], 16)
                  msgMap["statusExtended"] = zoneMatcher.group("statusExtended")
                  msgMap["statusExtendedInt"] = Integer.parseInt(msgMap["statusExtended"], 16)
            } 
            else {
               msgMap["parsed"] = false
            }
            logWarn "line 64 section used"
        }

    switch(msgMap["cluster"] + '_' + msgMap["attrId"]) {
        case "0000_0001":
            logDebug("Application ID Received")
            if(msgMap['value']) {
                updateDataValue("application", msgMap['value'])
            }
            break
        case "0000_0004":
        logDebug("Manufacturer Name Received ${msgMap['value']}")
            if(msgMap['value']) {
                updateDataValue("manufacturer", msgMap['value'])
            }
            break
        case "0000_0005":
            logDebug("Model Name Received")
            if(msgMap['value']) {
                updateDataValue('model', msgMap['value'])
            }    
            break
        default:
            //log.debug " ${msgMap["cluster"]}  ${msgMap["attrId"]} $msgMap"
            switch(msgMap["clusterId"]) {
///
/*
                case "0013":
                    logging("MULTISTATE CLUSTER EVENT")
                    break
                case "8021":
                    logging("BIND RESPONSE CLUSTER EVENT")
                    break
                case "8001":
                    logging("GENERAL CLUSTER EVENT")
                    break
                case "8004":
                    logTrace("Simple Descriptor Information Received - description:${description} | parseMap:${msgMap}")
                    updateDataFromSimpleDescriptorData(msgMap["data"])
                    break
                case "8031":
                    logging("Link Quality Cluster Event - description:${description} | parseMap:${msgMap}")

                    break
                case "8032":
                    logging("Routing Table Cluster Event - description:${description} | parseMap:${msgMap}")
                    break
                case "8021":
                case "8038":
                    logging("GENERAL CATCHALL (0x${msgMap["clusterId"]}")
                    break
///////////TUYA TRV messages////////////
*/
///
                case "EF00":  
                    //log.debug "clutsInt= ${msgMap[clusterInt]} ,att ID ${msgMap["attrId"]}, cluster ${msgMap["clusterId"]} -- ${msgMap}"
                    List data = msgMap['data']
                    if (data[2] && data[3])
                    {
                        String commandType = data[2] + data[3]
                        //logDebug "commandType = ${commandType}"
                        switch(commandType)
                        {
                          case "0101": // whatever it is ...
                            logDebug "case = EF00 - command = 0101 - data = ${data}"
                            //def wrkData = "${data[1]}.${data[6]}" // put the relevant data together to make coding easier ... ok. it's a hack, but works!
                            if (data [1] == "00" && data [6] == "01") 
                            { // open
                              switch (device.currentValue("valve", true))
                              {
                                case "open":
                                  // Already processed - let's just ignore it
                                  break
                                case "closed":
                                  sendEvent (name: "valve", value: "open")
                                  sendEvent (name: "status", value: "opening finished")
                                  break
                              }
                            }
                            else if (data [1] == "00" && data [6] == "00")
                            { // close
                              switch (device.currentValue("valve", true))
                              {
                                case "open":
                                  sendEvent (name: "valve", value: "closed")
                                  sendEvent (name: "status", value: "closing finished")
                                  break
                                case "closed":
                                  // Already processed - let's just ignore it
                                  break
                              }
                            }
                            else if (data [6] == "01")
                            { // opening
                                  sendEvent (name: "status", value: "opening")
                            }
                            else if (data [6] == "00")
                            { // closing
                                  sendEvent (name: "status", value: "closing")
                            }
                            else
                            { // error - unexpected
                            }
                            break
/*
///
//set point temp
                            case "0202": //set point temp
                                String SetPoint = HexUtils.hexStringToInt("${data[-2]}${data[-1]}") / 10
                                logging("${device.displayName} Temp Set Point ${SetPoint}, data ${msgMap["data"]}")
                                sendEvent(name: "heatingSetpoint", value: SetPoint.toFloat(), unit: "C")
                                sendEvent(name: "thermostatSetpoint", value: SetPoint.toFloat(), unit: "C")
                                if (device.currentValue("thermostatMode") != "off" && SetPoint.toFloat() > device.currentValue("temperature").toFloat()) { 
                                    sendEvent(name: "thermostatOperatingState", value: "heating")}
                                else { sendEvent(name: "thermostatOperatingState", value: "idle")}
                            break
                            case "1002": // Moes setpoint
                                String SetPoint = HexUtils.hexStringToInt("${data[-1]}") / 2
                                logging("${device.displayName} Temp Set Point ${SetPoint}, data ${msgMap["data"]}")
                                sendEvent(name: "heatingSetpoint", value: SetPoint.toFloat(), unit: "C")
                                sendEvent(name: "thermostatSetpoint", value: SetPoint.toFloat(), unit: "C")
                            break
//7202 away preset temperature
                            case "0702": //0x7202 away/off preset temperature
                                String SetPoint = HexUtils.hexStringToInt(data[9]) / 10
                                logging("${device.displayName} AWAY Temp Set Point ${commandType}, data9 ${SetPoint}")
                            break
//0302 Temperature
                            case '0302': //Temperature
                                String temperature = HexUtils.hexStringToInt("${data[-2]}${data[-1]}") / 10
                                logging("${device.displayName} Temp ${temperature}, data ${msgMap["data"]}")
                                sendEvent(name: "temperature", value: temperature, unit: "C" )
                                if (device.currentValue("thermostatMode") != "off" && temperature.toFloat() < device.currentValue("thermostatSetpoint").toFloat()) {
                                    sendEvent(name: "thermostatOperatingState", value: "heating")}
                                else { sendEvent(name: "thermostatOperatingState", value: "idle")}
                            break
                            case '1802': //Moes Temperature
                                String temperature = HexUtils.hexStringToInt("${data[-2]}${data[-1]}") / 10
                                logging("${device.displayName} Temp ${temperature}, data ${msgMap["data"]}")
                                sendEvent(name: "temperature", value: temperature, unit: "C" )
                            break
// Mode                            
                            case '0404': // Mode
                                String mode = HexUtils.hexStringToInt(data[6])
                                logging("${device.displayName} mode Code=${mode}")
                                switch (mode){
                                    case '0':
                                        sendEvent(name: "thermostatMode", value: "off" )
                                    break
                                    case '1':
                                        sendEvent(name: "thermostatMode", value: "auto" , descriptionText:"internal programming of device")
                                    break
                                    case '2':
                                        sendEvent(name: "thermostatMode", value: "heat" )
                                    break
                                }
                            break
                            case "0204": //Moes Mode
                            String mode = HexUtils.hexStringToInt(data[6])
                                logging("${device.displayName} mode Code=${mode}")
                                switch (mode){
                                    case '2':
                                        sendEvent(name: "thermostatMode", value: "off" , descriptionText:"Holiday Mode")
                                    break
                                    case '0':
                                        sendEvent(name: "thermostatMode", value: "auto" , descriptionText:"Using internally programmed schedule")
                                    break
                                    case '1':
                                        sendEvent(name: "thermostatMode", value: "heat" , descriptionText:"Manual Mode")
                                    break
                                }
                            break
// battery --- DEV
                            case '1502':
                                //String values = data.collect{c -> HexUtils.hexStringToInt(c)}
                                String batt = HexUtils.hexStringToInt(data[-1])
                            state.batdev = batt //dv to see it it is ever peported
                                logging("${device.displayName} battery ${batt}")
                                sendEvent(name: "battery", value: batt, unit:"%", descriptionText: "reported from 1502")
                            break
                            case '6702':
                                //String values = data.collect{c -> HexUtils.hexStringToInt(c)}
                                String batt = (HexUtils.hexStringToInt(data[-1]).toFloat() /3) *10
                                logging("${device.displayName} battery ${batt}")
                                sendEvent(name: "battery", value: batt, unit:"%", descriptionText: "reported from 6702" )
                                // 6702 - [68, 13, 103, 2, 0, 4, 0, 0, 0, 35] ?3.5 volt?
                                // 6702 - [126, 162, 103, 2, 0, 4, 0, 0, 0, 30] ?3 volt?
                            break
                            
                            case '0D05': // battery low warning
                            String battminmax = HexUtils.hexStringToInt(data[-1])
                            logging("${device.displayName} battery $data")
                            if (battminmax == "0"){
                                    if (device.currentValue("battery") == null || device.currentValue("battery") == 0 ) {
                                        sendEvent(name: "battery", value: 100, unit:"%" )
                                    }
                            }
                            else if (battminmax == "10" ||battminmax == "16"){//10 for low 
                                sendEvent(name: "battery", value: 0, unit:"%", descriptionText: "battery value $battminmax" )
                            }
                            break
                            case "2202": //Moes Low battery warning? -- Dev
                                logging("${device.displayName} Unknown Moes ${commandType} , data ${msgMap["data"]}")
                            break
                            case '6D02': // Valve position
                                String valve = HexUtils.hexStringToInt(data[-1])
                                logging("${device.displayName} valve position ${valve}")
                                sendEvent(name: "valve", value: valve, unit: "%", descriptionText: "Valve open ${valve}%")
                            break
        //// Temperature correction reporting ---DEV   
                            case '2C02': //Temperature correction reporting
                                String temperatureCorr = HexUtils.hexStringToInt(data[9])/ 10
                                logging("${device.displayName} Temp correction reporting DEV STILL, ${temperatureCorr}, data ${msgMap["data"]}")
                            break
         // Child lock --- DEV                   
                            case '0701': // Child lock
                                String locked = HexUtils.hexStringToInt(data[6])
                                logging("${device.displayName} child lock ${commandType}, ${locked} 1 - is locked 0 is unlocked")
                                switch (locked){
                                    case '0':
                                        sendEvent(name: "childLock", value: "off" )
                                    break
                                    case '1':
                                    sendEvent(name: "childLock", value: "on")
                                    break
                                }
                            break
                            case '7401': // auto lock setting A3
                                String autolock = HexUtils.hexStringToInt(data[6])
                                switch (autolock){
                                    case '0':
                                        logging("${device.displayName} Auto lock A3 Off")
                                        sendEvent(name: "autolock", value: "off")
                                    break
                                    case '1':
                                        logging("${device.displayName} Auto lock A3 On ather 10min")
                                        sendEvent(name: "autolock", value: "on")
                                    break
                                }
                            break
                            case '6800': //window open detection
                                String WinTemp = HexUtils.hexStringToInt(data[7])
                                String WinMink = HexUtils.hexStringToInt(data[8])
                                logging("${device.displayName} window open detection ${WinTemp}deg in ${WinMin}min will trigger shutdown")
                                sendEvent(name: "WindowOpenDetection", value: "${WinTemp}deg in ${WinMin}min")
                            break
                            
                           case '6902': //boost -- Dev
                                String values = data.collect{c -> HexUtils.hexStringToInt(c)}
                                logging("${device.displayName} boost ${values}")
                            break
                            
                            case '7000': // schedule setting aka Auto mode -- Dev
                                values = data.collect{c -> HexUtils.hexStringToInt(c)}
                                logging("${device.displayName} schedual P1 ${data[6]}:${data[7]} = ${data[8]}deg , ${data[9]}:${data[10]} = ${data[11]}deg ,more ${data} ")
                                state.SchduleP1 = "${values[6]}:${values[7]} = ${values[8]}deg , ${values[9]}:${values[10]} = ${values[11]}deg ,more ${values}"
                            break
                            case '7001': // schedule setting aka Auto mode -- Dev
                                values = data.collect{c -> HexUtils.hexStringToInt(c)}
                                logging("${device.displayName} schedual P2 ${data[6]}:${data[7]} = ${data[8]}deg , ${data[9]}:${data[10]} = ${data[11]}deg ,more ${data} ")
                                state.SchduleP2 = "${values[6]}:${values[7]} = ${values[8]}deg , ${values[9]}:${values[10]} = ${values[11]}deg ,more ${values}"
                            break
                            case '7100': // schedule setting aka Auto mode -- Dev
                                values = data.collect{c -> HexUtils.hexStringToInt(c)}
                                logging("${device.displayName} schedual P3? ${data[6]}:${data[7]} = ${data[8]}deg , ${data[9]}:${data[10]} = ${data[11]}deg ,more ${data} ")
                                state.SchduleP3 = "${values[6]}:${values[7]} = ${values[8]}deg , ${values[9]}:${values[10]} = ${values[11]}deg ,more ${values}"
                            break
                            
// 0x7502 away preset number of days                            
                            case '7502':
                            logging("${device.displayName} away preset number of days ${HexUtils.hexStringToInt(data[-1])} ")
                            break
*/
///                            
                            default:
                                String values = data.collect{c -> HexUtils.hexStringToInt(c)}
                                logDebug "${device.displayName} other EF00 cluster - ${commandType} - Values: ${values} // Data: ${data}"
                                break
                        }
                    }
                    else { 
                        // found data in map of, data:[02, 19]], data:[00, 00]]
                        //logging("other cluster EF00 but map null- ${data}")
                    }
                    break
                
                /////////////////////////////////////////////////////////////////////////////////////////mc
                default:
                    //log.debug "Unhandled Event IGNORE THIS - description:${description} | msgMap:${msgMap}"
                    break
            }
            break
    }
    msgMap = null
    return cmd
}

def logsOff(){
    logWarn "debug logging disabled..."
    device.updateSetting("logEnable",[value:"false",type:"bool"])
}

// from markus toolbox driver
def zigbee_generic_decodeZigbeeData(String value, String cTypeStr, boolean reverseBytes=true) {
    List values = value.split("(?<=\\G..)")
    values = reverseBytes == true ? values.reverse() : values
    Integer cType = Integer.parseInt(cTypeStr, 16)
    Map rMap = [:]
    rMap['raw'] = [:]
    List ret = zigbee_generic_convertStructValue(rMap, values, cType, "NA", "NA")
    return ret[0]["NA"]
}


//end markus toobox ////////////////////


boolean isMoesModel(String manufacturer=null) {
    manufacturer = manufacturer != null ? manufacturer : getDeviceDataByName('manufacturer')
    switch(manufacturer) {
        case "_TZE200_zion52ef":
            return true
            break
        default:
            return false
    }
}

////////////////////////////////////////////////////////////////////////////

def refresh() {
    def dp = "0302"
    def fn = "0"
    def data = "00" // ??
    
    logDebug "refresh"
    zigbee.readAttribute(0 , 0 )
    //zigbee.readAttribute(0, 0, 770 )
    //zigbee.readAttribute(0x0000, CLUSTER_TUYA) //get setting but not temparture
    //zigbee.readAttribute(0x0302, CLUSTER_TUYA) 
    //zigbee.configureReporting(0x0000, CLUSTER_TUYA)
    //zigbee.readAttribute(CLUSTER_TUYA, 0x0402) 
    
    //configureReporting(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer), 
    //zigbee.readAttribute(0x0000, 0x0402)
    
    
//    zigbee.readAttribute(0x0000, 0x0005) //encoding: 42 parse 51 ELSE true Model Name Received 0000_0005
//    zigbee.readAttribute(0x0000, 0x0004)
   //zigbee.readAttribute(0x0000, 0x0000) // 0000 0000
    
    // nothing zigbee.readAttribute(0x0000, CLUSTER_TUYA, [:] ) 
    // nothing zigbee.readAttribute(0x0402,0x0000) 
    // error   zigbee.readAttribute(0x0000)
    // nothing zigbee.readAttribute(0x0000, 0x0021)
    
    //sendTuyaCommand(dp,fn,data)
    // ????????private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 }
//    return  [
//            "he rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x0000  0x0005 {}","delay 600", 
//            "he rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x0000, 0x0004 {}"
//    ]
}    



///////////// commands ///////////////
private sendTuyaCommand(dp, fn, data) { // everything goes through here
	//log.info "sending ${zigbee.convertToHexString(rand(256), 2)}=${dp},${fn},${data}"
	zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + dp + fn + data)
}

private getCLUSTER_TUYA() { 0xEF00 }
private getSETDATA() { 0x00 }
private rand(n) { return (new Random().nextInt(n))} 

///
def close ( ) 
{
  logInfo "Close valve command received."
  
  if (device.currentValue ("valve", true) == "closed")
  {  // it is already closed - let's ignore the command
    logInfo ("Close valve command ignored - valve already closed!")
    sendStatusEvent ("Close command ignored - valve already closed")
  }
  else
  {  // it is open - let's close it
    def dp = "0404"
    def fn = "0001"
    def data = "00" // off
    
    sendTuyaCommand(dp,fn,data)
  } 
//    off ()    
}


def open ()
{
  logInfo ("Open valve command received.")
  
  if (device.currentValue ("valve", true) == "open")
  {  // it is already open - let's ignore the command
    logInfo ("Open valve command ignored - valve already open!")
    sendStatusEvent ("Open command ignored - valve already open")
  }
  else
  {
    def dp = "0404"
    def fn = "0001"
    def data = "01" // auto mode, internal schdual
    
    sendTuyaCommand(dp,fn,data)
  }
}


def sendStatusEvent (payload)
{
  sendEvent (name: "status", value: payload)
}

///

def sendEventProgress (name, value, totalTime, stepTime)
{
    def dot = ""
    def progress = value.trim() + " "
    
    for (int thisTime = 0; thisTime <= totalTime; thisTime += stepTime)
    {
        sendEvent (name: "${name}", value: "${progress += "."}" )
        //pauseExecution (stepTime)
    }
}

void updateDataFromSimpleDescriptorData(List<String> data) {
    Map<String,String> sdi = parseSimpleDescriptorData(data)
    if(sdi != [:]) {
        updateDataValue("endpointId", sdi['endpointId'])
        updateDataValue("profileId", sdi['profileId'])
        updateDataValue("inClusters", sdi['inClusters'])
        updateDataValue("outClusters", sdi['outClusters'])
        getInfo(true, sdi)
    } else {
        logWarn("No VALID Simple Descriptor Data received!")
    }
    sdi = null
}


List zigbee_generic_convertStructValue(Map r, List values, Integer cType, String cKey, String cTag) {
    String cTypeStr = cType != null ? integerToHexString(cType, 1) : null
    switch(cType) {
        case 0x10:
            r["raw"][cKey] = values.take(1)[0]
            r[cKey] = Integer.parseInt(r["raw"][cKey], 16) != 0
            values = values.drop(1)
            break
        case 0x18:
        case 0x20:
            r["raw"][cKey] = values.take(1)[0]
            r[cKey] = Integer.parseInt(r["raw"][cKey], 16)
            values = values.drop(1)
            break
        case 0x19:
        case 0x21:
            r["raw"][cKey] = values.take(2).reverse().join()
            r[cKey] = Integer.parseInt(r["raw"][cKey], 16)
            values = values.drop(2)
            break
        case 0x1A:
        case 0x22:
            r["raw"][cKey] = values.take(3).reverse().join()
            r[cKey] = Integer.parseInt(r["raw"][cKey], 16)
            values = values.drop(3)
            break
        case 0x1B:
        case 0x23:
            r["raw"][cKey] = values.take(4).reverse().join()
            r[cKey] = Long.parseLong(r["raw"][cKey], 16)
            values = values.drop(4)
            break
        case 0x1C:
        case 0x24:
            r["raw"][cKey] = values.take(5).reverse().join()
            r[cKey] = Long.parseLong(r["raw"][cKey], 16)
            values = values.drop(5)
            break
        case 0x1D:
        case 0x25:
            r["raw"][cKey] = values.take(6).reverse().join()
            r[cKey] = Long.parseLong(r["raw"][cKey], 16)
            values = values.drop(6)
            break
        case 0x1E:
        case 0x26:
            r["raw"][cKey] = values.take(7).reverse().join()
            r[cKey] = Long.parseLong(r["raw"][cKey], 16)
            values = values.drop(7)
            break
        case 0x1F:
        case 0x27:
            r["raw"][cKey] = values.take(8).reverse().join()
            r[cKey] = new BigInteger(r["raw"][cKey], 16)
            values = values.drop(8)
            break
        case 0x28:
            r["raw"][cKey] = values.take(1).reverse().join()
            r[cKey] = convertToSignedInt8(Integer.parseInt(r["raw"][cKey], 16))
            values = values.drop(1)
            break
        case 0x29:
            r["raw"][cKey] = values.take(2).reverse().join()
            r[cKey] = (Integer) (short) Integer.parseInt(r["raw"][cKey], 16)
            values = values.drop(2)
            break
        case 0x2B:
            r["raw"][cKey] = values.take(4).reverse().join()
            r[cKey] = (Integer) Long.parseLong(r["raw"][cKey], 16)
            values = values.drop(4)
            break
        case 0x30:
            r["raw"][cKey] = values.take(1)[0]
            r[cKey] = Integer.parseInt(r["raw"][cKey], 16)
            values = values.drop(1)
            break
        case 0x31:
            r["raw"][cKey] = values.take(2).reverse().join()
            r[cKey] = Integer.parseInt(r["raw"][cKey], 16)
            values = values.drop(2)
            break
        case 0x39:
            r["raw"][cKey] = values.take(4).reverse().join()
            r[cKey] = parseSingleHexToFloat(r["raw"][cKey])
            values = values.drop(4)
            break
        case 0x42:
            Integer strLength = Integer.parseInt(values.take(1)[0], 16)
            values = values.drop(1)
            r["raw"][cKey] = values.take(strLength)
            r[cKey] = r["raw"][cKey].collect { 
                (char)(int) Integer.parseInt(it, 16)
            }.join()
            values = values.drop(strLength)
            break
        default:
            throw new Exception("The Struct used an unrecognized type: $cTypeStr ($cType) for tag 0x$cTag with key $cKey (values: $values, map: $r)")
    }
    return [r, values]
}
String integerToHexString(BigDecimal value, Integer minBytes, boolean reverse=false) {
    return integerToHexString(value.intValue(), minBytes, reverse=reverse)
}

String integerToHexString(Integer value, Integer minBytes, boolean reverse=false) {
    if(reverse == true) {
        return HexUtils.integerToHexString(value, minBytes).split("(?<=\\G..)").reverse().join()
    } else {
        return HexUtils.integerToHexString(value, minBytes)
    }
    
}


//
// Logging stuff
//

def logDebug (message) { if ( debugLogging ) log.debug (message) }
def logInfo  (message) { if ( infoLogging ) log.info (message) }
def logWarn  (message) { log.warn (message) }

Hope it works with you guys!

2 Likes

should post that to HPM

There is a new one .. :slight_smile:
Can you test it with your device?

I mean when you have time only.. I am trying to consolidate several different types of Tuya valves in one driver, and can't test the TS0601 model myself.

Edit: the temporary link to the driver development version in Github was replaced with a link to the HE community thread for the new driver.

1 Like

What is your valve power-on behavior ?

After powering on, does the valve go to an open or closed state or stay in the last state?

I’m traveling right now, so I can’t test it.

I’ll be back home by May, 13th and I’ll test it.

I promise!

1 Like

As far I remember, it stays at the same status as before.

But when I get back home I’ll test it.

1 Like