Zemismart curtain switch

Hello everyone.

First of, great job with HE, its amazing :).

Now to the tricky part, I currently use a wi-fi curtain switch that works based of of tuya, and that is no good with HE at the moment unfortunetly.
Then I saw that zemismart improved that same switch to be zigbee based so I bought it, but my troubles now is that HE can pair it fine but does not recognize the device and I don't see any device type that works the same or even similar, I tried several just in case and used many from Google as well.
Anyone got any working solution or can point me in the right direction?

This is the switch btw:

If it is any help, the tuya version would just receive a 1,2 or 3 as command to declare its states, now as for the zigbee one I have no idea.

I have contacted zemismart as well just in case but I don't have much hopes there.

Any help is appreciated.

Thanks!

Checking the logs while pressing the buttons gives me the following:

dev:5182019-08-08 21:56:01.790 debugcurtain zigbee, parse description: read attr - raw: FB700101020808002000, dni: FB70, endpoint: 01, cluster: 0102, size: 08, attrId: 0008, encoding: 20, command: 0A, value: 00

dev:5182019-08-08 21:55:54.456 debugcurtain zigbee, parse description: read attr - raw: FB700101020808002064, dni: FB70, endpoint: 01, cluster: 0102, size: 08, attrId: 0008, encoding: 20, command: 0A, value: 64

dev:5182019-08-08 21:55:53.983 debugcurtain zigbee, parse description: read attr - raw: FB700101020808002032, dni: FB70, endpoint: 01, cluster: 0102, size: 08, attrId: 0008, encoding: 20, command: 0A, value: 32

Anything I can do with this? x)

This may be of some use?

Take a look at the YouTube video. There’s a smart app and driver, so would need converting from ST to HE.

Thank you for the reply.

I did see that but that is for a 3 gang switch. The one I need is 3 button that are part of the same switch and all 3 buttons work together and only 1 can be enabled at any time. You think it would still work?

I never handled this kind of code let alone porting it from ST to HE, that should be fun. You think it would work?

EDIT:
Tried that after porting and doens't work

That’s a bummer. Hope someone else has a better idea :+1:

But there is only the SmartApp.....the link for the DTH is dead so it can't be downloaded. You need to have that in order to get it to work. Do you have that piece?

The DTH is there as well, the link just has an extra characters. I tried the DTH as well.
It doesn't even do anything on the device, none of the commands work

I was able to fix it by using this by the way :slight_smile:


So all good here, thanks everyone for the help

2 Likes

Great news :+1::+1:
I take it these require a neutral wire?

Yeah that is correct.
These require a neutral wire.
Then you have a Live, a L1 and L2 to control the motor for open or close

1 Like

that is for smartthings code, someone need fix it for hubitat users

The fix is quite simple. Just try to add the code as is, it will give you an error about a function, just search and replace that word with "hubitat" and it will be good.

Fixed for HE. Just copy/paste.

