[OBSOLETE] Xioami Zigbee Door/Window Sensor, Motion Sensor, & Smart Button Device Type

Thanks. Just installed!

Tidied up for cut and paste here:

/**
* Copyright 2015 SmartThings
*
* 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.
*
*/
metadata {
definition (name: "Xiaomi Temperature Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Configuration"
capability "Battery"
capability "Refresh"
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Health Check"
capability "Sensor"

	fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
}

simulator {
	status 'H 40': 'catchall: 0104 FC45 01 01 0140 00 D9B9 00 04 C2DF 0A 01 000021780F'
	status 'H 45': 'catchall: 0104 FC45 01 01 0140 00 D9B9 00 04 C2DF 0A 01 0000218911'
	status 'H 57': 'catchall: 0104 FC45 01 01 0140 00 4E55 00 04 C2DF 0A 01 0000211316'
	status 'H 53': 'catchall: 0104 FC45 01 01 0140 00 20CD 00 04 C2DF 0A 01 0000219814'
	status 'H 43':  'read attr - raw: BF7601FC450C00000021A410, dni: BF76, endpoint: 01, cluster: FC45, size: 0C, attrId: 0000, result: success, encoding: 21, value: 10a4'
}

preferences {
	input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
	input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}

tiles(scale: 2) {
    multiAttributeTile(name:"temperature", type: "generic", width: 6, height: 4){
		tileAttribute ("device.temperature", key: "PRIMARY_CONTROL") {
			attributeState "temperature", label:'${currentValue}°',
				backgroundColors:[
					[value: 31, color: "#153591"],
					[value: 44, color: "#1e9cbb"],
					[value: 59, color: "#90d2a7"],
					[value: 74, color: "#44b621"],
					[value: 84, color: "#f1d801"],
					[value: 95, color: "#d04e00"],
					[value: 96, color: "#bc2323"]
				]
		}
	}
	valueTile("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) {
		state "humidity", label:'${currentValue}% humidity', unit:""
	}
    
	valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
		state "battery", label:'${currentValue}% battery'
	}
    
    /*
	standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
		state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
	}
	*/
    
	main "temperature", "humidity"
	details(["temperature", "humidity", "battery", "refresh"])
}
}

def parse(String description) {
log.debug "description: $description"

Map map = [:]
if (description?.startsWith('catchall:')) {
	map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
	map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
	map = parseCustomMessage(description)
}

log.debug "Parse returned $map"
return map ? createEvent(map) : [:]
}

private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break

        case 0x0402:
			if (cluster.command == 0x07) {
				if (cluster.data[0] == 0x00){
					log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
					resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
				}
				else {
					log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
				}
			}
			else {
				// 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

		case 0xFC45:
			// 0x07 - configure reporting
			if (cluster.command != 0x07) {
				String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
				String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
				resultMap = getHumidityResult(display)
			}
            break
    }
}

return resultMap
}

private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
(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 == "0402" && descMap.attrId == "0000") {
	def value = getTemperature(descMap.value)
	resultMap = getTemperatureResult(value)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
	resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
else if (descMap.cluster == "FC45" && descMap.attrId == "0000") {
	def value = getReportAttributeHumidity(descMap.value)
	resultMap = getHumidityResult(value)
}

return resultMap
}

def getReportAttributeHumidity(String value) {
def humidity = null
if (value?.trim()) {
try {
// value is hex with no decimal
def pct = Integer.parseInt(value.trim(), 16) / 100
humidity = String.format('%.0f', pct)
} catch(NumberFormatException nfe) {
log.debug "Error converting $value to humidity"
}
}
return humidity
}

private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
//def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale()) as Float
def value = ((description - "temperature: ").trim()) as Float
if (value >= 100) {
log.debug 'TEMPERATURE: over 100'
value = 0 - (value - 100)
}
if (getTemperatureScale() != "C") {
value = Math.round(celsiusToFahrenheit(value))
}
resultMap = getTemperatureResult(value)
}
else if (description?.startsWith('humidity: ')) {
def pct = (description - "humidity: " - "%").trim()
if (pct.isNumber()) {
def value = Math.round(new BigDecimal(pct)).toString()
resultMap = getHumidityResult(value)
} else {
log.error "invalid humidity: ${pct}"
}
}
return resultMap
}

