Advertisement
Guest User

Untitled

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