[RELEASE] Z-Wave Dimmer Switch with stepper and Low/Med/High

I have fixed the previous bugs and provide the full code below, hope everyone enjoys. This is my first device handler, so I’m sure a proficient developer could clean the code up but its working as designed.


/* Dimmer-Switch-Levels.device.groovy
 *
 * Variation of the stock SmartThings "Dimmer-Switch"
 * Variation of the twack "Better-Dimmer-Switch"
 * Variation of the ChadCK "Z-Wave Smart Fan Control"
 *
 * Device type adds set levels low, medium, and High for preset values (Moods).
 * Adds increment up and down buttons with ability to set value.
 *
 * pmjoen@yahoo.com
 * 20160517
 *
 * v1.3 - Improvement select stepper value up/down shows adjusting in multiAttributeTile
 * v1.2 - Bug fix modify icon for current state in multiAttributeTile 
 * v1.1 - Bug fix showing speed in multiAttributeTile 
 * v1.0 - Initial release of device handler
 *  
*/

metadata {
	// Automatically generated. Make future change here.
	definition (name: "Dimmer-Switch-Levels", namespace: "pmjoen", author: "SmartThings") {
		capability "Switch Level"
		capability "Actuator"
		capability "Indicator"
		capability "Switch"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"

        command "low"
		command "med"
		command "high"
        command "levelUp"
        command "levelDown"

		attribute "currentState", "string"
        attribute "switch", "string"

		//fingerprint deviceId: "0x1101", inClusters: "0x26, 0x27, 0x70, 0x86, 0x72"
	}

		tiles(scale: 2) {
		multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
			tileAttribute ("device.currentState", key: "PRIMARY_CONTROL") {
				attributeState "default", label:'ADJUSTING', action:"refresh.refresh",icon:"st.Lighting.light13", backgroundColor:"#2179b8", nextState: "turningOff"
				attributeState "HIGH", label:'HIGH', action:"switch.off", icon:"st.illuminance.illuminance.bright", backgroundColor:"#79b821", nextState:"turningOff"
				attributeState "MED", label:'MED', action:"switch.off", icon:"st.illuminance.illuminance.light", backgroundColor:"#79b821", nextState:"turningOff" 
				attributeState "LOW", label:'LOW', action:"switch.off", icon:"st.Weather.weather4", backgroundColor:"#79b821", nextState:"turningOff" 
				attributeState "OFF", label:'OFF', action:"switch.on", icon:"st.Lighting.light13", backgroundColor:"#ffffff", nextState: "turningOn"
                attributeState "on", label:'ON', action:"switch.off", icon:"st.Lighting.light11", backgroundColor:"#79b821", nextState:"turningOff"
                attributeState "turningOn", action:"switch.on", label:'TURNINGON', icon:"st.Lighting.light11", backgroundColor:"#2179b8", nextState: "turningOn"
				attributeState "turningOff", action:"switch.off", label:'TURNINGOFF', icon:"st.Lighting.light13", backgroundColor:"#2179b8", nextState: "turningOff"
			}
            
    	tileAttribute("device.level", key: "VALUE_CONTROL") {
                attributeState("VALUE_UP", action: "levelUp")
                attributeState("VALUE_DOWN", action: "levelDown")
			}
		tileAttribute ("device.level", key: "SLIDER_CONTROL") {
				attributeState "level", action:"switch level.setLevel"
			}
		}

		standardTile("indicator", "device.indicatorStatus", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
			state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off"
			state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on"
			state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit"
		}

		standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
			state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
		}

		valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
			state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff"
		}

        standardTile("low", "device.currentState", inactiveLabel: false, width: 2, height: 2, canChangeBackground: false) {
        	state "default", label: 'LOW', action: "low", icon:"st.Weather.weather4", backgroundColor: "#ffffff"
            state "LOW", label:'LOW', action: "low", icon:"st.Weather.weather4", backgroundColor: "#79b821"
            state "ADJUSTING.LOW", label:'LOW', action: "low", icon:"st.Weather.weather4", backgroundColor: "#2179b8"
  		}
        
        standardTile("med", "device.currentState", inactiveLabel: false, width: 2, height: 2, canChangeBackground: false) {
			state "default", label: 'MED', action: "med", icon:"st.illuminance.illuminance.light", backgroundColor: "#ffffff"
            state "MED", label: 'MED', action: "med", icon:"st.illuminance.illuminance.light", backgroundColor: "#79b821"
			state "ADJUSTING.MED", label:'MED', action: "med", icon:"st.illuminance.illuminance.light", backgroundColor: "#2179b8"
		}
            
		standardTile("high", "device.currentState", inactiveLabel: false, width: 2, height: 2, canChangeBackground: false) {
			state "default", label: 'HIGH', action: "high", icon:"st.illuminance.illuminance.bright", backgroundColor: "#ffffff"
            state "HIGH", label: 'HIGH', action: "high", icon:"st.illuminance.illuminance.bright", backgroundColor: "#79b821"
            state "ADJUSTING.HIGH", label:'HIGH', action: "high", icon:"st.illuminance.illuminance.bright", backgroundColor: "#2179b8"
		}

		main(["switch"])
        details(["switch", "low", "med", "high", "level", "indicator", "refresh"])
        
	}
    preferences {
		section("Light Level Values") {
			input "lowThreshold", "number", title: "Low Level", range: "1..99"
			input "medThreshold", "number", title: "Medium Level", range: "1..99"
			input "highThreshold", "number", title: "High Level", range: "1..99"
		}
        section ("Interval Selection") {
            input "stepper", "enum", title: "Interval Value", defaultValue: "10", options: ["5","10","20"]
        }
	}
}

