Advertisement
matt123454321

MCOHome MH9 driver

Dec 21st, 2020 (edited)
354
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Groovy 14.82 KB | None | 0 0
  1. /*
  2.    Driver for MCOHome MH9 CO2 sensor
  3.  
  4.    V1.0.0 Initial draft of driver, based on MCOHome MH9 carbon dioxide sensor
  5.    - Credits to csteele-PD for the AeotecMultiSensor6 driver which is the basis for this driver.
  6.         https://github.com/HubitatCommunity/AeotecMultiSensor6
  7.    
  8. */
  9.  
  10.  public static String version()      {  return "v2.0.0"  }
  11.  
  12. metadata {
  13.     definition (name: "MCOHome-MH9", namespace: "community", author: "mattie_k") {
  14.         capability "Temperature Measurement"
  15.         capability "Relative Humidity Measurement"
  16.         capability "CarbonDioxide Measurement"
  17.         capability "Configuration"
  18.  
  19.         command    "refresh"
  20.  
  21.         attribute  "firmware", "decimal"
  22.         fingerprint  mfr:"015F", prod:"0902", deviceId:"5102", inClusters:"0x5E,0x86,0x72,0x5A,0x85,0x59,0x73,0x70,0x31,0x71,0x9E,0x7A"
  23.     }
  24.    
  25.  
  26.     preferences {
  27.         input name: "debugOutput",   type: "bool", title: "<b>Enable debug logging?</b>",   description: "<br>", defaultValue: true
  28.         input name: "descTextEnable", type: "bool", title: "<b>Enable descriptionText logging?</b>", defaultValue: true
  29.         input name: "co2Threshold", type: "number", title: "<b>CO2 notification threshold 1-2000ppm</b>", range: "1..5000", description: "<br><i>Set the CO2 level (in ppm) that will trigger a notification.</i><br>", defaultValue: 1200
  30.         input name: "co2ReportInterval", type: "number", title: "<b>CO2 report interval</b>", range: "-127...127", description: "<br><i>1 to 127: Report when change > n*5ppm.<br>-1 to -127: Report when change > (n+256) * 5ppm.<br>Set to 0 to disable reporting. </i><br>", defaultValue: 10
  31.         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
  32.         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
  33.         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                      
  34.     }
  35. }
  36.  
  37.  
  38. /*
  39.     updated
  40.    
  41.     When "Save Preferences" gets clicked...
  42. */
  43. def updated() {
  44.     if (debugOutput) log.debug "In Updated with settings: ${settings}"
  45.     if (debugOutput) log.debug "${device.displayName} is now on ${device.latestValue("powerSource")} power"
  46.     unschedule()
  47.     initialize()
  48.     dbCleanUp()     // remove antique db entries created in older versions and no longer used.
  49.     if (debugOutput) runIn(1800,logsOff)
  50.     return(configure(1))
  51. }
  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. /*
  80.     installed
  81.    
  82.     Doesn't do much other than call initialize().
  83. */
  84. void installed()
  85. {
  86.     initialize()
  87. }
  88.  
  89.  
  90.  
  91. /*
  92.     initialize
  93.    
  94.     Doesn't do much.
  95. */
  96. def initialize() {
  97.     state.firmware = state.firmware ?: 0.0d
  98. }
  99.  
  100.  
  101. /*
  102.     Beginning of Z-Wave Commands
  103. */
  104.  
  105. //this notification will be sent only when device is battery powered
  106. def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
  107.     def result = [createEvent(descriptionText: "${device.displayName} woke up")]
  108.     def cmds = []
  109.     if (!isConfigured()) {
  110.         if (debugOutput) log.debug("late configure")
  111.         result << response(configure(3))
  112.     } else {
  113.         //if (debugOutput) log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
  114.         cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
  115.         result << response(cmds)
  116.     }
  117.     result
  118. }
  119.  
  120.  
  121. def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
  122.     def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
  123.     state.sec = 1
  124.     if (debugOutput) log.debug "encapsulated: ${encapsulatedCommand}"
  125.     if (encapsulatedCommand) {
  126.         zwaveEvent(encapsulatedCommand)
  127.     } else {
  128.         log.warn "Unable to extract encapsulated cmd from $cmd"
  129.         createEvent(descriptionText: cmd.toString())
  130.     }
  131. }
  132.  
  133.  
  134. def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv1.ManufacturerSpecificReport cmd) {
  135.     if (debugOutput) log.debug "ManufacturerSpecificReport cmd = $cmd"
  136.     if (debugOutput) log.debug "manufacturerId:   ${cmd.manufacturerId}"
  137.     if (debugOutput) log.debug "manufacturerName: ${cmd.manufacturerName}"
  138.     if (debugOutput) log.debug "productId:        ${cmd.productId}"
  139.     def model = ""   // We'll decode the specific model for the log, but we don't currently use this info
  140.     if (debugOutput) log.debug "model:            ${model}"
  141.     if (debugOutput) log.debug "productTypeId:    ${cmd.productTypeId}"
  142.     def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
  143.     updateDataValue("MSR", msr)
  144. }
  145.  
  146.  
  147. def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
  148.     if (debugOutput) log.debug "In multi level report cmd = $cmd"
  149.  
  150.     if (cmd.scaledSensorValue == null) return
  151.     def map = [:]
  152.     switch (cmd.sensorType) {
  153.         case 1:
  154.             if (debugOutput) log.debug "raw temp = $cmd.scaledSensorValue"
  155.             // Convert temperature (if needed) to the system's configured temperature scale
  156.             def finalval = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
  157.  
  158.             if (debugOutput) log.debug "finalval = $finalval"
  159.  
  160.             map.value = finalval
  161.             map.unit = "\u00b0" + getTemperatureScale()
  162.             map.name = "temperature"
  163.             map.descriptionText = "${device.displayName} temperature is ${map.value}${map.unit}"
  164.             if (descTextEnable) log.info "Temperature is ${map.value}${map.unit}"
  165.             break
  166.         case 5:
  167.             if (debugOutput) log.debug "raw humidity = $cmd.scaledSensorValue"
  168.             map.value = roundIt(cmd.scaledSensorValue, 0) as Integer     // .toInteger()
  169.             map.unit = "%"
  170.             map.name = "humidity"
  171.             map.descriptionText = "${device.displayName} humidity is ${map.value}%"
  172.             if (descTextEnable) log.info "Humidity is ${map.value}%"
  173.             break
  174.         case 17:
  175.             if (debugOutput) log.debug "raw carbonDioxide = $cmd.scaledSensorValue"
  176.             map.name = "carbonDioxide"
  177.             map.value = roundIt(cmd.scaledSensorValue, 0) as Integer    // .toInteger()
  178.             map.descriptionText = "${device.displayName} CO2 level is ${map.value}"
  179.         if (descTextEnable) log.info "CO2 level is ${map.value}"
  180.             break
  181.         default:
  182.             map.descriptionText = cmd.toString()
  183.     }
  184.     createEvent(map)
  185. }
  186.  
  187. def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
  188.     if (debugOutput) log.debug "NotificationReport: ${cmd}"
  189.     def result = []
  190.     if (cmd.notificationType == 7) {
  191. // Check debug output to determine specs
  192.     } else {
  193.         log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
  194.         result << createEvent(descriptionText: cmd.toString())
  195.     }
  196.     result
  197. }
  198.  
  199.  
  200. def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
  201.     if (debugOutput) log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
  202.     def result = []
  203.     result << response(configure(4))
  204.     result
  205. }
  206.  
  207.  
  208. def zwaveEvent(hubitat.zwave.Command cmd) {
  209.     if (debugOutput) log.debug "General zwaveEvent cmd: ${cmd}"
  210.     createEvent(descriptionText: cmd.toString(), isStateChange: false)
  211. }
  212.  
  213.  
  214. def zwaveEvent(hubitat.zwave.commands.versionv1.VersionCommandClassReport cmd) {
  215.     if (debugOutput) log.debug "in version command class report"
  216.     if (debugOutput) log.debug "---VERSION COMMAND CLASS REPORT V1--- ${device.displayName} has version: ${cmd.commandClassVersion} for command class ${cmd.requestedCommandClass} - payload: ${cmd.payload}"
  217. }
  218.  
  219.  
  220. def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
  221.     if (debugOutput) log.debug "in version report"
  222.     // SubVersion is in 1/100ths so that 1.01 < 1.08 < 1.10, etc.//    state.firmware = 0.0d
  223.     if (cmd.firmwareVersion) {
  224.         BigDecimal fw = cmd.firmwareVersion + (cmd.firmwareSubVersion/100)
  225.         state.firmware = fw
  226.     }
  227.     if (debugOutput) log.debug "---VERSION REPORT V1--- ${device.displayName} is running firmware version: ${String.format("%1.2f",state.firmware)}, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
  228.     if(state.firmware < 1.10)
  229.         log.warn "--- WARNING: Device handler expects devices to have firmware 1.10 or later"
  230. }
  231.  
  232.  
  233. private command(hubitat.zwave.Command cmd) {
  234.         if (debugOutput) log.debug "Sending Z-wave command: ${cmd.toString()}"
  235.         return zwaveSecureEncap(cmd.format())
  236. }
  237.  
  238.  
  239. private commands(commands, delay=1000) {
  240.     //if (descTextEnable) log.info "sending commands: ${commands}"
  241.     return delayBetween(commands.collect{ command(it) }, delay)
  242. }
  243.  
  244.  
  245. /*
  246.     End of Z-Wave Commands
  247.  
  248.     Beginning of Driver Commands
  249.  
  250. */
  251. def configure(ccc) {
  252.     if (debugOutput) log.debug "ccc: $ccc"
  253.     if (descTextEnable) log.info "${device.displayName} is configuring its settings"
  254.  
  255.  
  256.     if (debugOutput) {
  257.             log.debug "Test debug output"
  258.     }
  259.  
  260.     def now = new Date()
  261.     def tf = new java.text.SimpleDateFormat("dd-MMM-yyyy h:mm a")
  262.     tf.setTimeZone(location.getTimeZone())
  263.     def newtime = "${tf.format(now)}" as String
  264.     sendEvent(name: "lastUpdate", value: newtime, descriptionText: "${device.displayName} configured at ${newtime}")
  265.  
  266.     setConfigured("true")
  267.     def waketime
  268.  
  269.     if (timeOptionValueMap[settings.reportInterval] < 300)
  270.         waketime = timeOptionValueMap[settings.reportInterval]
  271.     else waketime = 300
  272.  
  273.     if (debugOutput) log.debug "wake time reset to $waketime"
  274.     if (debugOutput) log.debug "Current firmware: ${sprintf ("%1.2f", state.firmware)}"
  275.  
  276.     // Retrieve local temperature scale: "C" = Celsius, "F" = Fahrenheit
  277.     // Convert to a value of 1 or 2 as used by the device to select the scale
  278.     if (debugOutput) log.debug "Location temperature scale: ${location.getTemperatureScale()}"
  279.     byte tempScaleByte = (location.getTemperatureScale() == "C" ? 1 : 2)
  280.     selectiveReport = selectiveReporting ? 1 : 0
  281.  
  282.     def request = [
  283.             zwave.versionV1.versionGet(),
  284.             zwave.manufacturerSpecificV1.manufacturerSpecificGet(),
  285.  
  286.             //1. set association groups for hub
  287.             zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
  288.  
  289.             // Set CO2 level notification threshold
  290.             zwave.configurationV1.configurationSet(parameterNumber: 1, size: 2, scaledConfigurationValue: co2Threshold as int),
  291.             // Automatically generate a report when CO2 changes by specified amount
  292.             zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: co2ReportInterval as int),
  293.             // Automatically generate a report when temp changes by specified amount
  294.             zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: tempReportInterval as int),
  295.             // Automatically generate a report when humidity changes by specified amount
  296.             zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: humidReportInterval as int),
  297.             // Factory reset device when parameter is set to value 85
  298.             zwave.configurationV1.configurationSet(parameterNumber: 255, size: 1, scaledConfigurationValue: factoryReset as int),
  299.             //7. query sensor data
  300.             zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
  301.             zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
  302.             zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 17) //carbonDioxide (CO2)
  303.     ]
  304.     return commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
  305. }
  306.  
  307.  
  308. def refresh() {
  309.     if (debugOutput) log.debug "in refresh"
  310.  
  311.     return commands([
  312.             zwave.versionV1.versionGet(),                                // Retrieve version info (includes firmware version)
  313.             zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
  314.             zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
  315.             zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 17) //carbonDioxide (CO2)
  316.     ])
  317. }
  318.  
  319. /*
  320.     Begin support methods
  321. */
  322.  
  323.  
  324. def getTimeOptionValueMap() { [
  325.     "20 seconds" : 20,
  326.     "30 seconds" : 30,
  327.     "1 minute"   : 60,
  328.     "2 minutes"  : 2*60,
  329.     "3 minutes"  : 3*60,
  330.     "4 minutes"  : 4*60,
  331.     "5 minutes"  : 5*60,
  332.     "10 minutes" : 10*60,
  333.     "15 minutes" : 15*60,
  334.     "30 minutes" : 30*60,
  335.     "1 hour"     : 1*60*60,
  336.     "6 hours"    : 6*60*60,
  337.     "12 hours"   : 12*60*60,
  338.     "18 hours"   : 18*60*60,
  339.     "24 hours"   : 24*60*60,
  340. ]}
  341.  
  342.  
  343. private setConfigured(configure) {
  344.     updateDataValue("configured", configure)
  345. }
  346.  
  347.  
  348. private isConfigured() {
  349.     getDataValue("configured") == "true"
  350. }
  351.  
  352.  
  353. def roundIt( value, decimals=0 ) {
  354.     return (value == null) ? null : value.toBigDecimal().setScale(decimals, BigDecimal.ROUND_HALF_UP)
  355. }
  356.  
  357.  
  358. def roundIt( BigDecimal value, decimals=0) {
  359.     return (value == null) ? null : value.setScale(decimals, BigDecimal.ROUND_HALF_UP)
  360. }
  361.  
  362.  
  363. def logsOff(){
  364.     log.warn "debug logging disabled..."
  365.     device.updateSetting("debugOutput",[value:"false",type:"bool"])
  366. }
  367.  
  368.  
  369. private dbCleanUp() {
  370.     // clean up state variables that are obsolete
  371. //  state.remove("tempOffset")
  372. //  state.remove("version")
  373. //  state.remove("Version")
  374. //  state.remove("sensorTemp")
  375. //  state.remove("author")
  376. //  state.remove("Copyright")
  377.     state.remove("verUpdate")
  378.     state.remove("verStatus")
  379.     state.remove("Type")
  380. }
  381.  
  382. /*
  383.     padVer
  384.  
  385.     Version progression of 1.4.9 to 1.4.10 would mis-compare unless each duple is padded first.
  386.  
  387. */
  388. String padVer(ver) {
  389.     def pad = ""
  390.     ver.replaceAll( "[vV]", "" ).split( /\./ ).each { pad += it.padLeft( 2, '0' ) }
  391.     return pad
  392. }
  393.  
  394. String getThisCopyright(){"&copy; 2021 Mattie_k "}
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement