Tuya Zigbee Roller Shade port help

ok thank

not sue what you mean but having a 2 dosnt work but when i put a 1 it does

Ok, that means that the ST implementation takes it as a literal 1 character, so you only need one byte here, there are apparently many subtle differences.

I installed the Tuya gateway, the issue is that it will not let me install a random zigbee device without having it. To do so without having the device I would probably have to create a "fake" device using a zigbee usb stick or something such. I do have plans to do something like that, but don't know when I will have the time. So for now I have no more information for you.

no problem mate you have been a great help, i have it all set up with the remote and working in HE now.
Only bug ive got now is i cant get it into GH keeps saying its not supported

but thanks again
on quest on code tidying insted of this zigbee command can i not pass it over to the send tuya commend section

def DirectionSet(Dval) {
	if(logEnable) log.info "Direction set ${Dval} "
   //sendHubCommand(zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + "05040001" + Dval))
    zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + "05040001" + Dval)

private sendTuyaCommand(dp, fn, data) {
	if(logEnable) log.trace "send tuya command ${dp},${fn},${data}"
	zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + dp + fn + data)

Do GH support blinds? I don't use it so don't know. But you could possibly expose it as a Switch and implement open/close as on/off.

zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + "05040001" + Dval)

could be rewritten as:

sendTuyaCommand("05040001", Dval, "")

thanks, and some unpaser commands
UN-Pasred Map [raw:61DE0100000801002049, dni:61DE, endpoint:01, cluster:0000, size:08, attrId:0001, encoding:20, command:0A, value:49, clusterInt:0, attrInt:1]

This is safe to ignore

1 Like

The built in GH integration doesn't support blinds.
You could add in the light capability to the driver and then it should pick it up and use the dimmer level to control the blind position.

tried, still no joy, wonder if i need to create a fake state for light?

            capability "Actuator"
		capability "Configuration"
		capability "Window Shade"
// mc not supported in HE		capability "Window Shade Preset"
		capability "Switch Level"
        
        capability "Light" //GH
        capability "Switch" //gh

sorted with
if (level < 100){ sendEvent(name: "switch", value: "on")}
else {sendEvent(name: "switch", value: "off")}

You need the device to have a state (i.e on/off) for GH to allow it to be added.
It should then hopefully use the switch level.

1 Like

ok ive got another blind installed the only difference is its running in revers but for some reason im getting a random closed when its onpen, is it the device or the driver, i operated them in parallel

Summary
/** zemismart
*  Tuya Window Shade (v.0.1.0) Hubitat v1
 *	Copyright 2020 iquix
 *
 *	Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *	in compliance with the License. You may obtain a copy of the License at:
 *
 *		http://www.apache.org/licenses/LICENSE-2.0
 *
 *	Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *	on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *	for the specific language governing permissions and limitations under the License.
 This DTH is coded based on iquix's tuya-window-shade DTH.
 https://github.com/iquix/Smartthings/blob/master/devicetypes/iquix/tuya-window-shade.src/tuya-window-shade.groovy


https://raw.githubusercontent.com/shin4299/XiaomiSJ/master/devicetypes/shinjjang/zemismart-zigbee-blind.src/zemismart-zigbee-blind.groovy

 */

import groovy.json.JsonOutput
//mc // import physicalgraph.zigbee.zcl.DataType`
import hubitat.zigbee.zcl.DataType
import hubitat.helper.HexUtils

metadata {
	definition(name: "ZemiSmart Zigbee Blind", namespace: "ShinJjang", author: "ShinJjang-iquix", ocfDeviceType: "oic.d.blind", vid: "generic-shade") {
		capability "Actuator"
		capability "Configuration"
		capability "Window Shade"
// mc not supported in HE		capability "Window Shade Preset"
		capability "Switch Level"
    
    capability "Light" //GH

	command "pause"
command "presetPosition"

attribute "Direction", "enum", ["Reverse","Forward"]

	fingerprint endpointId: "01", profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006", outClusters: "0019", manufacturer: "_TYST11_wmcdj3aq", model: "mcdj3aq", deviceJoinName: "Zemismart Zigbee Blind"
//mc changeed endpointId from 0x01 to 01	
}

preferences {
input "preset", "number", title: "Preset position", description: "Set the window shade preset position", defaultValue: 50, range: "0..100", required: false, displayDuringSetup: false
input name: "Direction", type: "enum", title: "Direction Set", defaultValue: "00", options:["01": "Reverse", "00": "Forward"], displayDuringSetup: true
    input "logEnable", "bool", title: "Enable logging", required: true, defaultValue: true
}
// removed tiles section as not used in Hubitat
}

private getCLUSTER_TUYA() { 0xEF00 }
private getSETDATA() { 0x00 }

// Parse incoming device messages to generate events
def parse(String description) {
if (description?.startsWith('catchall:') || description?.startsWith('read attr -')) {
	Map descMap = zigbee.parseDescriptionAsMap(description)
//if(logEnable) log.debug "Pasred Map $descMap"
	if (descMap?.clusterInt==CLUSTER_TUYA) {
		if ( descMap?.command == "01" || descMap?.command == "02" ) {
			def dp = zigbee.convertHexToInt(descMap?.data[3]+descMap?.data[2])
        if(logEnable) log.debug "dp = " + dp
			switch (dp) {
				case 1025: // 0x04 0x01: Confirm opening/closing/stopping (triggered from Zigbee)
            	def data = descMap.data[6]
            	if (descMap.data[6] == "00") {
                	if(logEnable) log.debug "parsed opening"
                    levelEventMoving(100)
                } 
                else if (descMap.data[6] == "02") {
                	if(logEnable) log.debug "parsed closing"
                    levelEventMoving(0)
                }
                else {log.debug "parsed else case $dp open/close/stop zigbee $data"}
            	break;
            
				case 1031: // 0x04 0x07: Confirm opening/closing/stopping (triggered from remote)
            	def data = descMap.data[6]
            	if (descMap.data[6] == "01") {
                	log.trace "remote closing"
                    levelEventMoving(0)
                } 
                else if (descMap.data[6] == "00") {
                	log.trace "remote opening"
                    levelEventMoving(100)
                }
                else {log.debug "parsed else case $dp open/close/stop remote $data"}
            	break;
            
				case 514: // 0x02 0x02: Started moving to position (triggered from Zigbee)
            	def pos = zigbee.convertHexToInt(descMap.data[9])
					if(logEnable) log.debug "moving to position :"+pos
                levelEventMoving(pos)
                break;
            
				case 515: // 0x03: Arrived at position
            	def pos = zigbee.convertHexToInt(descMap.data[9])
                if(logEnable) log.debug description
            	log.info "arrived at position :"+pos
            	levelEventArrived(pos)
                break;
            
            log.warn "UN-handled CLUSTER_TUYA case  $dp $descMap"  
			}
		}
 
	}
else {
    //log.warn "UN-Pasred Map $descMap"
}
}

}

private levelEventMoving(currentLevel) {
def lastLevel = device.currentValue("level")
if(logEnable) log.debug "levelEventMoving - currentLevel: ${currentLevel} lastLevel: ${lastLevel}"
if (lastLevel == "undefined" || currentLevel == lastLevel) { //Ignore invalid reports
log.debug "Ignore invalid reports"
} else {
if (lastLevel < currentLevel) {
sendEvent([name:"windowShade", value: "opening"])
} else if (lastLevel > currentLevel) {
sendEvent([name:"windowShade", value: "closing"])
}
}
}

private levelEventArrived(level) {
if (level == 0) {
sendEvent(name: "windowShade", value: "closed")
} else if (level == 100) {
sendEvent(name: "windowShade", value: "open")
} else if (level > 0 && level < 100) {
sendEvent(name: "windowShade", value: "partially open")
} else {
sendEvent(name: "windowShade", value: "unknown")
//return
}
sendEvent(name: "level", value: (level))
sendEvent(name: "position", value: (level))
//To enable in GoggleHome
if (level < 100){ sendEvent(name: "switch", value: "on")}
else {sendEvent(name: "switch", value: "off")}
}

def close() {
if(logEnable) log.info "close()"
def currentLevel = device.currentValue("level")
if (currentLevel == 0) {
sendEvent(name: "windowShade", value: "closed")
return
}
sendTuyaCommand("0104", "00", "0102")
}

def open() {
if(logEnable) log.info "open()"
def currentLevel = device.currentValue("level")
if (currentLevel == 100) {
sendEvent(name: "windowShade", value: "open")
return
}
sendTuyaCommand("0104", "00", "0100")
}

def pause() {
if(logEnable) log.info "pause()"
sendTuyaCommand("0104", "00", "0101")

}

def setLevel(data, rate = null) {
if(logEnable) log.info "setLevel("+data+")"
def currentLevel = device.currentValue("level")
if (currentLevel == data) {
sendEvent(name: "level", value: currentLevel)
sendEvent(name: "position", value: currentLevel) //HE capability attribute
return
}
sendTuyaCommand("0202", "00", "04000000"+HexUtils.integerToHexString(data.intValue(), 1))
}

def setPosition(position){ //mc new for HE Commands
if(logEnable) log.info "setPos to $position"
setLevel(position, null)
}

def presetPosition() { //custom command preset
setLevel(preset ?: 50)
}

def installed() {
sendEvent(name: "supportedWindowShadeCommands", value: JsonOutput.toJson(["open", "close", "pause"]), displayed: false)
}

def updated() {
def val = Direction
sendEvent([name:"Direction", value: (val == "00" ? "Forward" : "Reverse")])
DirectionSet(val)
}

def DirectionSet(Dval) {
if(logEnable) log.info "Direction set ${Dval} "
//zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + "05040001" + Dval)
sendTuyaCommand("05040001", Dval, "") //not tested
}

def configure() {
log.info "configure()"
}

private sendTuyaCommand(dp, fn, data) {
if(logEnable) log.trace "send tuya command ${dp},${fn},${data}"
zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + dp + fn + data)
}

private rand(n) {
return (new Random().nextInt(n))
}

updated code to handle on off, as google think they are dimmers
https://raw.githubusercontent.com/Mark-C-uk/Hubitat/master/ZemiSmart%20Zigbee%20Blind

Hi. My first post and a somewhat new user to HE (I just figured out what HE stands for... Hubitat Elevation). Guess a guide as to what all the abbreviations mean, is needed.
I have the Tuya zigbee blind motor (ZM25TQ), and have been trying the different drivers that have been presented here, and although I can manually control the motor, it does not show up in the dashboard's device list, so I cannot add a dashboard tile.
How do I do this? Probably a real simple answer, but I have tried to find out and have gotten nowhere.
Any help would REALLY be appreciated!
Thanks.... Bill

Just an edit.... I have not been able to add any limits to the motor as i don't have the Tuya zigbee hub yet... just ordered it last night and should have it Sunday.
I gather that none of the device's buttons can do that? A minor inconvenience as once the remote is paired to the motor, I can use the remote to set limits.

Have you a remote? You can set the limits with that.
Can you post a screenshot of the device details page

Hello Mark. Thank you for replying.
From what I have found, the Tuya zigbee hub is required to set the limits because it has to be paired first with the app to enable limit setting.
and yes I do have the remote for the zigbee and wifi versions.
I could not find any info to set the limits with the remote without pairing first and enabling set limits in the app. Really kind of frustrating that Tuya makes this so difficult!
I know the wifi version does not need the hub because it pairs over wifi (if your lucky... kinda flakey). I have both versions of the motor and that is why I know this.
Here is HE's device details of the zigbee version:

Thanks Mark for your help!

Hi all,

