Monoprice Z-Wave Smoke Detector (Device Handler)

I made some changes that should send battery status.

UPDATE: fixed battery status always being requested with every wakeupnotification event.

/**
 *  Monoprice Z-Wave Smoke Detector
 *
 *  Copyright 2015 Jim Worley
 *		Updated by Cuboy29: 8/24 - Add ability to change wakeup time.
 *
 *  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.
 *
 */

preferences {
    // manufacturer default wake up is every hour; optionally increase for better battery life
    input "userWakeUpInterval", "number", title: "Wake Up Interval (seconds)", description: "Default 3600 sec (10 minutes - 7 days)", defaultValue: '3600', required: false, displayDuringSetup: true
}

metadata {
	definition (name: "Monoprice Z-Wave Smoke Detector", namespace: "Smartthings", author: "Jim Wolley") {
		capability "Smoke Detector"
		capability "Sensor"
		capability "Battery"

		attribute "alarmState", "string"
		attribute "deviceAlert", "string"
        
        //Support Command Class
        //0x30 Sensor Binary
        //0x71 Alarm/Notification
        //0x72 Manufacture Specific
        //0x86 Version
        //0x85 Association V2
        //0x80 Battery
        //0x84 Wake Up V2

		fingerprint deviceId: "0xA100", inClusters: "0x30,0x71,0x72,0x86,0x85,0x80,0x84"
	}

	simulator {	
		status "smoke/test": "command: 7105, payload: 01 FF 00 FF 01 02 00 00"
        status "clear": "command: 7105, payload: 01 00 00 FF 01 02 00 00"
        status "tamper" : "command: 7105, payload: 01 FF 00 FF 01 FE 00 00"
        status "wakeUp Device" : "command: 8407, payload:"
		status "battery 100%": "command: 8003, payload: 64"
		status "battery 5%": "command: 8003, payload: 05"
	}

	tiles {
		standardTile("smoke", "device.alarmState", width: 2, height: 2) {
			state("clear", label:"clear", icon:"st.alarm.smoke.clear", backgroundColor:"#ffffff")
			state("smoke", label:"SMOKE", icon:"st.alarm.smoke.smoke", backgroundColor:"#e86d13")
            state("tamper", label:"tamper", icon:"st.alarm.smoke.smoke", backgroundColor:"#e86d13")
			state("lowbat", label:"lowbat", icon:"st.alarm.smoke.smoke", backgroundColor:"#e86d13")
		}
		valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
			state "battery", label:'${currentValue}% battery', unit:""
		}

        
		main "smoke"
		details(["smoke", "battery"])
	}
}

// parse events into attributes
def parse(String description) {
    log.debug "Smoke Detector description: $description"
	def results = []
	if (description.startsWith("Err")) {
	    results << createEvent(descriptionText:description, displayed:true)
	} else {
		def cmd = zwave.parse(description, [0x71: 2, 0x84: 2, 0x80: 1])
		if (cmd) {
            //log.debug "Smoke Detector CMD: $cmd"
            //log.debug "Smoke Detector CMD properties:  ${cmd.getProperties()}"
			zwaveEvent(cmd, results)
		}
	}
	log.debug "\"$description\" parsed to ${results.inspect()}"
	return results
}

