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.
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
Hi,
I was able to make it work using the following driver:
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!
should post that to HPM
There is a new one ..
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.
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!
As far I remember, it stays at the same status as before.
But when I get back home I’ll test it.
Dude, you're a rockstar. You managed to get all 63 versions(exaggeration but there are MANY) of that Tuya valve. I hate how Tuya does that, making many versions of the same exact thing. If I do buy any Tuya stuff it has to be right at the time someone reports it as working, and even then it's a crapshoot if it'll work, and you'll never get a refund because they're specified to work with Tuya hub only.
The problem with the many different versions of a product that looks the same is more complicated... These devices are actually not made by Tuya, but by many other different companies. What is in common is that these different implementations or modifications are certified by Tuya to work with their Cloud platform. So no matter how the Zigbee commands are implemented, the product will always work when used with Tuya Zigbee gateway and the gateway communicates to Tuya Cloud servers. This allows for small startup companies to come very quickly on the market - use the Tuya IOT developments platform, pass the Tuya certification, pay Tuya a tax for using their cloud platform - and your product is ready to be sold to millions of SmartLife (and many other white labels) system users.
What makes it complicated here is that we are trying to bypass the Cloud communication, thus we can not benefit from the abstraction layer that Tuya Cloud API provides. And we have to deal with the lower level Zigbee communication specifics for each manufacturer.
It was pointed out recently that Hubitat met with Tuya and they declined to help sort through their id clusters and only wanted cloud implementation so they could collect data. HE of course said NO rather emphatically. They weren't going to let that happen. Keep up the good work sir!