Somfy Motorized Blinds (Z-Wave)

DTH is Device Type Handler? I’m using the below that Ash put together:

/**
*

*/
metadata {
definition (name: “Somfy Z-Wave Shades Multi tile”, namespace: “ash”, author: “Ash Raj”) {
capability “Switch Level”
capability “Switch”
capability “Window Shade”
//capability “Polling”
capability “Refresh”
capability “Actuator”

    command "levelOpenClose"
    fingerprint deviceId: "0x1105", inClusters: "0x2C, 0x72, 0x26, 0x20, 0x25, 0x2B, 0x86"
}
simulator {
    status "on":  "command: 2003, payload: FF"
    status "off": "command: 2003, payload: 00"
    status "09%": "command: 2003, payload: 09"
    status "10%": "command: 2003, payload: 0A"
    status "33%": "command: 2003, payload: 21"
    status "66%": "command: 2003, payload: 42"
    status "99%": "command: 2003, payload: 63"
    
    // reply messages
    reply "2001FF,delay 5000,2602": "command: 2603, payload: FF"
    reply "200100,delay 5000,2602": "command: 2603, payload: 00"
    reply "200119,delay 5000,2602": "command: 2603, payload: 19"
    reply "200132,delay 5000,2602": "command: 2603, payload: 32"
    reply "20014B,delay 5000,2602": "command: 2603, payload: 4B"
    reply "200163,delay 5000,2602": "command: 2603, payload: 63"
}
tiles(scale: 2) {
    multiAttributeTile(name:"shade", type: "lighting", width: 6, height: 4) {
        tileAttribute("device.windowShade", key: "PRIMARY_CONTROL") {
            attributeState("unknown", label:'${name}', action:"refresh.refresh", icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e")
            attributeState("closed", label:'${name}', action:"open", icon:"st.doors.garage.garage-closed", backgroundColor:"#bbbbdd", nextState: "opening")
            attributeState("open", label:'${name}', action:"close", icon:"st.doors.garage.garage-open", backgroundColor:"#ffcc33", nextState: "closing")
            attributeState("partially open", label:'stop/my', action:"presetPosition", icon:"st.doors.garage.garage-open", backgroundColor:"#ffcc33")
            attributeState("closing", label:'${name}', action:"presetPosition", icon:"st.doors.garage.garage-closing", backgroundColor:"#bbbbdd")
            attributeState("opening", label:'${name}', action:"presetPosition", icon:"st.doors.garage.garage-opening", backgroundColor:"#ffcc33")
        }
        tileAttribute ("device.level", key: "SLIDER_CONTROL") {
            attributeState("level", action:"switch level.setLevel")
        }
        tileAttribute ("device.speedLevel", key: "VALUE_CONTROL") {
            attributeState("level", action: "levelOpenClose")
        }
    }
    standardTile("switchmain", "device.switch", width: 2, height: 2) {
        state "on", label:'open', action:"switch.off", icon:"st.doors.garage.garage-open", backgroundColor:"#ffcc33"
        state "off", label:'closed', action:"switch.on", icon:"st.doors.garage.garage-closed", backgroundColor:"#bbbbdd"
        state "default", label:'stop/my', action:"presetPosition", icon:"st.doors.garage.garage-open", backgroundColor:"#ffcc33"
    }
    standardTile("on", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("on", label:'open', action:"switch.on", icon:"st.doors.garage.garage-opening")
    }
    standardTile("off", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("off", label:'close', action:"switch.off", icon:"st.doors.garage.garage-closing")
    }
    standardTile("stop", "device.level", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("default", label:'stop/my', action:"switch level.setLevel", icon:"st.Transportation.transportation13")
    }
    controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false) {
        state("level", action:"switch level.setLevel")
    }
    standardTile("refresh", "command.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
            state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
    }

// Poll provides data, but the ZRTSII does not provide accurate status
//
// standardTile(“poll”, “command.poll”, width:2, height:2, inactiveLabel: false, decoration: “flat”) {
// state “default”, label:‘poll’, action:“poll”, icon:“st.secondary.poll”
// }

    main(["switchmain"])
    details(["shade", "on", "off", "stop"])
}

}

def parse(String description) {
description
def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
log.debug “Parsed ${description} to ${cmd}”
if (cmd) {
result = zwaveEvent(cmd)
log.debug “zwaveEvent( ${cmd} ) returned ${result.inspect()}”
} else {
log.debug “Non-parsed event: ${description}”
}
return result
}

def levelOpenClose(value) {
log.trace “levelOpenClose called with value $value”
if (value) {
on()
} else {
off()
}
}

// Somfy ZRTSII does not report accurate status for the device.
// This device handler maintains an internal view of device status based on last command
// reissuing a command to the shade (up, down, my (when stopped)) does not move the shade if it is already in that position
// My/stop command does different actions depending if the shade is idle (go to MY position) or moving (stop)

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd)
{
def result =
log.trace “Basic report cmd.value: ${cmd.value}”
if (cmd.value == 0) {
//result << createEvent(name: “switch”, value: “off”)
log.debug "Reported state is closed; device is ${device.latestValue(‘switch’)} ${device.latestValue(‘level’)} "
} else if (cmd.value == 0xFF) {
//result << createEvent(name: “switch”, value: “on”)
log.debug "Reported state is open; device is ${device.latestValue(‘switch’)} ${device.latestValue(‘level’)} "
} else { // This has never happend
//result << createEvent(name: “switch”, value: “default”)
log.debug "Reported state is neither open or closed; device is ${device.latestValue(‘switch’)} ${device.latestValue(‘level’)} "
}
return result
}

def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
def result =
log.debug “SwitchBinaryReport cmd.value: ${cmd.value}”

if (cmd.value == 0) {
    log.debug "Reported state is closed; device is ${device.latestValue('switch')}  ${device.latestValue('level')} "
} else if (cmd.value == 0xFF) {
    log.debug "Reported state is open; device is ${device.latestValue('switch')}  ${device.latestValue('level')} "
} else {  // this has never happened
    log.debug "Reported state is neither open or closed; device is ${device.latestValue('switch')}  ${device.latestValue('level')} "
}

//result << createEvent(name:"switch", value: cmd.value ? "on" : "off")
//result << createEvent(name: "level",value: cmd.value, unit:"%",
    //descriptionText:"${device.displayName} dimmed ${cmd.value==255 ? 100 : cmd.value}%")
return result

}

def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd)
{
def result =
log.trace “SwitchMultilevelReport cmd.value: ${cmd.value}”

if (cmd.value == 0) {
    //result << createEvent(name: "switch", value: "off")
    log.debug "Reported state is closed; device is ${device.latestValue('switch')}  ${device.latestValue('level')} "
} else if (cmd.value == 0xFF) {
    //result << createEvent(name: "switch", value: "on")
    log.debug "Reported state is open; device is ${device.latestValue('switch')}  ${device.latestValue('level')} "
} else {
   //result << createEvent(name: "switch", value: "default")
   log.debug "Reported state is neither open or closed; device is ${device.latestValue('switch')}  ${device.latestValue('level')} "
}
//result << createEvent(name: "level",value: cmd.value, unit:"%",
  //descriptionText:"${device.displayName} dimmed ${cmd.value==255 ? 100 : cmd.value}%")
return result

}

def on() {
int level = 100
log.trace “on() treated as open()”
setLevel(level)
}

def off() {
int level = 0
log.trace “off() treated as close()”
setLevel(level)
}

def setLevel() {
log.trace “setLevel() treated as my/stop”
setLevel(50)
}

def open() {
log.trace “open()”
on()
}

def close() {
log.trace “close()”
off()
}

def presetPosition() {
log.trace “presetPosition() treated as my/stop”
setLevel()
}

def refresh() {
log.trace “refresh()”
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),
zwave.switchMultilevelV1.switchMultilevelGet().format(),
//zwave.meterV2.meterGet(scale: 0).format(), // get kWh
//zwave.meterV2.meterGet(scale: 2).format(), // get Watts
//zwave.sensorMultilevelV1.sensorMultilevelGet().format(),
//zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1).format(), // get temp in Fahrenheit
//zwave.batteryV1.batteryGet().format(),
zwave.basicV1.basicGet().format(),
], 3000)
}

// If you add the Polling capability to your device type, this command
// will be called approximately every 5 minutes to check the device’s state
// zrtsII does not provide accurate status of shade position
//def poll() {
// log.trace “Poll”
// zwave.basicV1.basicGet().format()
//}

def setLevel(level) {
log.trace “setLevel(level) {$level}”
log.debug "level.inspect " + level.inspect()

int newlevel = level
if (level > null) {
    sendEvent(name: "level", value: level)
    if (level >= 75) {
        sendEvent(name: "windowShade", value: "opening")
        newlevel = 100
        delayBetween([
            zwave.switchMultilevelV1.switchMultilevelSet(value: 0xFF).format(),
            sendEvent(name: "switch", value: "on"),
            sendEvent(name: "windowShade", value: "open")
        ], 5000)
    } else if (level <= 25) {
        sendEvent(name: "windowShade", value: "closing")
        newlevel = 0
        delayBetween([
            zwave.switchMultilevelV1.switchMultilevelSet(value: 0x00).format(),
            sendEvent(name: "switch", value: "off"),
            sendEvent(name: "windowShade", value: "closed")
        ], 5000)
    } else {
        delayBetween([
            zwave.switchMultilevelV1.switchMultilevelStopLevelChange().format(),
            sendEvent(name: "switch", value: "default"),
            sendEvent(name: "windowShade", value: "partially open")
        ], 5000)
    }
    // this code below causes commands not be sent/received by the Somfy ZRTSII - assume delayBetween is asynchronous...
    //log.trace("finished level adjust")
    //if (newlevel != level) { 
        //log.trace("finished level adjust1")
        //delayBetween([
            //sendEvent(name: "level", value: newlevel)
        //], 1000)
    //}
}

}

// this appears to never be called

//def setLevel(level, duration) {
// log.trace “setLevel(level, duration) {$level} ${duration}”
// setLevel(level)
// return
//}