I have right now working: Button, Motion & Door Sensors.
All of them long past the 60minute threshold.
My hope is, that they keep on working as they did once in the past.
BTW in case you want to try my enhanced device handlers (They automatically install the correct device once paired.)
Motion
[details=Summary]> metadata {
definition (name: "Xiaomi Motion", namespace: "X", author: "X") { capability "Motion Sensor" capability "Configuration" capability "Battery" capability "Sensor"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006" fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch" fingerprint profileId: "0104", deviceId: "0104", inClusters: "0000, 0003, FFFF, 0019", outClusters: "0000, 0004, 0003, 0006, 0008, 0005, 0019", manufacturer: "LUMI", model: "lumi.sensor_motion", deviceJoinName: "Xiaomi Motion"
command "reset"
}
simulator {
}
preferences {
input “motionReset”, “number”, title: “Number of seconds after the last reported activity to report that motion is inactive (in seconds).”, description: “”, value:120, displayDuringSetup: false
}
tiles(scale: 2) {
multiAttributeTile(name:“motion”, type: “generic”, width: 6, height: 4){
tileAttribute (“device.motion”, key: “PRIMARY_CONTROL”) {
attributeState “active”, label:‘motion’, icon:“st.motion.motion.active”, backgroundColor:"#53a7c0"
attributeState “inactive”, label:‘no motion’, icon:“st.motion.motion.inactive”, backgroundColor:"#ffffff"
}
}
valueTile(“battery”, “device.battery”, decoration: “flat”, inactiveLabel: false, width: 2, height: 2) {
state “battery”, label:’${currentValue}% battery’, unit:""
}
standardTile(“reset”, “device.reset”, inactiveLabel: false, decoration: “flat”, width: 2, height: 2) {
state “default”, action:“reset”, label: “Reset Motion” //icon:“st.secondary.refresh”
}
main(["motion"]) details(["motion", "reset"])
}
}
def parse(String description) {
log.debug "description: $description"
def value = zigbee.parse(description)?.text
log.debug "Parse: $value"
Map map = [:]
if (description?.startsWith(‘catchall:’)) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith(‘read attr -’)) {
map = parseReportAttributeMessage(description)
}log.debug "Parse returned $map"
def result = map ? createEvent(map) : nullif (description?.startsWith('enroll request')) { List cmds = enrollResponse() log.debug "enroll response: ${cmds}" result = cmds?.collect { new physicalgraph.device.HubAction(it) } }
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
log.debug cluster
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0xFC02: log.debug 'ACCELERATION' break
case 0x0402: log.debug 'TEMP' // temp is last 2 data values. reverse to swap endian String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() def value = getTemperature(temp) resultMap = getTemperatureResult(value) break }
}
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - “read attr - “).split(”,”).inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
//log.debug “Desc Map: $descMap”Map resultMap = [:]
if (descMap.cluster == “0001” && descMap.attrId == “0020”) {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
else if (descMap.cluster == “0406” && descMap.attrId == “0000”) {
def value = descMap.value.endsWith(“01”) ? “active” : "inactive"
if (settings.motionReset == null || settings.motionReset == “” ) settings.motionReset = 120
if (value == “active”) runIn(settings.motionReset, stopMotion)
resultMap = getMotionResult(value)
}
return resultMap
}private Map parseCustomMessage(String description) {
Map resultMap = [:]
return resultMap
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(’ ')
String msgCode = parsedMsg[2]Map resultMap = [:] switch(msgCode) { case '0x0020': // Closed/No Motion/Dry resultMap = getMotionResult('inactive') break
case '0x0021': // Open/Motion/Wet resultMap = getMotionResult('active') break
case '0x0022': // Tamper Alarm log.debug 'motion with tamper alarm' resultMap = getMotionResult('active') break
case '0x0023': // Battery Alarm break
case '0x0024': // Supervision Report log.debug 'no motion with tamper alarm' resultMap = getMotionResult('inactive') break
case '0x0025': // Restore Report break
case '0x0026': // Trouble/Failure log.debug 'motion with failure alarm' resultMap = getMotionResult('active') break
case '0x0028': // Test Mode break } return resultMap
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery’
def linkText = getLinkText(device)
log.debug rawValue
def result = [
name: ‘battery’,
value: ‘–’
]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0) {}
else {
if (volts > 3.5) {
result.descriptionText = “${linkText} battery has too much power (${volts} volts).”
}
else if (volts > 0){
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = “${linkText} battery was ${result.value}%”
}
}
return result
}
private Map getMotionResult(value) {
log.debug 'motion’
String linkText = getLinkText(device)
String descriptionText = value == ‘active’ ? “${linkText} detected motion” : "${linkText} motion has stopped"
def commands = [
name: ‘motion’,
value: value,
descriptionText: descriptionText
]
return commands
}
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = []
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug “Sending enroll response”
}
def stopMotion() {
sendEvent(name:“motion”, value:“inactive”)
}
def reset() {
sendEvent(name:“motion”, value:“inactive”)
}[/details]
Button:
[details=Summary]> metadata {
definition (name: “Xiaomi Button”, namespace: “X”, author: “X”) {
capability "Button"
capability "Configuration"
capability "Sensor"
capability "Refresh"
capability “Polling”attribute "lastPress", "string" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ" fingerprint profileId: "0104", deviceId: "0104", inClusters: "0000, 0003", outClusters: "0000, 0004, 0003, 0006, 0008, 0005", manufacturer: "LUMI", model: "lumi.sensor_switch", deviceJoinName: "Xiaomi Button"
}
simulator { status "button 1 pressed": "on/off: 0" status "button 1 released": "on/off: 1" } preferences{ input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"", defaultValue: 4, displayDuringSetup: false) }
tiles(scale: 2) {
standardTile(“button”, “device.button”, decoration: “flat”, width: 2, height: 2) {
state “default”, icon: “st.unknown.zwave.remote-controller”, backgroundColor: “#ffffff”
}
standardTile(“refresh”, “device.refresh”, inactiveLabel: false, decoration: “flat”, width: 2, height: 2) {
state “default”, action:“refresh.refresh”, icon:“st.secondary.refresh”
}
main (["button"]) details(["button","refresh"])
}
}
def parse(String description) {
log.debug "Parsing ‘${description}’"
def descMap = zigbee.parseDescriptionAsMap(description)
def results = []
if (description?.startsWith('on/off: '))
results = parseCustomMessage(description)
return results;
}
def configure(){
refresh()
}
def refresh(){
}
private Map parseCustomMessage(String description) {
if (description?.startsWith('on/off: ')) {
if (description == ‘on/off: 0’) //button pressed
createPressEvent(1)
else if (description == ‘on/off: 1’) //button released
createButtonEvent(1)
}
}
//this method determines if a press should count as a push or a hold and returns the relevant event type
private createButtonEvent(button) {
def currentTime = now()
def startOfPress = device.latestState(‘lastPress’).date.getTime()
def timeDif = currentTime - startOfPress
def holdTimeMillisec = (settings.holdTime?:3).toInteger() * 1000if (timeDif < 0) return [] //likely a message sequence issue. Drop this press and wait for another. Probably won't happen... else if (timeDif < holdTimeMillisec) return createButtonPushedEvent(button) else return createButtonHeldEvent(button)
}
private createPressEvent(button) {
return createEvent([name: ‘lastPress’, value: now(), data:[buttonNumber: button], displayed: false])
}
private createButtonPushedEvent(button) {
log.debug "Button ${button} pushed"
return createEvent([
name: “button”,
value: “pushed”,
data:[buttonNumber: button],
descriptionText: “${device.displayName} button ${button} was pushed”,
isStateChange: true,
displayed: true])
}
private createButtonHeldEvent(button) {
log.debug "Button ${button} held"
return createEvent([
name: “button”,
value: “held”,
data:[buttonNumber: button],
descriptionText: “${device.displayName} button ${button} was held”,
isStateChange: true])
}[/details]
Door:
[details=Summary]> metadata {
definition (name: “Xiaomi Door/Window Sensor”, namespace: “X”, author: “X”) {
capability "Configuration"
capability "Sensor"
capability "Contact Sensor"
capability "Refresh"
capability “Polling”command “enrollResponse”
fingerprint profileId: "0104", deviceId: "0104", inClusters: "0000, 0003", outClusters: "0000, 0004, 0003, 0006, 0008, 0005", manufacturer: "LUMI", model: "lumi.sensor_magnet", deviceJoinName: "Xiaomi Door Sensor"
//fingerprint endpointId: “01”, inClusters: “0000,0001”, outClusters: “1234”//, model: “3320-L”, manufacturer: “CentraLite”
//fingerprint endpoint: “01”,
//profileId: “0104”,
//inClusters: “0000,0001”
//outClusters: “1234”}
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:"#ffa81e"
attributeState “closed”, label:’${name}’, icon:“st.contact.contact.closed”, backgroundColor:"#79b821"
}
}
standardTile(“refresh”, “device.refresh”, inactiveLabel: false, decoration: “flat”, width: 2, height: 2) {
state “default”, action:“refresh.refresh”, icon:“st.secondary.refresh”
}
standardTile(“configure”, “device.configure”, inactiveLabel: false, width: 2, height: 2, decoration: “flat”) {
state “configure”, label:’’, action:“configuration.configure”, icon:“st.secondary.configure”
}
main (["contact"]) details(["contact","refresh","configure"])
}
}
def parse(String description) {
log.debug "Parsing ‘${description}’"
Map map = [:]
//def descMap = zigbee.parseDescriptionAsMap(description)
def resultMap = zigbee.getKnownDescription(description)
log.debug "${resultMap}"
if (description?.startsWith('on/off: '))
map = parseCustomMessage(description)
log.debug "Parse returned $map"
def results = map ? createEvent(map) : null
return results;
}
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "${device.deviceNetworkId}"
def endpointId = 1
log.debug "${device.zigbeeId}"
log.debug "${zigbeeEui}“
def configCmds = [
//battery reporting and heartbeat
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 1 {${device.zigbeeId}} {}”, “delay 200”,
“zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}”, “delay 200”,
“send 0x${device.deviceNetworkId} 1 ${endpointId}”, “delay 1500”,
// Writes CIE attribute on end device to direct reports to the hub's EUID "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
log.debug "configure: Write IAS CIE"
return configCmds
}
def enrollResponse() {
log.debug “Enrolling device into the IAS Zone”
[
// Enrolling device into the IAS Zone
"raw 0x500 {01 23 00 00 00}", “delay 200”,
“send 0x${device.deviceNetworkId} 1 1”
]
}
def refresh() {
log.debug "Refreshing Battery"
def endpointId = 1
[
“st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x20”, “delay 200”
] + enrollResponse()
}
private Map parseCustomMessage(String description) {
def result
if (description?.startsWith('on/off: ')) {
if (description == ‘on/off: 0’) //contact closed
result = getContactResult(“closed”)
else if (description == ‘on/off: 1’) //contact opened
result = getContactResult(“open”)
return result
}
}
private Map getContactResult(value) {
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == ‘open’ ? ‘opened’ : ‘closed’}"
return [
name: ‘contact’,
value: value,
descriptionText: descriptionText
]
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j–;
i++;
}
return array
}[/details]