Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- 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.
- V1.0 Initial draft of MCOHome MH10 Air Quality Sensor
- - Tested with feedback from Hubitat community user "toyforrick"
- - Credits to csteele-PD for the AeotecMultiSensor6 driver which is the basis for this driver.
- GitHub - HubitatCommunity/AeotecMultiSensor6: Aeon Labs or Aeotec's MultiSensor6 Hubitat Driver
- */
- public static String version() { return "v2.0.0" }
- metadata {
- definition (name: "MCOHome-MH10", namespace: "community", author: "mattie_k") {
- capability "Temperature Measurement"
- capability "Relative Humidity Measurement"
- capability "CarbonDioxide Measurement"
- capability "Configuration"
- command "refresh"
- attribute "firmware", "decimal"
- attribute "airQuality", "number"
- attribute "temperature", "number"
- attribute "humidity", "number"
- fingerprint mfr:"015F", prod:"2562", deviceId:"20737", inClusters:"0x5E,0x86,0x72,0x5A,0x85,0x59,0x73,0x70,0x31,0x7A"
- }
- preferences {
- input name: "debugOutput", type: "bool", title: "<b>Enable debug logging?</b>", description: "<br>", defaultValue: true
- input name: "descTextEnable", type: "bool", title: "<b>Enable descriptionText logging?</b>", defaultValue: true
- 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
- 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
- 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
- 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
- }
- }
- /*
- updated
- When "Save Preferences" gets clicked...
- */
- def updated() {
- if (debugOutput) log.debug "In Updated with settings: ${settings}"
- unschedule()
- initialize()
- dbCleanUp() // remove antique db entries created in older versions and no longer used.
- if (debugOutput) runIn(1800,logsOff)
- return(configure(1))
- }
- /*
- parse
- Respond to received zwave commands.
- */
- def parse(String description) {
- // log.debug "In parse() for description: $description"
- def result = null
- if (description.startsWith("Err 106")) {
- log.warn "parse() >> Err 106"
- result = createEvent( name: "secureInclusion", value: "failed",
- 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.")
- } else if (description != "updated") {
- // log.debug "About to zwave.parse($description)"
- def cmd = zwave.parse(description, [0x31: 5, 0x71: 1, 0x72: 1, 0x86: 1])
- if (cmd) {
- // log.debug "About to call handler for ${cmd.toString()}"
- result = zwaveEvent(cmd)
- }
- }
- //log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
- return result
- }
- /*
- installed
- Doesn't do much other than call initialize().
- */
- void installed()
- {
- initialize()
- }
- /*
- initialize
- Doesn't do much.
- */
- def initialize() {
- // Firmware version not supported
- // state.firmware = state.firmware ?: 0.0d
- }
- /*
- Beginning of Z-Wave Commands
- */
- def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
- def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
- state.sec = 1
- if (debugOutput) log.debug "encapsulated: ${encapsulatedCommand}"
- if (encapsulatedCommand) {
- zwaveEvent(encapsulatedCommand)
- } else {
- log.warn "Unable to extract encapsulated cmd from $cmd"
- createEvent(descriptionText: cmd.toString())
- }
- }
- def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv1.ManufacturerSpecificReport cmd) {
- if (debugOutput) log.debug "ManufacturerSpecificReport cmd = $cmd"
- if (debugOutput) log.debug "manufacturerId: ${cmd.manufacturerId}"
- if (debugOutput) log.debug "manufacturerName: ${cmd.manufacturerName}"
- if (debugOutput) log.debug "productId: ${cmd.productId}"
- def model = "" // We'll decode the specific model for the log, but we don't currently use this info
- if (debugOutput) log.debug "model: ${model}"
- if (debugOutput) log.debug "productTypeId: ${cmd.productTypeId}"
- def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
- updateDataValue("MSR", msr)
- }
- def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
- if (debugOutput) log.debug "In multi level report cmd = $cmd"
- if (cmd.scaledSensorValue == null) return
- def map = [:]
- switch (cmd.sensorType) {
- case 1:
- if (debugOutput) log.debug "raw temp = $cmd.scaledSensorValue"
- // Convert temperature (if needed) to the system's configured temperature scale
- def finalval = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
- if (debugOutput) log.debug "finalval = $finalval"
- map.value = finalval
- map.unit = "\u00b0" + getTemperatureScale()
- map.name = "temperature"
- map.descriptionText = "${device.displayName} temperature is ${map.value}${map.unit}"
- if (descTextEnable) log.info "Temperature is ${map.value}${map.unit}"
- break
- case 5:
- if (debugOutput) log.debug "raw humidity = $cmd.scaledSensorValue"
- map.value = roundIt(cmd.scaledSensorValue, 0) as Integer // .toInteger()
- map.unit = "%"
- map.name = "humidity"
- map.descriptionText = "${device.displayName} humidity is ${map.value}%"
- if (descTextEnable) log.info "Humidity is ${map.value}%"
- break
- // First check which sensor ID reports air quality
- case 35:
- if (debugOutput) log.debug "raw airQuality = $cmd.scaledSensorValue"
- map.name = "airQuality"
- map.value = roundIt(cmd.scaledSensorValue, 0) as Integer // .toInteger()
- map.descriptionText = "${device.displayName} air Quality level is ${map.value}"
- if (descTextEnable) log.info "Air quality level is ${map.value}"
- break
- default:
- map.descriptionText = cmd.toString()
- }
- createEvent(map)
- }
- def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
- if (debugOutput) log.debug "NotificationReport: ${cmd}"
- def result = []
- if (cmd.notificationType == 7) {
- // Check debug output to determine specs
- } else {
- log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
- result << createEvent(descriptionText: cmd.toString())
- }
- result
- }
- def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
- if (debugOutput) log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
- def result = []
- result << response(configure(4))
- result
- }
- def zwaveEvent(hubitat.zwave.Command cmd) {
- if (debugOutput) log.debug "General zwaveEvent cmd: ${cmd}"
- createEvent(descriptionText: cmd.toString(), isStateChange: false)
- }
- def zwaveEvent(hubitat.zwave.commands.versionv1.VersionCommandClassReport cmd) {
- if (debugOutput) log.debug "in version command class report"
- if (debugOutput) log.debug "---VERSION COMMAND CLASS REPORT V1--- ${device.displayName} has version: ${cmd.commandClassVersion} for command class ${cmd.requestedCommandClass} - payload: ${cmd.payload}"
- }
- private command(hubitat.zwave.Command cmd) {
- if (debugOutput) log.debug "Sending Z-wave command: ${cmd.toString()}"
- return zwaveSecureEncap(cmd.format())
- }
- private commands(commands, delay=1000) {
- //if (descTextEnable) log.info "sending commands: ${commands}"
- return delayBetween(commands.collect{ command(it) }, delay)
- }
- /*
- End of Z-Wave Commands
- Beginning of Driver Commands
- */
- def configure(ccc) {
- if (debugOutput) log.debug "ccc: $ccc"
- if (descTextEnable) log.info "${device.displayName} is configuring its settings"
- if (debugOutput) {
- log.debug "Test debug output"
- }
- def a = airqReportInterval ? airqReportInterval : 10
- if (debugOutput) log.debug "airqReportInterval is $a"
- def b = tempReportInterval ? tempReportInterval : 1
- if (debugOutput) log.debug "tempReportInterval is $b"
- def c = humidReportInterval ? humidReportInterval : 1
- if (debugOutput) log.debug "humidReportInterval is $c"
- def now = new Date()
- def tf = new java.text.SimpleDateFormat("dd-MMM-yyyy h:mm a")
- tf.setTimeZone(location.getTimeZone())
- def newtime = "${tf.format(now)}" as String
- sendEvent(name: "lastUpdate", value: newtime, descriptionText: "${device.displayName} configured at ${newtime}")
- setConfigured("true")
- def waketime
- if (timeOptionValueMap[settings.reportInterval] < 300)
- waketime = timeOptionValueMap[settings.reportInterval]
- else waketime = 300
- if (debugOutput) log.debug "wake time reset to $waketime"
- // Retrieve local temperature scale: "C" = Celsius, "F" = Fahrenheit
- // Convert to a value of 1 or 2 as used by the device to select the scale
- if (debugOutput) log.debug "Location temperature scale: ${location.getTemperatureScale()}"
- byte tempScaleByte = (location.getTemperatureScale() == "C" ? 1 : 2)
- selectiveReport = selectiveReporting ? 1 : 0
- if (factoryReset == null) factoryReset = 0
- def request = [
- zwave.versionV1.versionGet(),
- zwave.manufacturerSpecificV1.manufacturerSpecificGet(),
- //1. set association groups for hub
- zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
- // Automatically generate a report when air quality changes by specified amount
- zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: a.toInteger()),
- // Automatically generate a report when temperature changes by specified amount
- zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: b.toInteger()),
- // Automatically generate a report when humidity changes by specified amount
- zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: c.toInteger()),
- // Automatically generate a report when humidity changes by specified amount
- zwave.configurationV1.configurationSet(parameterNumber: 255, size: 1, scaledConfigurationValue: factoryReset.toInteger()),
- //7. query sensor data
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 35) //air quality, check sensorType
- ]
- return commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
- }
- def refresh() {
- if (debugOutput) log.debug "in refresh"
- return commands([
- zwave.versionV1.versionGet(),
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 35) //air quality, check sensorType
- ])
- }
- /*
- Begin support methods
- */
- def getTimeOptionValueMap() { [
- "20 seconds" : 20,
- "30 seconds" : 30,
- "1 minute" : 60,
- "2 minutes" : 260,
- "3 minutes" : 360,
- "4 minutes" : 460,
- "5 minutes" : 560,
- "10 minutes" : 1060,
- "15 minutes" : 1560,
- "30 minutes" : 3060,
- "1 hour" : 16060,
- "6 hours" : 66060,
- "12 hours" : 126060,
- "18 hours" : 186060,
- "24 hours" : 2460*60,
- ]}
- private setConfigured(configure) {
- updateDataValue("configured", configure)
- }
- private isConfigured() {
- getDataValue("configured") == "true"
- }
- def roundIt( value, decimals=0 ) {
- return (value == null) ? null : value.toBigDecimal().setScale(decimals, BigDecimal.ROUND_HALF_UP)
- }
- def roundIt( BigDecimal value, decimals=0) {
- return (value == null) ? null : value.setScale(decimals, BigDecimal.ROUND_HALF_UP)
- }
- def logsOff(){
- log.warn "debug logging disabled..."
- device.updateSetting("debugOutput",[value:"false",type:"bool"])
- }
- private dbCleanUp() {
- // clean up state variables that are obsolete
- // state.remove("tempOffset")
- // state.remove("version")
- // state.remove("Version")
- // state.remove("sensorTemp")
- // state.remove("author")
- // state.remove("Copyright")
- state.remove("verUpdate")
- state.remove("verStatus")
- state.remove("Type")
- }
- /*
- padVer
- Version progression of 1.4.9 to 1.4.10 would mis-compare unless each duple is padded first.
- */
- String padVer(ver) {
- def pad = ""
- ver.replaceAll( "[vV]", "" ).split( /./ ).each { pad += it.padLeft( 2, '0' ) }
- return pad
- }
- String getThisCopyright(){"© 2021 Mattie_k "}
Add Comment
Please, Sign In to add comment