/**
 *
 *	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: "ZigBee Window Shade", namespace: "smartthings", author: "SmartThings", 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, 0003, 0004, 0005, 0006, 0008, 0102", outClusters: "000A", manufacturer: "Feibit Co.Ltd", model: "FTB56-ZT218AK1.6", deviceJoinName: "Wistar Curtain Motor(CMJ)"
        fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0102", outClusters: "000A", manufacturer: "Feibit Co.Ltd", model: "FTB56-ZT218AK1.8", deviceJoinName: "Wistar Curtain Motor(CMJ)"
        fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0102", outClusters: "0003", manufacturer: "REXENSE", model: "DY0010", deviceJoinName: "Smart Curtain Motor(DT82TV)"
    }


    tiles(scale: 2) {
        multiAttributeTile(name:"windowShade", type: "generic", width: 6, height: 4) {
            tileAttribute("device.windowShade", key: "PRIMARY_CONTROL") {
                attributeState "open", label: 'Open', action: "close", icon: "http://www.ezex.co.kr/img/st/window_open.png", backgroundColor: "#00A0DC", nextState: "closing"
                attributeState "closed", label: 'Closed', action: "open", icon: "http://www.ezex.co.kr/img/st/window_close.png", backgroundColor: "#ffffff", nextState: "opening"
                attributeState "partially open", label: 'Partially open', action: "close", icon: "http://www.ezex.co.kr/img/st/window_open.png", backgroundColor: "#d45614", nextState: "closing"
                attributeState "opening", label: 'Opening', action: "pause", icon: "http://www.ezex.co.kr/img/st/window_open.png", backgroundColor: "#00A0DC", nextState: "partially open"
                attributeState "closing", label: 'Closing', action: "pause", icon: "http://www.ezex.co.kr/img/st/window_close.png", backgroundColor: "#ffffff", nextState: "partially open"
            }
        }
        standardTile("contPause", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
            state "pause", label:"", icon:'st.sonos.pause-btn', action:'pause', backgroundColor:"#cccccc"
        }
        standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 1) {
            state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
        }
        valueTile("shadeLevel", "device.level", width: 4, height: 1) {
            state "level", label: 'Shade is ${currentValue}% up', defaultState: true
        }
        controlTile("levelSliderControl", "device.level", "slider", width:2, height: 1, inactiveLabel: false) {
            state "level", action:"switch level.setLevel"
        }

        main "windowShade"
        details(["windowShade", "contPause", "shadeLevel", "levelSliderControl", "refresh"])
    }
}

private getCLUSTER_WINDOW_COVERING() { 0x0102 }
private getCOMMAND_OPEN() { 0x00 }
private getCOMMAND_CLOSE() { 0x01 }
private getCOMMAND_PAUSE() { 0x02 }
private getCOMMAND_GOTO_LIFT_PERCENTAGE() { 0x05 }
private getATTRIBUTE_POSITION_LIFT() { 0x0008 }
private getATTRIBUTE_CURRENT_LEVEL() { 0x0000 }
private getCOMMAND_MOVE_LEVEL_ONOFF() { 0x04 }

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 (supportsLiftPercentage() && 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 && liftmap.value) {
                def newLevel = zigbee.convertHexToInt(liftmap.value)
                levelEventHandler(newLevel)
            }
        } else if (!supportsLiftPercentage() && descMap?.clusterInt == zigbee.LEVEL_CONTROL_CLUSTER && descMap.value) {
            def valueInt = Math.round((zigbee.convertHexToInt(descMap.value)) / 255 * 100)

            levelEventHandler(valueInt)
        }
    }
}

def levelEventHandler(currentLevel) {
    def lastLevel = device.currentValue("level")
    log.debug "levelEventHandle - currentLevel: ${currentLevel} lastLevel: ${lastLevel}"
    if (lastLevel == "undefined" || currentLevel == lastLevel) { //Ignore invalid reports
        log.debug "Ignore invalid reports"
    } else {
        sendEvent(name: "level", value: currentLevel)
        if (currentLevel == 0 || currentLevel == 100) {
            sendEvent(name: "windowShade", value: currentLevel == 0 ? "closed" : "open")
        } else {
            if (lastLevel < currentLevel) {
                sendEvent([name:"windowShade", value: "opening"])
            } else if (lastLevel > currentLevel) {
                sendEvent([name:"windowShade", value: "closing"])
            }
            runIn(1, "updateFinalState", [overwrite:true])
        }
    }
}

def updateFinalState() {
    def level = device.currentValue("level")
    log.debug "updateFinalState: ${level}"
    if (level > 0 && level < 100) {
        sendEvent(name: "windowShade", value: "partially open")
    }
}

def supportsLiftPercentage() {
    device.getDataValue("manufacturer") != "Feibit Co.Ltd"
}

def close() {
    log.info "close()"
    zigbee.command(CLUSTER_WINDOW_COVERING, COMMAND_CLOSE)
}

def open() {
    log.info "open()"
    zigbee.command(CLUSTER_WINDOW_COVERING, COMMAND_OPEN)
}

def setLevel(data, rate = null) {
    log.info "setLevel()"
    def cmd
    if (supportsLiftPercentage()) {
        cmd = zigbee.command(CLUSTER_WINDOW_COVERING, COMMAND_GOTO_LIFT_PERCENTAGE, zigbee.convertToHexString(data, 2))
    } else {
        cmd = zigbee.command(zigbee.LEVEL_CONTROL_CLUSTER, COMMAND_MOVE_LEVEL_ONOFF, zigbee.convertToHexString(Math.round(data * 255 / 100), 2))
    }

    return cmd
}

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

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

def refresh() {
    log.info "refresh()"
    def cmds
    if (supportsLiftPercentage()) {
        cmds = zigbee.readAttribute(CLUSTER_WINDOW_COVERING, ATTRIBUTE_POSITION_LIFT)
    } else {
        cmds = zigbee.readAttribute(zigbee.LEVEL_CONTROL_CLUSTER, ATTRIBUTE_CURRENT_LEVEL)
    }
    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."

    def cmds
    if (supportsLiftPercentage()) {
        cmds = zigbee.configureReporting(CLUSTER_WINDOW_COVERING, ATTRIBUTE_POSITION_LIFT, DataType.UINT8, 0, 600, null)
    } else {
        cmds = zigbee.levelConfig()
    }
    return refresh() + cmds
}
3 Likes

Hi all, I also have a similar situation my device is a Xiaomi aquaria curtain motor theres a Smartthings code however i can't seem to get it to work on hubitat, sadly the generic Zigbee shade does not support all the functions, eg when manually opened, curtains state won't reflect in hubitat, in ST however this is perfectly reflected, code below, I get this error ---

Value too long for column "ENDPOINT_ID VARCHAR(2)": "'0x01' (4)"; SQL statement: INSERT INTO FINGERPRINT(DEVICE_TYPE_ID, PROFILE_ID, ENDPOINT_ID, IN_CLUSTERS, OUT_CLUSTERS, MANUFACTURER, MODEL, DEVICE_JOIN_NAME, DEVICE_ID, MFR, PROD, CONTROLLER_TYPE) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [22001-197] HELP ?

/**
*

  • 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.
  • 2017
    */