def parse(String description) {
	def item1 = [
		canBeCurrentState: false,
		linkText: getLinkText(device),
		isStateChange: false,
		displayed: false,
		descriptionText: description,
		value:  description
	]
	def result
	def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
	if (cmd) {
		result = createEvent(cmd, item1)
	}
	else {
		item1.displayed = displayed(description, item1.isStateChange)
		result = [item1]
	}
	log.debug "Parse returned ${result?.descriptionText}"
	result
}

def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) {
	def result = doCreateEvent(cmd, item1)
	for (int i = 0; i < result.size(); i++) {
  	result[i].type = "physical"
	}
	log.trace "BasicReport"
  result
}

def createEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd, Map item1) {
	def result = doCreateEvent(cmd, item1)
	for (int i = 0; i < result.size(); i++) {
		result[i].type = "physical"
	}
	log.trace "BasicSet"
	result
}

def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd, Map item1) {
	[]
	log.trace "StartLevel"
}

def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd, Map item1) {
	[response(zwave.basicV1.basicGet())]
}

def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd, Map item1) {
	def result = doCreateEvent(cmd, item1)
	for (int i = 0; i < result.size(); i++) {
		result[i].type = "physical"
	}
	log.trace "SwitchMultiLevelSet"
	result
}

def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) {
	def result = doCreateEvent(cmd, item1)
	result[0].descriptionText = "${item1.linkText} is ${item1.value}"
	result[0].handlerName = cmd.value ? "statusOn" : "statusOff"
	for (int i = 0; i < result.size(); i++) {
		result[i].type = "digital"
	}
	log.trace "SwitchMultilevelReport"
	result
}

def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) {
	def result = [item1]
	def lowThresholdvalue = (settings.lowThreshold != null && settings.lowThreshold != "") ? settings.lowThreshold.toInteger() : 33
	def medThresholdvalue = (settings.medThreshold != null && settings.medThreshold != "") ? settings.medThreshold.toInteger() : 67
	def highThresholdvalue = (settings.highThreshold != null && settings.highThreshold != "") ? settings.highThreshold.toInteger() : 99

	item1.name = "switch"
	item1.value = cmd.value ? "on" : "off"
	if (item1.value == "off") {
		sendEvent(name: "currentState", value: "OFF" as String)
	}
	item1.handlerName = item1.value
	item1.descriptionText = "${item1.linkText} was turned ${item1.value}"
	item1.canBeCurrentState = true
	item1.isStateChange = isStateChange(device, item1.name, item1.value)
	item1.displayed = false

	if (cmd.value) {
		def item2 = new LinkedHashMap(item1)
		item2.name = "level"
		item2.value = cmd.value as String
		item2.unit = "%"
		item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %"
		item2.canBeCurrentState = true
		item2.isStateChange = isStateChange(device, item2.name, item2.value)
		item2.displayed = false
        
        if (item2.value.toInteger() == lowThresholdvalue) { sendEvent(name: "currentState", value: "LOW" as String) } 
        else if (item2.value.toInteger() == medThresholdvalue) { sendEvent(name: "currentState", value: "MED" as String) } 
        else if (item2.value.toInteger() == highThresholdvalue) { sendEvent(name: "currentState", value: "HIGH" as String) } 
        else if (item2.value.toInteger() == 0) { sendEvent(name: "currentState", value: "OFF" as String) } 
        else { sendEvent(name: "currentState", value: "on" as String) } 

		result << item2
	}
	log.trace "doCreateEvent"
	result
}

def createEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd, Map map) {
//def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
	def value = "when off"
	log.trace "ConfigurationReport"
	if (cmd.configurationValue[0] == 1) {value = "when on"}
	if (cmd.configurationValue[0] == 2) {value = "never"}
	[name: "indicatorStatus", value: value, display: false]
}

def createEvent(physicalgraph.zwave.Command cmd,  Map map) {
	// Handles any Z-Wave commands we aren't interested in
	log.debug "UNHANDLED COMMAND $cmd"
}

