Groovy web console

subscribe to the feed Subscribe
to this
site
Somfy (via #groovywebconsole)
tweet this snippet Tweet
this
script

Somfy

Published 7 months ago by Chuck
Actions  ➤ Edit in console Back to console Show/hide line numbers View recent scripts
/** * 
 * https://community.smartthings.com/t/my-somfy-smartthings-integration/13492
 * Modified ERS 12/29/2016
 *
 * Version 1.0.6
 *
 * Version History
 *
 * 1.0.6    29 Dec 2016  Health Check
 * 1.0.5    01 May 2016  bug fixes
 * 1.0.4    01 May 2016  Sync commands for cases where blinds respond to multiple channels (all vs. single)
 * 1.0.3    17 Apr 2016  Expanded runIn timer for movement and  completed states
 * 1.0.2    04 Apr 2016  Added runIn timer for movement vs. completed states
 * 1.0.1    07 Mar 2016  Add Blinds support by edit device to set to blinds type
 * 1.0.0    24 Feb 2016  Multi-tile, Window Shade Capability, Device Handler attempts to maintain state
 *
 * Notes:
 *
 * 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, preset (when stopped)) does not move the shade/blinds if it is already in that position
 * My/stop command does different actions depending if the shade is idle (go to MY or closed position) or moving (stop)
 *
 * Once the device is installed, it defaults to "shade" operation.  If "blinds" operation is desired, for the device go to settings (gear)
 * and change the device operation to Window Blinds
 *
 *	Shade and Blinds operate differently in ZRTSII buttons
 *	- Shades actions: up button: open (on switch),  down button: close (off switch),       my/stop button: presetPosition (50%)
 *	- Blinds actions: up button: open (on switch),  down button: tilt open (off switch),   my/stop button: close (50%)
 *
 * Window Shade Capability standardizes:  (these should not be changed, except by SmartThings capabilities updates)
 *	- windowShade: unknown, closed, open, partially open, closing, opening 
 *	- Commands:  open(), close(), presetPosition()
 *
 */
  metadata {
    definition (name: "Somfy Z-Wave Shades and Blinds Multi tile", namespace: "E_Sch", author: "Eric, Ash, Others") {
        capability "Switch Level"
        capability "Switch"
        capability "Window Shade"
        //capability "Polling"
        capability "Refresh"
        capability "Actuator"
        capability "Health Check"

        attribute "stopStr", "enum", ["preset/stop", "close/stop"]

        command "OpenSync"
        command "CloseSync"
        command "TiltSync"
        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"
    }

    preferences {
	input ("shadeType", "enum", options:[
  "shades": "Window Shades",
  "blinds": "Window Blinds"],
  title: "Window Shades or Blinds?", description: "set type (shades or blinds)", defaultValue: "shades",
                required: false, displayDuringSetup: true )
	}

    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:'up', action:"close", icon:"st.doors.garage.garage-open", backgroundColor:"#ffcc33", nextState: "closing")
                attributeState("partially open", label:'preset', action:"presetPosition", icon:"st.Transportation.transportation13", 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.windowShade") {
            state("unknown", label:'${name}', action:"refresh.refresh", icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e")
            state("closed",  label:'${name}', action:"open", icon:"st.doors.garage.garage-closed", backgroundColor:"#bbbbdd", nextState: "opening")
            state("open",    label:'up', action:"close", icon:"st.doors.garage.garage-open", backgroundColor:"#ffcc33", nextState: "closing")
            state("partially open", label:'preset', action:"presetPosition", icon:"st.Transportation.transportation13", backgroundColor:"#ffcc33")
            state("closing", label:'${name}', action:"presetPosition", icon:"st.doors.garage.garage-closing", backgroundColor:"#bbbbdd")
            state("opening", label:'${name}', action:"presetPosition", icon:"st.doors.garage.garage-opening", backgroundColor:"#ffcc33")

//            state("on", label:'up', 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:'preset', action:"presetPosition", icon:"st.Transportation.transportation13", 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.stopStr", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state("close/stop", label:'close/stop', action:"switch.off", icon:"st.doors.garage.garage-closing")
            state("default", label:'close', action:"switch.off", icon:"st.doors.garage.garage-closing")
        }
        standardTile("preset", "device.stopStr", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state("close/stop", label:'slats open', action:"switch level.setLevel", icon:"st.Transportation.transportation13")
            state("default", label:'preset/stop', 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", "preset"])
    }
}

def configure() {
    log.trace "configure() called"
    updated()
}

def ping() {
	refresh()
}

def updated() {
    log.trace "updated() called"

    sendEvent(name: "checkInterval", value: 60 * 60 * 8, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID], displayed: false)

    def currstat = device.latestValue("level")
    def currstat1 = device.latestValue("windowShade")

    log.debug "Shade type: ${settings?.shadeType}"
    if (settings?.shadeType) {
        if (settings.shadeType == "shades") {
            sendEvent(name: "stopStr", value: "preset/stop")
        } else {
            sendEvent(name: "stopStr", value: "close/stop")
        }
    } else {
        sendEvent(name: "stopStr", value: "preset/stop")
    }

    log.debug "switch state: ${currstat}  windowShade state: ${currstat1}"
    if ( (currstat == null) || (currstat1 == null)) {
        if (currstat > null) {
            if (currstat >= 75) {
                //sendEvent(name: "windowShade", value: "open")
                finishOpenShade()
            } else if (currstat <= 25) {
                //sendEvent(name: "windowShade", value: "closed")
                finishCloseShade()
            } else {
                //sendEvent(name: "windowShade", value: "partially open")
                finishPartialOpenShade()
            }
        }
    }
}

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, preset (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 = []
    def tempstr = ""
    def statstr = "SAME"

    log.trace "Basic report cmd.value:  ${cmd.value}"

    if (cmd.value == 0) {
        //result << createEvent(name: "switch", value: "off")

        tempstr = "closed"
        if (settings?.shadeType) {
            if (settings.shadeType == "blinds") {
                tempstr = "tilted open"
            }
        }
    } else if (cmd.value == 0xFF) {
        //result << createEvent(name: "switch", value: "on")
        tempstr = "open"

    } else {  // This has never happend
        //result << createEvent(name: "switch", value: "default")
        tempstr="neither open or closed"
    }
    def swstatstr = "${device.latestValue('switch')}"
    if (cmd.value == 0 && swstatstr == "on") { statstr = "DIFFERENT" }
    if (cmd.value == 0xFF && swstatstr == "off") { statstr = "DIFFERENT" }
        
    log.debug "${statstr} Zwave state is ${tempstr}; device stored state is ${device.latestValue('switch')} dimmer level: ${device.latestValue('level')} "
    return result
}

def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
    def result = []
    def tempstr = ""

    log.debug "SwitchBinaryReport cmd.value:  ${cmd.value}"
    
    if (cmd.value == 0) {
        tempstr = "closed"
        if (settings?.shadeType) {
            if (settings.shadeType == "blinds") {
                tempstr = "tilted open"
            }
        }

    } else if (cmd.value == 0xFF) {
        tempstr = "open"

    } else {  // this has never happened
        tempstr="neither open or closed"
    }
    log.debug "Reported state is ${tempstr}; 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 = []
    def tempstr = ""

    log.trace "SwitchMultilevelReport cmd.value:  ${cmd.value}"
    
    if (cmd.value == 0) {
        //result << createEvent(name: "switch", value: "off")
        tempstr = "closed"
        if (settings?.shadeType) {
            if (settings.shadeType == "blinds") {
                tempstr = "tilted open"
            }
        }

    } else if (cmd.value == 0xFF) {
        //result << createEvent(name: "switch", value: "on")
        tempstr = "open"
    } else {
        //result << createEvent(name: "switch", value: "default")
        tempstr="neither open or closed"
    }
    //result << createEvent(name: "level",value: cmd.value, unit:"%",
      //descriptionText:"${device.displayName} dimmed ${cmd.value==255 ? 100 : cmd.value}%")
    log.debug "Reported state is ${tempstr}; device is ${device.latestValue('switch')}  ${device.latestValue('level')} "
    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 preset position"
    setLevel(50) 
}

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

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