metadata {
definition(name: "Xiaomi Curtain SJ", namespace: "ShinJjang", author: "ShinJjang", ocfDeviceType: "oic.d.blind") {
capability "Window Shade"
capability "Switch Level"
capability "Actuator"
capability "Health Check"
capability "Switch"
capability "Sensor"
capability "Refresh"

// attribute "Window Shade", "enum", ["open", "close", "stop"]

    fingerprint endpointId: "0x01", profileId: "0104", deviceId: "0202", inClusters: "0000, 0004, 0003, 0005, 000A, 0102, 000D, 0013, 0006, 0001, 0406", outClusters: "0019, 000A, 000D, 0102, 0013, 0006, 0001, 0406"

}

command "levelOpenClose"
command "shadeAction"

command "stop"

preferences {
      input name: "mode", type: "bool", title: "Xiaomi Curtain Direction Set", description: "Reverse Mode ON", required: true,
         displayDuringSetup: true

}

tiles(scale: 2) {
    multiAttributeTile(name: "windowShade", type: "generic", width: 6, height: 4) {
        tileAttribute("device.windowShade", key: "PRIMARY_CONTROL") {
            attributeState("closed", label: 'closed', action: "windowShade.open", icon: "st.doors.garage.garage-closed", backgroundColor: "#A8A8C6", nextState: "opening")
            attributeState("open", label: 'open', action: "windowShade.close", icon: "st.doors.garage.garage-open", backgroundColor: "#F7D73E", nextState: "closing")
            attributeState("closing", label: '${name}', action: "windowShade.open", icon: "st.contact.contact.closed", backgroundColor: "#B9C6A8")
            attributeState("opening", label: '${name}', action: "windowShade.close", icon: "st.contact.contact.open", backgroundColor: "#D4CF14")
            attributeState("partially open", label: 'partially\nopen', action: "windowShade.close", icon: "st.doors.garage.garage-closing", backgroundColor: "#D4ACEE", nextState: "closing")
        }
        tileAttribute("device.level", key: "SLIDER_CONTROL") {
            attributeState("level", action: "setLevel")
        }
    }
    standardTile("open", "open", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("open", label: 'open', action: "windowShade.open", icon: "st.contact.contact.open")
    }
    standardTile("close", "close", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("close", label: 'close', action: "windowShade.close", icon: "st.contact.contact.closed")
    }
    standardTile("stop", "stop", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("stop", label: 'stop', action: "windowShade.stop", icon: "st.illuminance.illuminance.dark")
    }
    standardTile("refresh", "command.refresh", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state "default", label: " ", action: "refresh.refresh", icon: "https://www.shareicon.net/data/128x128/2016/06/27/623885_home_256x256.png"
    }
    standardTile("home", "device.level", width: 2, height: 2, decoration: "flat") {
        state "default", label: "home", action:"presetPosition", icon:"st.Home.home2"
    }
    main(["windowShade"])
    details(["windowShade", "open", "stop", "close", "refresh"])
}

}
// Parse incoming device messages to generate events
def parse(String description) {
def parseMap = zigbee.parseDescriptionAsMap(description)
log.debug "parseMap11:${parseMap}"
def event = zigbee.getEvent(description)

try {
    if (parseMap.raw.startsWith("0104")) {
        log.debug "Xiaomi Curtain"
        log.debug "Unhandled Event - description:${description}, parseMap:${parseMap}, event:${event}"
    } else if (parseMap.raw.endsWith("0007")) {
        log.debug "Unhandled Event - description:${description}, parseMap:${parseMap}, event:${event}"
        log.debug "running…"
    } else if (parseMap.endpoint.endsWith("01")) {
        log.debug "Unhandled Event - description:${description}, parseMap:${parseMap}, event:${event}"
        if (parseMap["cluster"] == "000D" && parseMap["attrId"] == "0055") {
            long theValue = Long.parseLong(parseMap["value"], 16)
            float floatValue = Float.intBitsToFloat(theValue.intValue());
            def windowShadeStatus = ""
        int curtainLevel = floatValue.intValue()
            if(mode == true) {
                if (theValue > 0x42c70000) {
                    log.debug "Just Closed"
                    windowShadeStatus = "closed"
                    curtainLevel = 0
                } else if (theValue > 0) {
                    log.debug curtainLevel + '% Partially Open'
                    windowShadeStatus = "partially open"
                    curtainLevel = 100 - floatValue.intValue()
                } else {
                    log.debug "Just Fully Open"
                    windowShadeStatus = "open"
                    curtainLevel = 100
                }
            } else {
                if (theValue > 0x42c70000) {
                    log.debug "Just Fully Open"
                    windowShadeStatus = "open"
                    curtainLevel = 100
                } else if (theValue > 0) {
                    log.debug curtainLevel + '% Partially Open'
                    windowShadeStatus = "partially open"
                    curtainLevel = floatValue.intValue()
                } else {
                    log.debug "Just Closed"
                    windowShadeStatus = "closed"
                    curtainLevel = 0
                }
            }

            def eventStack = []
            eventStack.push(createEvent(name:"windowShade", value: windowShadeStatus as String))
            eventStack.push(createEvent(name:"level", value: curtainLevel))
            eventStack.push(createEvent(name:"switch", value: (windowShadeStatus == "closed" ? "off" : "on")))

            return eventStack
        }
    } else {
        log.debug "Unhandled Event - description:${description}, parseMap:${parseMap}, event:${event}"
    }

} catch (Exception e) {
    log.warn e
}

}

def close() {
setLevel(0)
}

def open() {
setLevel(100)
}

def off() {
log.debug "off()"
close()
}

def on() {
log.debug "on()"
open()
}

def stop() {
log.debug "stop()"
delayBetween([
zigbee.command(0x0102, 0x02),
zigbee.readAttribute(0x000d, 0x0055)
], 500)
// runIn(1, refresh)
}

def setLevel(level) {
if (level == null) {level = 0}
level = level as int

if(mode == true){
if(level == 100) {
log.debug "Set Close"
zigbee.command(0x0006, 0x00)
} else if(level < 1) {
log.debug "Set Open"
zigbee.command(0x0006, 0x01)
} else {
log.debug "Set Level: ${level}%"
def f = 100 - level
String hex = Integer.toHexString(Float.floatToIntBits(f)).toUpperCase()
zigbee.writeAttribute(0x000d, 0x0055, 0x39, hex)
}
} else{
if (level == 100){
log.debug "Set Open"
zigbee.command(0x0006, 0x01)
} else if(level > 0) {
log.debug "Set Level: ${level}%"
String hex = Integer.toHexString(Float.floatToIntBits(level)).toUpperCase()
zigbee.writeAttribute(0x000d, 0x0055, 0x39, hex)
} else {
log.debug "Set Close"
zigbee.command(0x0006, 0x00)
}
}
}