I ordered one from AliExpress, which claimed to be Zemismart Tuya Zigbee but actually runs on the wifi-433 network (model#ZM25TQ). Very frustrated.
Just wondering which of the Zemismart Tuya Roller Blind Motor model(s) you guys have that actually works with direct connection on the Hubitat Hub?

Thanks.

Zigbee

Hi, I have a Nue ZigBee Blind Switch working with basic functionality (Open, Close & Pause) using the below DH. Does anyone know of a DH that may work with the setLevel functionally or able to please help me with getting the setLevel commands working? Currently the only states that get reported are checkinterval and windowShade. The windowShade vaule only changes between opening and closing when pressing the button.
image

**
 *
 *	Copyright 2019 SmartThings
 *
 *	Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *	in compliance with the License. You may obtain a copy of the License at:
 *
 *		http://www.apache.org/licenses/LICENSE-2.0
 *
 *	Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *	on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *	for the specific language governing permissions and limitations under the License.
 */
import hubitat.zigbee.zcl.DataType

metadata {
    definition(name: "Nue ZigBee Blind Switch", namespace: "3A", author: "Kevin", ocfDeviceType: "oic.d.blind", mnmn: "SmartThings", vid: "generic-shade") {
        capability "Actuator"
        capability "Configuration"
        capability "Refresh"
        capability "Window Shade"
        capability "Health Check"
        capability "Switch Level"

        command "pause"

        fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0102", outClusters: "0019", model: "E2B0-KR000Z0-HA", deviceJoinName: "SOMFY Blind Controller/eZEX" // SY-IoT201-BD
        fingerprint profileId: "0104", inClusters: "0000, 0202, 0003, 0004, 0005, 0008, 0102", outClusters: "000A, 0019", manufacturer: "Feibit Co.Ltd", model: "FB56+CUR17SB2.2", deviceJoinName: "Nue Smart Blind Switch"
        fingerprint profileId: "0104", inClusters: "0000, 0202, 0003, 0004, 0005, 0008, 0102", outClusters: "000A, 0019", manufacturer: "Feibit Co.Ltd", model: "FB56+CUR17SB1.9", deviceJoinName: "Nue Smart Blinds"
    }
}
private getCLUSTER_WINDOW_COVERING() { 0x0102 }
private getATTRIBUTE_POSITION_LIFT() { 0x0008 }

private List<Map> collectAttributes(Map descMap) {
	List<Map> descMaps = new ArrayList<Map>()

	descMaps.add(descMap)

	if (descMap.additionalAttrs) {
		descMaps.addAll(descMap.additionalAttrs)
	}

	return  descMaps
}

// Parse incoming device messages to generate events
def parse(String description) {
    log.debug "description:- ${description}"
    if (description?.startsWith("read attr -")) {
        Map descMap = zigbee.parseDescriptionAsMap(description)
        if (descMap?.clusterInt == CLUSTER_WINDOW_COVERING && descMap.value) {
            log.debug "attr: ${descMap?.attrInt}, value: ${descMap?.value}, descValue: ${Integer.parseInt(descMap.value, 16)}, ${device.getDataValue("model")}"
            List<Map> descMaps = collectAttributes(descMap)
            def liftmap = descMaps.find { it.attrInt == ATTRIBUTE_POSITION_LIFT }
            if (liftmap) {
                state.level = Integer.parseInt(liftmap.value, 16)
                if (liftmap.value == "64") { //open
                    sendEvent(name: "windowShade", value: "open")
                    sendEvent(name: "level", value: "100")
                } else if (liftmap.value == "00") { //closed
                    sendEvent(name: "windowShade", value: "closed")
                    sendEvent(name: "level", value: "0")
                } else {
                    sendEvent(name: "windowShade", value: "partially open")
                    sendEvent(name: "level", value: zigbee.convertHexToInt(liftmap.value))
                }
            }
        }
    }
}

def close() {
    log.info "close()"
    sendEvent(name: "windowShade", value: "closing")
    zigbee.command(CLUSTER_WINDOW_COVERING, 0x01)
}

def open() {
    log.info "open()"
    sendEvent(name: "windowShade", value: "opening")
    zigbee.command(CLUSTER_WINDOW_COVERING, 0x00)
}

def setLevel(data) {
    log.info "setLevel()"
    Integer currentLevel = state.level
    Integer level = data as Integer
    if (level > currentLevel) {
        sendEvent(name: "windowShade", value: "opening")
    } else if (level < currentLevel) {
        sendEvent(name: "windowShade", value: "closing")
    }
    zigbee.command(CLUSTER_WINDOW_COVERING, 0x05, zigbee.convertToHexString(data, 2))
}

def pause() {
    log.info "pause()"
    zigbee.command(CLUSTER_WINDOW_COVERING, 0x02)
}

/**
 * PING is used by Device-Watch in attempt to reach the Device
 * */
def ping() {
    return refresh()
}

def refresh() {
    log.info "refresh()"
    def cmds = zigbee.readAttribute(CLUSTER_WINDOW_COVERING, ATTRIBUTE_POSITION_LIFT)
    return cmds
}

def configure() {
    // Device-Watch allows 2 check-in misses from device + ping (plus 2 min lag time)
    log.info "configure()"
    sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
    log.debug "Configuring Reporting and Bindings."
    zigbee.configureReporting(CLUSTER_WINDOW_COVERING, ATTRIBUTE_POSITION_LIFT, DataType.UINT8, 0, 600, null)
}

image