matt123454321

MCOHome MH10 Hubitat driver

Jan 25th, 2021 (edited)
611
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Groovy 13.24 KB | None | 0 0
  1. /*
  2. V1.1 Fully working version, thanks to Hubitat community users "toyforrick" and "thebearguy" for debugging and fixing the driver. Thanks to their efforts this driver is now fully functional.
  3.  
  4. V1.0 Initial draft of MCOHome MH10 Air Quality Sensor
  5. - Tested with feedback from Hubitat community user "toyforrick"
  6. - Credits to csteele-PD for the AeotecMultiSensor6 driver which is the basis for this driver.
  7. GitHub - HubitatCommunity/AeotecMultiSensor6: Aeon Labs or Aeotec's MultiSensor6 Hubitat Driver
  8.  
  9. */
  10.  
  11. public static String version() { return "v2.0.0" }
  12.  
  13. metadata {
  14. definition (name: "MCOHome-MH10", namespace: "community", author: "mattie_k") {
  15. capability "Temperature Measurement"
  16. capability "Relative Humidity Measurement"
  17. capability "CarbonDioxide Measurement"
  18. capability "Configuration"
  19.  
  20.     command    "refresh"
  21.  
  22.     attribute  "firmware", "decimal"
  23.     attribute  "airQuality", "number"
  24.     attribute  "temperature", "number"
  25.     attribute  "humidity", "number"
  26.     fingerprint  mfr:"015F", prod:"2562", deviceId:"20737", inClusters:"0x5E,0x86,0x72,0x5A,0x85,0x59,0x73,0x70,0x31,0x7A"
  27. }
  28.  
  29.  
  30. preferences {
  31.     input name: "debugOutput",   type: "bool", title: "<b>Enable debug logging?</b>",   description: "<br>", defaultValue: true
  32.     input name: "descTextEnable", type: "bool", title: "<b>Enable descriptionText logging?</b>", defaultValue: true
  33.     input name: "airqReportInterval", type: "number", title: "<b>Air quality report interval</b>", range: "-127...127", description: "<br><i>1 to 127: Report when change > n*0.1ug/m3.<br>-1 to -127: Report when change > (n+256) * 0.1ug/m3.<br>Set to 0 to disable reporting. </i><br>", defaultValue: 10
  34.     input name: "tempReportInterval", type: "number", title: "<b>Temperature report interval</b>", range: "-127...127", description: "<br><i>1 to 127: Report when change > n*0.5 Celsius.<br>-1 to -127: Report when change > (n+256) * 0.5 Celsius.<br>Set to 0 to disable reporting. </i><br>", defaultValue: 1
  35.     input name: "humidReportInterval", type: "number", title: "<b>Humidity report interval</b>", range: "0...100", description: "<br><i>1 to 99: Report when change > n %.<br>Set to 0 to disable reporting. </i><br>", defaultValue: 2
  36.     input name: "factoryReset", type: "number", title: "<b>Restore factory setting (set to 85)</b>", range: "0...85", description: "Only use to reset to factory defaults.<br>In that case set parameter value to 85", defaultValue: 0                      
  37. }
  38. }
  39.  
  40. /*
  41. updated
  42.  
  43. When "Save Preferences" gets clicked...
  44. */
  45. def updated() {
  46. if (debugOutput) log.debug "In Updated with settings: ${settings}"
  47. unschedule()
  48. initialize()
  49. dbCleanUp() // remove antique db entries created in older versions and no longer used.
  50. if (debugOutput) runIn(1800,logsOff)
  51. return(configure(1))
  52. }
  53.  
  54. /*
  55. parse
  56.  
  57. Respond to received zwave commands.
  58. */
  59. def parse(String description) {
  60. // log.debug "In parse() for description: $description"
  61. def result = null
  62. if (description.startsWith("Err 106")) {
  63. log.warn "parse() >> Err 106"
  64. result = createEvent( name: "secureInclusion", value: "failed",
  65. descriptionText: "This sensor (${device.displayName}) failed to complete the network security key exchange. If you are unable to control it via Hubitat, you must remove it from your network and add it again.")
  66. } else if (description != "updated") {
  67. // log.debug "About to zwave.parse($description)"
  68. def cmd = zwave.parse(description, [0x31: 5, 0x71: 1, 0x72: 1, 0x86: 1])
  69. if (cmd) {
  70. // log.debug "About to call handler for ${cmd.toString()}"
  71. result = zwaveEvent(cmd)
  72. }
  73. }
  74. //log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
  75. return result
  76. }
  77.  
  78. /*
  79. installed
  80.  
  81. Doesn't do much other than call initialize().
  82. */
  83. void installed()
  84. {
  85. initialize()
  86. }
  87.  
  88. /*
  89. initialize
  90.  
  91. Doesn't do much.
  92. */
  93. def initialize() {
  94. // Firmware version not supported
  95. // state.firmware = state.firmware ?: 0.0d
  96. }
  97.  
  98. /*
  99. Beginning of Z-Wave Commands
  100. */
  101.  
  102. def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
  103. def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
  104. state.sec = 1
  105. if (debugOutput) log.debug "encapsulated: ${encapsulatedCommand}"
  106. if (encapsulatedCommand) {
  107. zwaveEvent(encapsulatedCommand)
  108. } else {
  109. log.warn "Unable to extract encapsulated cmd from $cmd"
  110. createEvent(descriptionText: cmd.toString())
  111. }
  112. }
  113.  
  114. def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv1.ManufacturerSpecificReport cmd) {
  115. if (debugOutput) log.debug "ManufacturerSpecificReport cmd = $cmd"
  116. if (debugOutput) log.debug "manufacturerId: ${cmd.manufacturerId}"
  117. if (debugOutput) log.debug "manufacturerName: ${cmd.manufacturerName}"
  118. if (debugOutput) log.debug "productId: ${cmd.productId}"
  119. def model = "" // We'll decode the specific model for the log, but we don't currently use this info
  120. if (debugOutput) log.debug "model: ${model}"
  121. if (debugOutput) log.debug "productTypeId: ${cmd.productTypeId}"
  122. def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
  123. updateDataValue("MSR", msr)
  124. }
  125.  
  126. def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
  127. if (debugOutput) log.debug "In multi level report cmd = $cmd"
  128.  
  129. if (cmd.scaledSensorValue == null) return
  130. def map = [:]
  131. switch (cmd.sensorType) {
  132.     case 1:
  133.         if (debugOutput) log.debug "raw temp = $cmd.scaledSensorValue"
  134.         // Convert temperature (if needed) to the system's configured temperature scale
  135.         def finalval = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
  136.  
  137.         if (debugOutput) log.debug "finalval = $finalval"
  138.  
  139.         map.value = finalval
  140.         map.unit = "\u00b0" + getTemperatureScale()
  141.         map.name = "temperature"
  142.         map.descriptionText = "${device.displayName} temperature is ${map.value}${map.unit}"
  143.         if (descTextEnable) log.info "Temperature is ${map.value}${map.unit}"
  144.         break
  145.     case 5:
  146.         if (debugOutput) log.debug "raw humidity = $cmd.scaledSensorValue"
  147.         map.value = roundIt(cmd.scaledSensorValue, 0) as Integer     // .toInteger()
  148.         map.unit = "%"
  149.         map.name = "humidity"
  150.         map.descriptionText = "${device.displayName} humidity is ${map.value}%"
  151.         if (descTextEnable) log.info "Humidity is ${map.value}%"
  152.         break
  153.     // First check which sensor ID reports air quality  
  154.     case 35:
  155.         if (debugOutput) log.debug "raw airQuality = $cmd.scaledSensorValue"
  156.         map.name = "airQuality"
  157.         map.value = roundIt(cmd.scaledSensorValue, 0) as Integer    // .toInteger()
  158.         map.descriptionText = "${device.displayName} air Quality level is ${map.value}"
  159.     if (descTextEnable) log.info "Air quality level is ${map.value}"
  160.         break
  161.  
  162.     default:
  163.         map.descriptionText = cmd.toString()
  164. }
  165. createEvent(map)
  166. }
  167.  
  168. def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
  169. if (debugOutput) log.debug "NotificationReport: ${cmd}"
  170. def result = []
  171. if (cmd.notificationType == 7) {
  172. // Check debug output to determine specs
  173. } else {
  174. log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
  175. result << createEvent(descriptionText: cmd.toString())
  176. }
  177. result
  178. }
  179.  
  180. def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
  181. if (debugOutput) log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
  182. def result = []
  183. result << response(configure(4))
  184. result
  185. }
  186.  
  187. def zwaveEvent(hubitat.zwave.Command cmd) {
  188. if (debugOutput) log.debug "General zwaveEvent cmd: ${cmd}"
  189. createEvent(descriptionText: cmd.toString(), isStateChange: false)
  190. }
  191.  
  192. def zwaveEvent(hubitat.zwave.commands.versionv1.VersionCommandClassReport cmd) {
  193. if (debugOutput) log.debug "in version command class report"
  194. if (debugOutput) log.debug "---VERSION COMMAND CLASS REPORT V1--- ${device.displayName} has version: ${cmd.commandClassVersion} for command class ${cmd.requestedCommandClass} - payload: ${cmd.payload}"
  195. }
  196.  
  197. private command(hubitat.zwave.Command cmd) {
  198. if (debugOutput) log.debug "Sending Z-wave command: ${cmd.toString()}"
  199. return zwaveSecureEncap(cmd.format())
  200. }
  201.  
  202. private commands(commands, delay=1000) {
  203. //if (descTextEnable) log.info "sending commands: ${commands}"
  204. return delayBetween(commands.collect{ command(it) }, delay)
  205. }
  206.  
  207. /*
  208. End of Z-Wave Commands
  209.  
  210. Beginning of Driver Commands
  211. */
  212. def configure(ccc) {
  213. if (debugOutput) log.debug "ccc: $ccc"
  214. if (descTextEnable) log.info "${device.displayName} is configuring its settings"
  215.  
  216. if (debugOutput) {
  217.         log.debug "Test debug output"
  218. }
  219. def a = airqReportInterval ? airqReportInterval : 10
  220. if (debugOutput) log.debug "airqReportInterval is $a"
  221. def b = tempReportInterval ? tempReportInterval : 1
  222. if (debugOutput) log.debug "tempReportInterval is $b"
  223. def c = humidReportInterval ? humidReportInterval : 1
  224. if (debugOutput) log.debug "humidReportInterval is $c"
  225.  
  226.  
  227.  
  228. def now = new Date()
  229. def tf = new java.text.SimpleDateFormat("dd-MMM-yyyy h:mm a")
  230. tf.setTimeZone(location.getTimeZone())
  231. def newtime = "${tf.format(now)}" as String
  232. sendEvent(name: "lastUpdate", value: newtime, descriptionText: "${device.displayName} configured at ${newtime}")
  233.  
  234. setConfigured("true")
  235. def waketime
  236.  
  237. if (timeOptionValueMap[settings.reportInterval] < 300)
  238.     waketime = timeOptionValueMap[settings.reportInterval]
  239. else waketime = 300
  240.  
  241. if (debugOutput) log.debug "wake time reset to $waketime"
  242.  
  243. // Retrieve local temperature scale: "C" = Celsius, "F" = Fahrenheit
  244. // Convert to a value of 1 or 2 as used by the device to select the scale
  245. if (debugOutput) log.debug "Location temperature scale: ${location.getTemperatureScale()}"
  246. byte tempScaleByte = (location.getTemperatureScale() == "C" ? 1 : 2)
  247. selectiveReport = selectiveReporting ? 1 : 0
  248.  
  249. if (factoryReset == null) factoryReset = 0
  250.  
  251. def request = [
  252.         zwave.versionV1.versionGet(),
  253.         zwave.manufacturerSpecificV1.manufacturerSpecificGet(),
  254.  
  255.         //1. set association groups for hub
  256.         zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
  257.         // Automatically generate a report when air quality changes by specified amount
  258.         zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: a.toInteger()),
  259.         // Automatically generate a report when temperature changes by specified amount
  260.         zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: b.toInteger()),
  261.         // Automatically generate a report when humidity changes by specified amount
  262.         zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: c.toInteger()),
  263.         // Automatically generate a report when humidity changes by specified amount
  264.         zwave.configurationV1.configurationSet(parameterNumber: 255, size: 1, scaledConfigurationValue: factoryReset.toInteger()),
  265.         //7. query sensor data
  266.         zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
  267.         zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
  268.         zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 35) //air quality, check sensorType
  269. ]
  270. return commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
  271. }
  272.  
  273. def refresh() {
  274. if (debugOutput) log.debug "in refresh"
  275.  
  276. return commands([
  277.         zwave.versionV1.versionGet(),                                
  278.         zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
  279.         zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
  280.         zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 35) //air quality, check sensorType
  281. ])
  282. }
  283.  
  284. /*
  285. Begin support methods
  286. */
  287.  
  288. def getTimeOptionValueMap() { [
  289. "20 seconds" : 20,
  290. "30 seconds" : 30,
  291. "1 minute" : 60,
  292. "2 minutes" : 260,
  293. "3 minutes" : 360,
  294. "4 minutes" : 460,
  295. "5 minutes" : 560,
  296. "10 minutes" : 1060,
  297. "15 minutes" : 1560,
  298. "30 minutes" : 3060,
  299. "1 hour" : 16060,
  300. "6 hours" : 66060,
  301. "12 hours" : 126060,
  302. "18 hours" : 186060,
  303. "24 hours" : 2460*60,
  304. ]}
  305.  
  306. private setConfigured(configure) {
  307. updateDataValue("configured", configure)
  308. }
  309.  
  310. private isConfigured() {
  311. getDataValue("configured") == "true"
  312. }
  313.  
  314. def roundIt( value, decimals=0 ) {
  315. return (value == null) ? null : value.toBigDecimal().setScale(decimals, BigDecimal.ROUND_HALF_UP)
  316. }
  317.  
  318. def roundIt( BigDecimal value, decimals=0) {
  319. return (value == null) ? null : value.setScale(decimals, BigDecimal.ROUND_HALF_UP)
  320. }
  321.  
  322. def logsOff(){
  323. log.warn "debug logging disabled..."
  324. device.updateSetting("debugOutput",[value:"false",type:"bool"])
  325. }
  326.  
  327. private dbCleanUp() {
  328. // clean up state variables that are obsolete
  329. // state.remove("tempOffset")
  330. // state.remove("version")
  331. // state.remove("Version")
  332. // state.remove("sensorTemp")
  333. // state.remove("author")
  334. // state.remove("Copyright")
  335. state.remove("verUpdate")
  336. state.remove("verStatus")
  337. state.remove("Type")
  338. }
  339.  
  340. /*
  341. padVer
  342.  
  343. Version progression of 1.4.9 to 1.4.10 would mis-compare unless each duple is padded first.
  344. */
  345. String padVer(ver) {
  346. def pad = ""
  347. ver.replaceAll( "[vV]", "" ).split( /./ ).each { pad += it.padLeft( 2, '0' ) }
  348. return pad
  349. }
  350.  
  351. String getThisCopyright(){"© 2021 Mattie_k "}
Add Comment
Please, Sign In to add comment