def shadeAction(level) {
if(mode == true){
if(level == 100) {
log.debug "Set Close"
zigbee.command(0x0006, 0x00)
} else if(level < 1) {
log.debug "Set Open"
zigbee.command(0x0006, 0x01)
} else {
log.debug "Set Level: ${level}%"
def f = 100 - level
String hex = Integer.toHexString(Float.floatToIntBits(f)).toUpperCase()
zigbee.writeAttribute(0x000d, 0x0055, 0x39, hex)
}
} else{
if (level == 100){
log.debug "Set Open"
zigbee.command(0x0006, 0x01)
} else if(level > 0) {
log.debug "Set Level: ${level}%"
String hex = Integer.toHexString(Float.floatToIntBits(level)).toUpperCase()
zigbee.writeAttribute(0x000d, 0x0055, 0x39, hex)
} else {
log.debug "Set Close"
zigbee.command(0x0006, 0x00)
}
}
}
def refresh() {
log.debug "refresh()"
// "st rattr 0x${device.deviceNetworkId} ${1} 0x000d 0x0055"
zigbee.readAttribute(0x000d, 0x0055)
}

If you use an aqara curtain its best if you check directlt on the xiaomi/aqara drivers post here in the community

HI,
I'm new here and need help with this topic.
do any one has driver for the zemismart zigbee curtain module ?
I make it work, but i can only full up or down, can't stop it in between.
any suggestion ?

You can cheat your way into it by just usin button to lower it for x amount of time and stop and so on,
If your motor doesn't make much noise and is fast you can just make it fully open first and then close for x amount of time.
There's various ways you can go to cheat it to stop more or less where you want but doin it properly there's no way if you are refering to controlling the switch.

if you are talkin about controlling the motor directly instead, then we need to know what motor it is as it may already even have an oficial driver

Hello Cexinho! It seems you've found a perfect solution for the curtain switch i've bought. I have to thank you for that ! :slight_smile:

I face only one problem left and can't really find a solution to this one :
I couldn't set the highest and lowest point of my blind. Moreover, the closing command works a bit longer than the opening command. Meaning than every time i close then open the blind, it's getting a bit lower and lower.

So, could you tell me how you did configure the highest and lowest point of your blind with HE ?
Btw, i'm a new HE user and i know barely nothing about code etc..

Thanks a lot for your time :slight_smile:

The highest and lowest point of the blind is set directly on the blind itself. It should have some knob kinda thingies or something where you can put a flat head screwdriver to configure that. It should be one knob/screw thinigie for the top limit and one for the lower limit.

The switch itself only tells the curtain motor to open or close, the motor itself is the one who knows where the limits are. Let me know if this helps.
You can see them here as an example btw

Woaw, what a quick answer Cexinho. Thanks a lot !

Unfortunately, that's what i first did before installing the switch. Once the switch was installed i found out that the parameters had been reset and the switch kinda did his own thing.

So i guess you don't have any "code" solution nor had to modify parameters in HE to set everything UP.

Idk if anyone else faced that kind of issue ?
Thanks again for your time!