def presetPosition() {
    log.trace "presetPosition()"
    setLevel(50)
}

def OpenSync() {
    log.trace "OpenSync()"
    finishOpenShade()
}

def CloseSync() {
    log.trace "CloseSync()"
    finishCloseShade()
}

def TiltSync() {
    log.trace "TiltSync()"
    finishPartialOpenShade()
}

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) {

        if (level >= 75) {
            sendEvent(name: "windowShade", value: "opening")
            sendEvent(name: "level", value: level)
            sendEvent(name: "switch", value: "on")
            runIn(25, "finishOpenShade", [overwrite: true])
            delayBetween([
                zwave.switchMultilevelV1.switchMultilevelSet(value: 0xFF).format(),
                zwave.basicV1.basicGet().format()
//                sendEvent(name: "windowShade", value: "open"),
//                sendEvent(name: "switch", value: "on")
            ], 4000)
        } else if (level <= 25) {
            sendEvent(name: "windowShade", value: "closing")
            sendEvent(name: "switch", value: "off")
            runIn(25, "finishCloseShade", [overwrite: true])
            if (settings.shadeType == "shades") {
                delayBetween([
                    zwave.switchMultilevelV1.switchMultilevelSet(value: 0x00).format(),
                    zwave.basicV1.basicGet().format()
//                    sendEvent(name: "windowShade", value: "closed"),
//                    sendEvent(name: "switch", value: "off")
                ], 4000)
            } else {
                delayBetween([
                    zwave.switchMultilevelV1.switchMultilevelStopLevelChange().format(),
                    zwave.basicV1.basicGet().format()
//                    sendEvent(name: "windowShade", value: "closed"),
//                    sendEvent(name: "switch", value: "off")
                ], 4000)
            }
        } else {
            def currstat = device.latestValue("windowShade")
            if (currstat == "open") { sendEvent(name: "windowShade", value: "closing") }
            else { sendEvent(name: "windowShade", value: "opening") }
            sendEvent(name: "level", value: level)
            sendEvent(name: "switch", value: "on")
            runIn(15, "finishPartialOpenShade", [overwrite: true])
            if (settings.shadeType == "shades") {
                delayBetween([
                    zwave.switchMultilevelV1.switchMultilevelStopLevelChange().format(),
                    zwave.basicV1.basicGet().format()
//                    sendEvent(name: "windowShade", value: "partially open"),
//                    sendEvent(name: "switch", value: "default")
                ], 4000)
            } else {
                delayBetween([
                    zwave.switchMultilevelV1.switchMultilevelSet(value: 0x00).format(),
                    zwave.basicV1.basicGet().format()
//                    sendEvent(name: "windowShade", value: "partially open"),
//                    sendEvent(name: "switch", value: "default")
                ], 4000)
            }
        }

        // this code below causes commands not be sent/received by the Somfy ZRTSII - I assume delayBetween is asynchronous...

        //log.trace("finished level adjust")
        //if (newlevel != level) { 
            //log.trace("finished level adjust1")
            //delayBetween([
                //sendEvent(name: "level", value: newlevel)
            //], 1000)
        //}
    }
}

def finishOpenShade() {
    sendEvent(name: "windowShade", value: "open")
    def newlevel = 100
    sendEvent(name: "level", value: newlevel)
    sendEvent(name: "switch", value: "on")
}

def finishCloseShade() {
    sendEvent(name: "windowShade", value: "closed")
    def newlevel = 100
    sendEvent(name: "level", value: newlevel)
    sendEvent(name: "switch", value: "off")
}

def finishPartialOpenShade() {
    sendEvent(name: "windowShade", value: "partially open")
    def newlevel = 50
    sendEvent(name: "level", value: newlevel)
    sendEvent(name: "switch", value: "on")
}

// this appears to never be called

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