def createSmokeEvents(name, results) {

    if (name == "smoke") {
		// these are displayed:false because the composite event is the one we want to see in the app
		results << createEvent(name: "smoke", value: "detected", descriptionText: "$device.displayName smoke was detected!", displayed: false)
        // This composite event is used for updating the tile
		results << createEvent(name: "alarmState", value: name, descriptionText: text)
	} else if (name == "clear") {
		results << createEvent(name: "smoke", value: "clear", descriptionText: "$device.displayName smoke is clear!", displayed: false)
        results << createEvent(name: "alarmState", value: name, descriptionText: text)
	} else if (name == "tamper"){
    	results << createEvent(name: "smoke", value: "tamper", descriptionText: "$device.displayName has been tampered with!", displayed: false)
        results << createEvent(name: "alarmState", value: name, descriptionText: text)
		results << createEvent(name: "deviceAlert",value: name, descriptionText:"$device.displayName has been tampered with!")
	} else if (name == "lowbat"){
   		results << createEvent(name: "smoke", value: "lowbat", descriptionText: "$device.displayName has been tampered with!", displayed: false)
    	results << createEvent(name: "alarmState", value: name, descriptionText: text)
        results << createEvent(name: "deviceAlert",value: name,descriptionText:"$device.displayName has a low battery!")
	}
}

def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
	
    if (cmd.zwaveAlarmEvent == 2){
		createSmokeEvents(cmd.alarmLevel ? "smoke" : "clear", results)
    } else if (cmd.zwaveAlarmEvent == 254){
		createSmokeEvents("tamper", results)
    } else {
    	createSmokeEvents("lowbat", results)
    }
}

def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd, results)
{
        createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
        def userWake = getUserWakeUp(userWakeUpInterval)
        
        // Only ask for battery if we haven't had a BatteryReport in a while
        if (!state.lastbatt || (new Date().time) - state.lastbatt > 50*60*1000) {
                results << response(zwave.batteryV1.batteryGet())
                results << response("delay 1500")  // leave time for device to respond to batteryGet
        }
        // If user has changed userWakeUpInterval, send the new interval to the device 
    	if (state.wakeUpInterval != userWake){
       		state.wakeUpInterval = userWake
            log.debug "Setting New WakeUp Interval to: " + state.wakeUpInterval
        	results << response(zwave.wakeUpV2.wakeUpIntervalSet(seconds:state.wakeUpInterval, nodeid:zwaveHubNodeId))
        	results << response("delay 200")
        	results << response(zwave.wakeUpV2.wakeUpIntervalGet())
            results << response("delay 1500")
    	}  
        results << new physicalgraph.device.HubAction(zwave.wakeUpV2.wakeUpNoMoreInformation().format())
		results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
		results << createEvent(name: "deviceAlert",value:"clear",descriptionText:"No more alerts are found!")
}

def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
	def map = [ name: "battery", unit: "%" ]
	if (cmd.batteryLevel == 0xFF) {
		map.value = 1
		map.descriptionText = "$device.displayName battery is low!"
	} else {
		map.value = cmd.batteryLevel
        results << createEvent(name: "deviceAlert",value:"clear",descriptionText:"Reported Battery Level: $cmd.batteryLevel")
	}
    state.lastbatt = new Date().time
	results << createEvent(map)
}

def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpIntervalCapabilitiesReport cmd, results) {

    def map = [ name: "defaultWakeUpInterval", unit: "seconds" ]
	map.value = cmd.defaultWakeUpIntervalSeconds
	map.displayed = false
	state.defaultWakeUpInterval = cmd.defaultWakeUpIntervalSeconds
    results << createEvent(map)
}

def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpIntervalReport cmd, results) {

	def map = [ name: "reportedWakeUpInterval", unit: "seconds" ]
	map.value = cmd.seconds
	map.displayed = false
    results << createEvent(map)
}

def zwaveEvent(physicalgraph.zwave.Command cmd, results) {
	def event = [ displayed: false ]
	event.linkText = device.label ?: device.name
	event.descriptionText = "$event.linkText: $cmd"
	results << createEvent(event)
}


private getUserWakeUp(userWake) {

    if (!userWake)                       { userWake =     '3600' }  // set default 1 hr if no user preference 
    // make sure user setting is within valid range for device 
    if (userWake.toInteger() <       60) { userWake =       '600' }  // 10 minutes - Mininum
    if (userWake.toInteger() > 604800) { userWake = '604800' }  // 1 week - Maximum
    return userWake.toInteger()
}
2 Likes