Monoprice recessed Zwave plus door sensor

Anyway to add the force open or close tiles on the app side like this DH has? I got them to showup in the app but they didn’t work. Also, this DH is for a zigbee device. Thanks![

Screenshot_20181230-205602|281x500](upload://HOMHRhbp8VQIJPVTEyptgeTjf7.png)

/**

  • Xiaomi Aqara Door/Window Sensor
  • Version 1.1
  • 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.
  • Original device handler code by a4refillpad, adapted for use with Aqara model by bspranger
  • Additional contributions to code by alecm, alixjg, bspranger, gn0st1c, foz333, jmagnuson, rinkek, ronvandegraaf, snalee, tmleafs, twonk, & veeceeoh
  • Known issues:
  • Xiaomi sensors do not seem to respond to refresh requests
  • Inconsistent rendering of user interface text/graphics between iOS and Android devices - This is due to SmartThings, not this device handler
  • Pairing Xiaomi sensors can be difficult as they were not designed to use with a SmartThings hub. See

*/
metadata {
definition (name: “Xiaomi Aqara Door/Window Sensor”, namespace: “bspranger”, author: “bspranger”) {
capability “Configuration”
capability “Sensor”
capability “Contact Sensor”
capability “Battery”
capability “Health Check”

    attribute "lastCheckin", "String"
    attribute "lastCheckinDate", "Date"
    attribute "lastOpened", "String"
    attribute "lastOpenedDate", "Date"
    attribute "batteryRuntime", "String"

    fingerprint endpointId: "01", profileId: "0104", deviceId: "5F01", inClusters: "0000,0003,FFFF,0006", outClusters: "0000,0004,FFFF", manufacturer: "LUMI", model: "lumi.sensor_magnet.aq2", deviceJoinName: "Xiaomi Aqara Door Sensor"

    command "resetBatteryRuntime"
    command "resetClosed"
    command "resetOpen"
}

simulator {
    status "closed": "on/off: 0"
    status "open": "on/off: 1"
}

tiles(scale: 2) {
multiAttributeTile(name:“contact”, type: “generic”, width: 6, height: 4) {
tileAttribute(“device.contact”, key: “PRIMARY_CONTROL”) {
attributeState “open”, label:‘${name}’, icon:“st.contact.contact.open”, backgroundColor:“#e86d13
attributeState “closed”, label:‘${name}’, icon:“st.contact.contact.closed”, backgroundColor:“#00a0dc
}
tileAttribute(“device.lastOpened”, key: “SECONDARY_CONTROL”) {
attributeState(“default”, label:‘Last Opened: ${currentValue}’)
}
}
valueTile(“battery”, “device.battery”, decoration: “flat”, inactiveLabel: false, width: 2, height: 2) {
state “battery”, label:‘${currentValue}%’, unit:“%”, icon:“https://raw.githubusercontent.com/bspranger/Xiaomi/master/images/XiaomiBattery.png”,
backgroundColors:[
[value: 10, color: “#bc2323”],
[value: 26, color: “#f1d801”],
[value: 51, color: “#44b621”]
]
}
valueTile(“spacer”, “spacer”, decoration: “flat”, inactiveLabel: false, width: 1, height: 1) {
state “default”, label:‘’
}
valueTile(“lastcheckin”, “device.lastCheckin”, decoration: “flat”, inactiveLabel: false, width: 4, height: 1) {
state “default”, label:‘Last Event:\n${currentValue}’
}
standardTile(“resetClosed”, “device.resetClosed”, inactiveLabel: false, decoration: “flat”, width: 2, height: 2) {
state “default”, action:“resetClosed”, label:‘Override Close’, icon:“st.contact.contact.closed”
}
standardTile(“resetOpen”, “device.resetOpen”, inactiveLabel: false, decoration: “flat”, width: 2, height: 2) {
state “default”, action:“resetOpen”, label:‘Override Open’, icon:“st.contact.contact.open”
}
valueTile(“batteryRuntime”, “device.batteryRuntime”, inactiveLabel: false, decoration: “flat”, width: 4, height: 1) {
state “batteryRuntime”, label:‘Battery Changed:\n ${currentValue}’
}

    main (["contact"])
details(["contact","battery","resetClosed","resetOpen","spacer","lastcheckin", "spacer", "spacer", "batteryRuntime", "spacer"])

}
preferences {
//Date & Time Config
input description: “”, type: “paragraph”, element: “paragraph”, title: “DATE & CLOCK”
input name: “dateformat”, type: “enum”, title: “Set Date Format\n US (MDY) - UK (DMY) - Other (YMD)”, description: “Date Format”, options:[“US”,“UK”,“Other”]
input name: “clockformat”, type: “bool”, title: “Use 24 hour clock?”
//Battery Reset Config
input description: “If you have installed a new battery, the toggle below will reset the Changed Battery date to help remember when it was changed.”, type: “paragraph”, element: “paragraph”, title: “CHANGED BATTERY DATE RESET”
input name: “battReset”, type: “bool”, title: “Battery Changed?”
//Battery Voltage Offset
input description: “Only change the settings below if you know what you’re doing.”, type: “paragraph”, element: “paragraph”, title: “ADVANCED SETTINGS”
input name: “voltsmax”, title: “Max Volts\nA battery is at 100% at __ volts\nRange 2.8 to 3.4”, type: “decimal”, range: “2.8…3.4”, defaultValue: 3, required: false
input name: “voltsmin”, title: “Min Volts\nA battery is at 0% (needs replacing) at __ volts\nRange 2.0 to 2.7”, type: “decimal”, range: “2…2.7”, defaultValue: 2.5, required: false
}
}

// Parse incoming device messages to generate events
def parse(String description) {
def result = zigbee.getEvent(description)

// Determine current time and date in the user-selected date format and clock style
def now = formatDate()    
def nowDate = new Date(now).getTime()
// Any report - contact sensor & Battery - results in a lastCheckin event and update to Last Checkin tile
// However, only a non-parseable report results in lastCheckin being displayed in events log
sendEvent(name: "lastCheckin", value: now, displayed: false)
sendEvent(name: "lastCheckinDate", value: nowDate, displayed: false)

Map map = [:]

// Send message data to appropriate parsing function based on the type of report	
if (result) {
    log.debug "${device.displayName} Event: ${result}"
    map = getContactResult(result)
    if (map.value == "open") {
        sendEvent(name: "lastOpened", value: now, displayed: false)
        sendEvent(name: "lastOpenedDate", value: nowDate, displayed: false)
    }
} else if (description?.startsWith('catchall:')) {
    map = parseCatchAllMessage(description)
} else if (description?.startsWith('read attr - raw:')) {
    map = parseReadAttr(description)
}

log.debug "${device.displayName}: Parse returned ${map}"
def results = map ? createEvent(map) : null
return results

}

// Convert raw 4 digit integer voltage value into percentage based on minVolts/maxVolts range
private Map getBatteryResult(rawValue) {
// raw voltage is normally supplied as a 4 digit integer that needs to be divided by 1000
// but in the case the final zero is dropped then divide by 100 to get actual voltage value
def rawVolts = rawValue / 1000

def minVolts
def maxVolts

if(voltsmin == null || voltsmin == "")
	minVolts = 2.5
else
minVolts = voltsmin

if(voltsmax == null || voltsmax == "")
	maxVolts = 3.0
else
maxVolts = voltsmax
    
def pct = (rawVolts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.min(100, Math.round(pct * 100))

def result = [
    name: 'battery',
    value: roundedPct,
    unit: "%",
    isStateChange:true,
    descriptionText : "${device.displayName} Battery at ${roundedPct}% (${rawVolts} Volts)"
]

log.debug "${device.displayName}: ${result}"
return result

}

// Check catchall for battery voltage data to pass to getBatteryResult for conversion to percentage report
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def catchall = zigbee.parse(description)
log.debug catchall

if (catchall.clusterId == 0x0000) {
	def MsgLength = catchall.data.size()
	// Xiaomi CatchAll does not have identifiers, first UINT16 is Battery
	if ((catchall.data.get(0) == 0x01 || catchall.data.get(0) == 0x02) && (catchall.data.get(1) == 0xFF)) {
		for (int i = 4; i < (MsgLength-3); i++) {
			if (catchall.data.get(i) == 0x21) { // check the data ID and data type
				// next two bytes are the battery voltage
				resultMap = getBatteryResult((catchall.data.get(i+2)<<8) + catchall.data.get(i+1))
				break
			}
		}
	}
}
return resultMap

}

// Parse raw data on reset button press to retrieve reported battery voltage
private Map parseReadAttr(String description) {
log.debug “${device.displayName}: reset button press detected”
def buttonRaw = (description - “read attr - raw:”)
Map resultMap = [:]

def cluster = description.split(",").find {it.split(":")[0].trim() == "cluster"}?.split(":")[1].trim()
def attrId = description.split(",").find {it.split(":")[0].trim() == "attrId"}?.split(":")[1].trim()
def value = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()
def model = value.split("01FF")[0]
def data = value.split("01FF")[1]

if (cluster == "0000" && attrId == "0005")  {
    def modelName = ""
    // Parsing the model
    for (int i = 0; i < model.length(); i+=2)
    {
        def str = model.substring(i, i+2);
        def NextChar = (char)Integer.parseInt(str, 16);
        modelName = modelName + NextChar
    }
    log.debug "${device.displayName} reported: cluster: ${cluster}, attrId: ${attrId}, value: ${value}, model:${modelName}, data:${data}"
}
if (data[4..7] == "0121") {
    resultMap = getBatteryResult(Integer.parseInt((data[10..11] + data[8..9]),16))
}
return resultMap

}

private Map getContactResult(result) {
def value = result.value == “on” ? “open” : “closed”
def descriptionText = “${device.displayName} was ${value == “open” ? value + “ed” : value}”
return [
name: ‘contact’,
value: value,
isStateChange: true,
descriptionText: descriptionText
]
}

def resetClosed() {
sendEvent(name: “contact”, value: “closed”, descriptionText: “${device.displayName} was manually reset to closed”)
}

def resetOpen() {
def now = formatDate()
def nowDate = new Date(now).getTime()
sendEvent(name: “lastOpened”, value: now, displayed: false)
sendEvent(name: “lastOpenedDate”, value: nowDate, displayed: false)
sendEvent(name: “contact”, value: “open”, descriptionText: “${device.displayName} was manually reset to open”)
}

//Reset the date displayed in Battery Changed tile to current date
def resetBatteryRuntime(paired) {
def now = formatDate(true)
def newlyPaired = paired ? " for newly paired sensor" : “”
sendEvent(name: “batteryRuntime”, value: now)
log.debug “${device.displayName}: Setting Battery Changed to current date${newlyPaired}”
}

// installed() runs just after a sensor is paired using the “Add a Thing” method in the SmartThings mobile app
def installed() {
state.battery = 0
if (!batteryRuntime) resetBatteryRuntime(true)
checkIntervalEvent(“installed”)
}

// configure() runs after installed() when a sensor is paired
def configure() {
log.debug “${device.displayName}: configuring”
state.battery = 0
if (!batteryRuntime) resetBatteryRuntime(true)
checkIntervalEvent(“configured”)
return
}

// updated() will run twice every time user presses save in preference settings page
def updated() {
checkIntervalEvent(“updated”)
if(battReset){
resetBatteryRuntime()
device.updateSetting(“battReset”, false)
}
}

private checkIntervalEvent(text) {
// Device wakes up every 1 hours, this interval allows us to miss one wakeup notification before marking offline
log.debug “${device.displayName}: Configured health checkInterval when ${text}()”
sendEvent(name: “checkInterval”, value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: “zigbee”, hubHardwareId: device.hub.hardwareID])
}

def formatDate(batteryReset) {
def correctedTimezone = “”
def timeString = clockformat ? “HH:mm:ss” : “h:mm:ss aa”

// If user's hub timezone is not set, display error messages in log and events log, and set timezone to GMT to avoid errors
if (!(location.timeZone)) {
    correctedTimezone = TimeZone.getTimeZone("GMT")
    log.error "${device.displayName}: Time Zone not set, so GMT was used. Please set up your location in the SmartThings mobile app."
    sendEvent(name: "error", value: "", descriptionText: "ERROR: Time Zone not set, so GMT was used. Please set up your location in the SmartThings mobile app.")
} 
else {
    correctedTimezone = location.timeZone
}
if (dateformat == "US" || dateformat == "" || dateformat == null) {
    if (batteryReset)
        return new Date().format("MMM dd yyyy", correctedTimezone)
    else
        return new Date().format("EEE MMM dd yyyy ${timeString}", correctedTimezone)
}
else if (dateformat == "UK") {
    if (batteryReset)
        return new Date().format("dd MMM yyyy", correctedTimezone)
    else
        return new Date().format("EEE dd MMM yyyy ${timeString}", correctedTimezone)
    }
else {
    if (batteryReset)
        return new Date().format("yyyy MMM dd", correctedTimezone)
    else
        return new Date().format("EEE yyyy MMM dd ${timeString}", correctedTimezone)
}

}

Blockquote

Why would you have to force a contact sensor open or closed? That sounds like it defeats the purpose of a contact sensor.