def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if (getTemperatureScale() == "C") {
return celsius
} 
else {
return celsiusToFahrenheit(celsius) as Integer
}
}

private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)

def result = [:]

def volts = rawValue / 10
if (!(rawValue == 0 || rawValue == 255)) {
	def minVolts = 2.1
	def maxVolts = 3.0
	def pct = (volts - minVolts) / (maxVolts - minVolts)
	def roundedPct = Math.round(pct * 100)
	if (roundedPct <= 0)
		roundedPct = 1
	result.value = Math.min(100, roundedPct)
	result.descriptionText = "${linkText} battery was ${result.value}%"
	result.name = 'battery'

}

return result
}

private Map getTemperatureResult(value) {
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset //as int
Float v = value as Float
value = (v + offset) as Float
}
Float nv = Math.round( (value as Float) * 10.0 ) / 10	// Need at least one decimal point
value = nv as Float
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
]
}

private Map getHumidityResult(value) {
log.debug 'Humidity'
return value ? [name: 'humidity', value: value, unit: '%'] : [:]
}

/*
*
* PING is used by Device-Watch in attempt to reach the Device
*
*/

def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}

def refresh()
{
log.debug "refresh temperature, humidity, and battery"
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
zigbee.readAttribute(0x0402, 0x0000) +
zigbee.readAttribute(0x0001, 0x0020)
}

def configure() {
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])

log.debug "Configuring Reporting and Bindings."
def humidityConfigCmds = [
	"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
	"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
	"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]

// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300)  // send refresh cmds as part of config
}

private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}

private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}

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
}
2 Likes

yep…
was gonna posted it easier… a few posts back…

link to the DHT for temperature / humidity sensor

https://community.smartthings.com/t/xiaomi-zigbee-door-window-sensor-motion-sensor-smart-button-device-type-beta/31948/550?u=paulo_nobrega

and how to add it:

https://community.smartthings.com/t/xiaomi-zigbee-door-window-sensor-motion-sensor-smart-button-device-type-beta/31948/535?u=paulo_nobrega

2 Likes

Missed that one! Thanks for clarifying. Does it report battery then?

damn…just hate this kind of forum… LOL cant just link to a previous… well…

@brumster it doesn’t report battery… but thats the only DH i could found here… with such a long post it’s really hard for a newbie to find anything here…
but it works… i have some doubts about humidity sensor, but i’ve just received a new one and gonna use it to compare…

1 Like

Does this work with smarttthings?

Xiaomi Magic Cube

Yep. Installed one this morning!

Everything you need to know is here:

What’s that for??

The Xiaomi cube ???

One of these: http://xiaomi-mi.com/mi-smart-home/xiaomi-mi-smart-home-cube-white/

Probably best to consider it as a 6 function button (but slightly cooler IMO). I intend to use for control of lights in the lounge via some simple CoRE rules.

They are not expensive either!

1 Like

For the Xiaomi Cube, you may refer to link below

Already linked above.

I’m in the UK and looking to get apme contact and motion sensors.
Amazom uk is not showing anything and ebay are all posted fron China.
Where are you uk peeps getting them from.

Contact sensors available from eBay in Salford I think:

I use Banggood.com. Check the EU warehouse as opposed to the Chinese one, if you can get them from there the delivery is usually quite good.

Thanks fot that. Don’t know why my eBay search didn’t find them.
Still can’t find UK supplier for motion sensors.

No, still not found one. Depends how desperate you are and what quantity you want.

Banggood.com will ship them in 7 days if you pay for the shipping, might be worth it if you want more than one?Possibly the same with TinyDeal.

Let us know how you get on if you order.

Out of stock on Banggood.com.

HI. … are you looking for these?

Original Xiaomi Smart Human Body Sensor - SMART HUMAN BODY SENSOR

Yep, in the UK if possible, if not somewhere with expedited delivery…

They are on sale (just bought one for £8.58) at gearbest with free delivery

Depending on how quick you need them, it might be worth a bash

1 Like

If you need a link

Did you get any indication of a delivery date?