Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * 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.
- *
- * 06-24-2006 : Added Absolute Humidity value to a new tile and the main tile.
- */
- metadata {
- definition (name: "Xiaomi Temperature Sensor with Abs Humidity", namespace: "jscgs350", author: "jscgs350") {
- capability "Configuration"
- capability "Battery"
- capability "Refresh"
- capability "Temperature Measurement"
- capability "Relative Humidity Measurement"
- capability "Health Check"
- capability "Sensor"
- attribute "absoluteHumidity", "number"
- attribute "tempAndHumidity", "string"
- 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:"tempAndHumidity", type: "generic", width: 20, height: 8){
- tileAttribute ("device.tempAndHumidity", key: "PRIMARY_CONTROL") {
- attributeState "tempAndHumidity", 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"]
- ]
- }
- tileAttribute ("device.absoluteHumidity", key: "SECONDARY_CONTROL") {
- attributeState "absoluteHumidity", label:'Absolute Humidity: ${currentValue} g/m3'
- }
- }
- 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'
- }
- valueTile("absoluteHumidity", "device.absoluteHumidity", inactiveLabel: false, width: 2, height: 2) {
- state "absoluteHumidity", label:'${currentValue} g/m3', unit:""
- }
- /*
- standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
- state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
- }
- */
- main "tempAndHumidity", "temperature", "humidity"
- details(["tempAndHumidity","temperature", "humidity", "absoluteHumidity", "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"
- // (6.112*2.71828^((17.67*TEMP)/(TEMP+243.5))*RH*2.1674)/(273.15+TEMP)
- // Source for absolute humidity calc: https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
- def deviceTemp
- if (getTemperatureScale() != "C") {
- deviceTemp = fahrenheitToCelsius(device.currentValue('temperature'))
- } else {
- deviceTemp = device.currentValue('temperature')
- }
- def numerator = (6.112 * Math.exp((17.67 * deviceTemp) / (deviceTemp + 243.5)) * device.currentValue('humidity') * 2.1674)
- def denominator = deviceTemp + 273.15
- def absHumidity = numerator / denominator
- def dispValue = String.format("%3.2f",absHumidity)
- sendEvent("name": "absoluteHumidity", "value": dispValue)
- def tempAndHumidityString = device.currentValue('temperature')+"° ("+device.currentValue("humidity")+"%)"
- sendEvent("name": "tempAndHumidity", "value": tempAndHumidityString)
- 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
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement