Xiaomi Zigbee Door/Window Sensor, Motion Sensor, & Smart Button Device Type [beta]

Well, I’m not having success. (Hard to connect, and then disconnects after 1 hr)
I have 1 Zigbee Hue Lux Bulb directly connected to my hub, but no Hue bridge. I don’t think I have “Hue installed” (what does that mean?)

Martin

This. :arrow_up:

SmartThings is officially certified as a zigbee coordinator using the Zigbee Home Automation 1.2 (ZHA) profile.

If you use zigbee devices not certified for the same profile, they are unlikely to work. They may pair, but after that there’s no telling.

It’s not something SmartThings engineering staff could be expected to address–there’s a reason for third party standards, and if you’re starting with a device which isn’t using that standard, incompatibilities are very likely.

The zigbee standard allows for the use of many different profiles, including manufacturer-proprietary ones. They aren’t interchangeable, and they don’t all use the same addressing or networking schemes.

If you want less expensive certified ZHA devices, the Orvibo line is good. :sunglasses:

(And, no. Xiaomi isn’t using ZLL, either. Zigbee is very popular for home automation devices in China, but most of them are not certified and are using their own proprietary encoding. A certified ZLL device will, per the standard, fall back to ZHA when joined to a certified ZHA coordinator. That’s what happens with Hue bulbs connected directly to the ST hub. But the xiaomi devices aren’t ZLL certified, either.)

1 Like

Just picked up a motion sensor just for the fun of it, wow these are tiny!

Xiaomi uses NXP’s JN516x microcontroller not sure if that helps anyone out getting these to work better, Data sheet is here

For connecting i had much more success if i did the following:
Long press reset (5seconds) until the lights blink shortly.

Then quick presses 3x or even more… didn’t figure out yet the exact amount of times you need to press. If i do this, im usually able to pair them pretty quickly.

Hi everyone. I just got a bunch of xiaomi devices.

  • gateway
  • temperature & humidity sensor
  • window/door sensor
  • button switch
  • PIR sensor
  • camera
  • robot vac on the way

Seem to be working fairly well with it’s app and gateway.Found my way here while googling. I looked around and it appears there is some sort of API for the smart home devices in Chinese. I haven’t had time to read it but some one has also created some android app too. I could be on the wrong track completely but I thought I’ll share the info incase it helps!

So far I’m planning to combine the xiaomi app together with Tasker on android to automate things that the xiaomi app can’t do. It would be awesome to have an open solution though!

here is the link to 'xiaomi smart home open platform’
https://open.home.mi.com/index.html
also someone’s app on GitHub

1 Like

The Motion sensor is also advertised by Xiaomi as a night light, anyone have any idea how to go about adding LED functionality to the DH.

I know these don’t work for beans but still like to try and see if something like Garden Hue might keep it active when it gets a constant command…

I’m looking to add a couple window sensors. Are these stable enough now that you would recommend them out the motion sensors?

1 Like

Nope. Not at all. Still buggy… hoping that the new Zigbee FW update will help but probably shouldn’t get my hopes up

Gearbest has a 2 pack of the door/window sensors for $6.99 with free (slow) shipping. I have an Xiaomi temp/humidity sensor that won’t pair but I figured I’d give these a shot anyway.

I just tried pairing the Button again after the recent Zigbee Firmware update.
So far it keeps on working for more than 12hours.

I now have to try adding the other items as well :slight_smile:

Please keep us posted. I’d order a couple of these and some smart buttons as well if they worked reliably.

I cancelled the order. I kept looking at the temp sensor on my nightstand and decided I didn’t need another useless item (or 2) sitting there :smile:.

Keep us posted!!

I reconnected my button tonight. It was the usual painful process of many resets on the button.
It has been connected, left untouched and then tested working again 2.5 hours later - first time it has ever worked after more than a 60 minute idle period.

(Hub zigbeeFirmware: 2.3.4)

Sounds promising

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) : null

if (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() * 1000

if (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]

1 Like

What type of batteries do these use?

I’ve had a motion sensor working for days now, but it’s literally sitting on the hub so…

I believe the door sensor uses a CR1632 battery (don’t own one myself, so not 100% sure)

I don’t own the sensor yet, but when trying to create the handler i get this error:

Grails.validation.ValidationException: Validation Error(s) occurred during save(): - Field error in object ‘physicalgraph.device.DeviceType’ on field ‘author’: rejected value [null]; codes

And then multiple times

Edit:
Figured it out, it was missing the author:

definition (name: “Xiaomi Door/Window Sensor”, namespace: “Xiaomi”, author: “AlmostSerious”)