def on() {
	log.info "on"
	delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}

def off() {
	log.info "off"
	delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}

def setLevel(value) {
	def lowThresholdvalue = (settings.lowThreshold != null && settings.lowThreshold != "") ? settings.lowThreshold.toInteger() : 33
	def medThresholdvalue = (settings.medThreshold != null && settings.medThreshold != "") ? settings.medThreshold.toInteger() : 67
	def highThresholdvalue = (settings.highThreshold != null && settings.highThreshold != "") ? settings.highThreshold.toInteger() : 99
	
	if (value == "LOW") { value = lowThresholdvalue }
	if (value == "MED") { value = medThresholdvalue }
	if (value == "HIGH") { value = highThresholdvalue }

	def level = Math.min(value as Integer, 99)
    
    if (level == lowThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.LOW" as String, displayed: false) } 
    else if (level == medThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.MED" as String, displayed: false) } 
    else if (level == highThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.HIGH" as String, displayed: false) } 
    else if (level == 0) { sendEvent(name: "currentState", value: "OFF" as String, displayed: false) } 
    else { sendEvent(name: "currentState", value: "on" as String, displayed: false) } 
    
    sendEvent(name: "level", value: level, unit: "%")
    
    log.trace "setLevelValue: ${value}"
    
    delayBetween ([zwave.basicV1.basicSet(value: level as Integer).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1000)
}

def resetLevel(value) {
    def level = Math.min(value as Integer, 99)
	delayBetween ([
    	delayBetween ([	zwave.basicV1.basicSet(value: level).format(),
        				zwave.basicV1.basicSet(value: 0x00).format()], 10),	
    	zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}

def setLevel(value, duration) {
	def lowThresholdvalue = (settings.lowThreshold != null && settings.lowThreshold != "") ? settings.lowThreshold.toInteger() : 33
	def medThresholdvalue = (settings.medThreshold != null && settings.medThreshold != "") ? settings.medThreshold.toInteger() : 67
	def highThresholdvalue = (settings.highThreshold != null && settings.highThreshold != "") ? settings.highThreshold.toInteger() : 99

	if (value == "LOW") { value = lowThresholdvalue }
	if (value == "MED") { value = medThresholdvalue }
	if (value == "HIGH") { value = highThresholdvalue }

	def level = Math.min(value as Integer, 99)
	def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
    
    if (level == lowThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.LOW" as String, displayed: false) } 
    else if (level == medThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.MED" as String, displayed: false) } 
    else if (level == highThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.HIGH" as String, displayed: false) }
    else if (level == 0) { sendEvent(name: "currentState", value: "OFF" as String, displayed: false) } 
    else { sendEvent(name: "currentState", value: "on" as String, displayed: false) } 

    log.trace "setLevelValueDuration: ${value}"
    
	zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format()
}

def levelUp(){

	def steppervalue = (settings.stepper != null && settings.stepper != "") ? settings.stepper.toInteger() : 10
    int nextLevel = device.currentValue("level") + steppervalue
    sendEvent(name: "currentState", value: "default" as String, displayed: false)  
	    
	    if( nextLevel > 100){
	    	nextLevel = 100
	    }
        
		log.trace "setLevelUp(value): ${level}"
	    log.debug "Setting dimmer level up to: ${nextLevel}"
	    delayBetween ([zwave.basicV1.basicSet(value: nextLevel).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1500)
	}
	
def levelDown(){

	def steppervalue = (settings.stepper != null && settings.stepper != "") ? settings.stepper.toInteger() : 10
    int nextLevel = device.currentValue("level") - steppervalue
	sendEvent(name: "currentState", value: "default" as String, displayed: false)  
    
	    if (nextLevel < 0){
	    	nextLevel = 0
	    }
	    
	    if (nextLevel == 0){
	    	off()
	    }
        
	    else
	    {
        	log.trace "setLevelDown(value): ${level}"
	    	log.debug "Setting dimmer level down to: ${nextLevel}"
	        delayBetween ([zwave.basicV1.basicSet(value: nextLevel).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1500)
    }
}

def low() {
	log.trace "setLow"
	setLevel("LOW")
}

def med() {
	log.trace "setMed"
	setLevel("MED")
}

def high() {
	log.trace "setHigh"
	setLevel("HIGH")
}

def poll() {
	zwave.switchMultilevelV1.switchMultilevelGet().format()
}

def refresh() {
	zwave.switchMultilevelV1.switchMultilevelGet().format()
}

def indicatorWhenOn() {
	sendEvent(name: "indicatorStatus", value: "when on", display: false)
	zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
}

def indicatorWhenOff() {
	sendEvent(name: "indicatorStatus", value: "when off", display: false)
	zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
}

def indicatorNever() {
	sendEvent(name: "indicatorStatus", value: "never", display: false)
	zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
}