Guest User

Untitled

a guest
Jun 19th, 2017
125
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.68 KB | None | 0 0
  1. /**
  2. * Copyright 2015 SmartThings
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  5. * in compliance with the License. You may obtain a copy of the License at:
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  10. * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  11. * for the specific language governing permissions and limitations under the License.
  12. *
  13. */
  14. metadata {
  15. definition (name: "Xiaomi Temperature Sensor", namespace: "smartthings", author: "SmartThings") {
  16. capability "Configuration"
  17. capability "Battery"
  18. capability "Refresh"
  19. capability "Temperature Measurement"
  20. capability "Relative Humidity Measurement"
  21. capability "Health Check"
  22. capability "Sensor"
  23.  
  24. fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
  25. }
  26.  
  27. simulator {
  28. status 'H 40': 'catchall: 0104 FC45 01 01 0140 00 D9B9 00 04 C2DF 0A 01 000021780F'
  29. status 'H 45': 'catchall: 0104 FC45 01 01 0140 00 D9B9 00 04 C2DF 0A 01 0000218911'
  30. status 'H 57': 'catchall: 0104 FC45 01 01 0140 00 4E55 00 04 C2DF 0A 01 0000211316'
  31. status 'H 53': 'catchall: 0104 FC45 01 01 0140 00 20CD 00 04 C2DF 0A 01 0000219814'
  32. status 'H 43': 'read attr - raw: BF7601FC450C00000021A410, dni: BF76, endpoint: 01, cluster: FC45, size: 0C, attrId: 0000, result: success, encoding: 21, value: 10a4'
  33. }
  34.  
  35. preferences {
  36. 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"
  37. input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
  38. }
  39.  
  40. tiles(scale: 2) {
  41. multiAttributeTile(name:"temperature", type: "generic", width: 6, height: 4){
  42. tileAttribute ("device.temperature", key: "PRIMARY_CONTROL") {
  43. attributeState "temperature", label:'${currentValue}°',
  44. backgroundColors:[
  45. [value: 31, color: "#153591"],
  46. [value: 44, color: "#1e9cbb"],
  47. [value: 59, color: "#90d2a7"],
  48. [value: 74, color: "#44b621"],
  49. [value: 84, color: "#f1d801"],
  50. [value: 95, color: "#d04e00"],
  51. [value: 96, color: "#bc2323"]
  52. ]
  53. }
  54. }
  55. valueTile("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) {
  56. state "humidity", label:'${currentValue}% humidity', unit:""
  57. }
  58.  
  59. valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
  60. state "battery", label:'${currentValue}% battery'
  61. }
  62.  
  63. /*
  64. standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
  65. state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
  66. }
  67. */
  68.  
  69. main "temperature", "humidity"
  70. details(["temperature", "humidity", "battery", "refresh"])
  71. }
  72. }
  73.  
  74. def parse(String description) {
  75. log.debug "description: $description"
  76.  
  77. Map map = [:]
  78. if (description?.startsWith('catchall:')) {
  79. map = parseCatchAllMessage(description)
  80. }
  81. else if (description?.startsWith('read attr -')) {
  82. map = parseReportAttributeMessage(description)
  83. }
  84. else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
  85. map = parseCustomMessage(description)
  86. }
  87.  
  88. log.debug "Parse returned $map"
  89. return map ? createEvent(map) : [:]
  90. }
  91.  
  92. private Map parseCatchAllMessage(String description) {
  93. Map resultMap = [:]
  94. def cluster = zigbee.parse(description)
  95. if (shouldProcessMessage(cluster)) {
  96. switch(cluster.clusterId) {
  97. case 0x0001:
  98. // 0x07 - configure reporting
  99. if (cluster.command != 0x07) {
  100. resultMap = getBatteryResult(cluster.data.last())
  101. }
  102. break
  103.  
  104. case 0x0402:
  105. if (cluster.command == 0x07) {
  106. if (cluster.data[0] == 0x00){
  107. log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
  108. resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
  109. }
  110. else {
  111. log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
  112. }
  113. }
  114. else {
  115. // temp is last 2 data values. reverse to swap endian
  116. String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
  117. def value = getTemperature(temp)
  118. resultMap = getTemperatureResult(value)
  119. }
  120. break
  121.  
  122. case 0xFC45:
  123. // 0x07 - configure reporting
  124. if (cluster.command != 0x07) {
  125. String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
  126. String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
  127. resultMap = getHumidityResult(display)
  128. }
  129. break
  130. }
  131. }
  132.  
  133. return resultMap
  134. }
  135.  
  136. private boolean shouldProcessMessage(cluster) {
  137. // 0x0B is default response indicating message got through
  138. boolean ignoredMessage = cluster.profileId != 0x0104 ||
  139. cluster.command == 0x0B ||
  140. (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
  141. return !ignoredMessage
  142. }
  143.  
  144. private Map parseReportAttributeMessage(String description) {
  145. Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
  146. def nameAndValue = param.split(":")
  147. map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
  148. }
  149. log.debug "Desc Map: $descMap"
  150.  
  151. Map resultMap = [:]
  152. if (descMap.cluster == "0402" && descMap.attrId == "0000") {
  153. def value = getTemperature(descMap.value)
  154. resultMap = getTemperatureResult(value)
  155. }
  156. else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
  157. resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
  158. }
  159. else if (descMap.cluster == "FC45" && descMap.attrId == "0000") {
  160. def value = getReportAttributeHumidity(descMap.value)
  161. resultMap = getHumidityResult(value)
  162. }
  163.  
  164. return resultMap
  165. }
  166.  
  167. def getReportAttributeHumidity(String value) {
  168. def humidity = null
  169. if (value?.trim()) {
  170. try {
  171. // value is hex with no decimal
  172. def pct = Integer.parseInt(value.trim(), 16) / 100
  173. humidity = String.format('%.0f', pct)
  174. } catch(NumberFormatException nfe) {
  175. log.debug "Error converting $value to humidity"
  176. }
  177. }
  178. return humidity
  179. }
  180.  
  181. private Map parseCustomMessage(String description) {
  182. Map resultMap = [:]
  183. if (description?.startsWith('temperature: ')) {
  184. //def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale()) as Float
  185. def value = ((description - "temperature: ").trim()) as Float
  186. if (value >= 100) {
  187. log.debug 'TEMPERATURE: over 100'
  188. value = 0 - (value - 100)
  189. }
  190. if (getTemperatureScale() != "C") {
  191. value = Math.round(celsiusToFahrenheit(value))
  192. }
  193. resultMap = getTemperatureResult(value)
  194. }
  195. else if (description?.startsWith('humidity: ')) {
  196. def pct = (description - "humidity: " - "%").trim()
  197. if (pct.isNumber()) {
  198. def value = Math.round(new BigDecimal(pct)).toString()
  199. resultMap = getHumidityResult(value)
  200. } else {
  201. log.error "invalid humidity: ${pct}"
  202. }
  203. }
  204. return resultMap
  205. }
  206.  
  207. def getTemperature(value) {
  208. def celsius = Integer.parseInt(value, 16).shortValue() / 100
  209. if (getTemperatureScale() == "C") {
  210. return celsius
  211. }
  212. else {
  213. return celsiusToFahrenheit(celsius) as Integer
  214. }
  215. }
  216.  
  217. private Map getBatteryResult(rawValue) {
  218. log.debug 'Battery'
  219. def linkText = getLinkText(device)
  220.  
  221. def result = [:]
  222.  
  223. def volts = rawValue / 10
  224. if (!(rawValue == 0 || rawValue == 255)) {
  225. def minVolts = 2.1
  226. def maxVolts = 3.0
  227. def pct = (volts - minVolts) / (maxVolts - minVolts)
  228. def roundedPct = Math.round(pct * 100)
  229. if (roundedPct <= 0)
  230. roundedPct = 1
  231. result.value = Math.min(100, roundedPct)
  232. result.descriptionText = "${linkText} battery was ${result.value}%"
  233. result.name = 'battery'
  234.  
  235. }
  236.  
  237. return result
  238. }
  239.  
  240. private Map getTemperatureResult(value) {
  241. def linkText = getLinkText(device)
  242. if (tempOffset) {
  243. def offset = tempOffset //as int
  244. Float v = value as Float
  245. value = (v + offset) as Float
  246. }
  247. Float nv = Math.round( (value as Float) * 10.0 ) / 10 // Need at least one decimal point
  248. value = nv as Float
  249. def descriptionText = "${linkText} was ${value}°${temperatureScale}"
  250. return [
  251. name: 'temperature',
  252. value: value,
  253. descriptionText: descriptionText,
  254. unit: temperatureScale
  255. ]
  256. }
  257.  
  258. private Map getHumidityResult(value) {
  259. log.debug 'Humidity'
  260. return value ? [name: 'humidity', value: value, unit: '%'] : [:]
  261. }
  262.  
  263. /*
  264. *
  265. * PING is used by Device-Watch in attempt to reach the Device
  266. *
  267. */
  268.  
  269. def ping() {
  270. return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
  271. }
  272.  
  273. def refresh()
  274. {
  275. log.debug "refresh temperature, humidity, and battery"
  276. return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
  277. zigbee.readAttribute(0x0402, 0x0000) +
  278. zigbee.readAttribute(0x0001, 0x0020)
  279. }
  280.  
  281. def configure() {
  282. // Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
  283. // enrolls with default periodic reporting until newer 5 min interval is confirmed
  284. sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
  285.  
  286. log.debug "Configuring Reporting and Bindings."
  287. def humidityConfigCmds = [
  288. "zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
  289. "zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
  290. "send 0x${device.deviceNetworkId} 1 1", "delay 500"
  291. ]
  292.  
  293. // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
  294. // battery minReport 30 seconds, maxReportTime 6 hrs by default
  295. return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
  296. }
  297.  
  298. private hex(value) {
  299. new BigInteger(Math.round(value).toString()).toString(16)
  300. }
  301.  
  302. private String swapEndianHex(String hex) {
  303. reverseArray(hex.decodeHex()).encodeHex()
  304. }
  305.  
  306. private byte[] reverseArray(byte[] array) {
  307. int i = 0;
  308. int j = array.length - 1;
  309. byte tmp;
  310. while (j > i) {
  311. tmp = array[j];
  312. array[j] = array[i];
  313. array[i] = tmp;
  314. j--;
  315. i++;
  316. }
  317. return array
  318. }
Advertisement
Add Comment
Please, Sign In to add comment