Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- V0.0.1 Initial draft of driver, based on MCOHome MH9 carbon dioxide sensor
- - Tested with feedback from Mr. Olsen
- - Credits to csteele-PD for the AeotecMultiSensor6 driver which is the basis for this driver.
- https://github.com/HubitatCommunity/AeotecMultiSensor6
- */
- public static String version() { return "v2.0.0" }
- metadata {
- definition (name: "Eurotronic Air Quality Sensor", namespace: "community", author: "mattie_k") {
- capability "Temperature Measurement"
- capability "Relative Humidity Measurement"
- capability "CarbonDioxide Measurement"
- capability "Configuration"
- command "refresh"
- attribute "firmware", "decimal"
- fingerprint mfr:"015F", prod:"0902", deviceId:"5102", inClusters:"0x5E,0x86,0x72,0x5A,0x85,0x59,0x73,0x70,0x31,0x71,0x9E,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: "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
- 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
- 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}"
- if (debugOutput) log.debug "${device.displayName} is now on ${device.latestValue("powerSource")} power"
- 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() {
- state.firmware = state.firmware ?: 0.0d
- }
- /*
- Beginning of Z-Wave Commands
- */
- //this notification will be sent only when device is battery powered
- def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
- def result = [createEvent(descriptionText: "${device.displayName} woke up")]
- def cmds = []
- if (!isConfigured()) {
- if (debugOutput) log.debug("late configure")
- result << response(configure(3))
- } else {
- //if (debugOutput) log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
- cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
- result << response(cmds)
- }
- result
- }
- 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
- case 17:
- if (debugOutput) log.debug "raw carbonDioxide = $cmd.scaledSensorValue"
- map.name = "carbonDioxide"
- map.value = roundIt(cmd.scaledSensorValue, 0) as Integer // .toInteger()
- map.descriptionText = "${device.displayName} CO2 level is ${map.value}"
- if (descTextEnable) log.info "CO2 level is ${map.value}"
- break
- case 39:
- if (debugOutput) log.debug "raw VOC = $cmd.scaledSensorValue"
- map.name = "VOC"
- map.value = roundIt(cmd.scaledSensorValue, 0) as Integer // .toInteger()
- map.descriptionText = "${device.displayName} VOC level is ${map.value}"
- if (descTextEnable) log.info "VOC 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}"
- }
- def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
- if (debugOutput) log.debug "in version report"
- // SubVersion is in 1/100ths so that 1.01 < 1.08 < 1.10, etc.// state.firmware = 0.0d
- if (cmd.firmwareVersion) {
- BigDecimal fw = cmd.firmwareVersion + (cmd.firmwareSubVersion/100)
- state.firmware = fw
- }
- 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}"
- if(state.firmware < 1.10)
- log.warn "--- WARNING: Device handler expects devices to have firmware 1.10 or later"
- }
- 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 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"
- if (debugOutput) log.debug "Current firmware: ${sprintf ("%1.2f", state.firmware)}"
- // 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
- def request = [
- zwave.versionV1.versionGet(),
- zwave.manufacturerSpecificV1.manufacturerSpecificGet(),
- //1. set association groups for hub
- zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
- // Set CO2 level notification threshold
- zwave.configurationV1.configurationSet(parameterNumber: 1, size: 2, scaledConfigurationValue: co2Threshold as int),
- // Automatically generate a report when CO2 changes by specified amount
- zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: co2ReportInterval as int),
- // Automatically generate a report when temp changes by specified amount
- zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: tempReportInterval as int),
- // Automatically generate a report when humidity changes by specified amount
- zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: humidReportInterval as int),
- // Factory reset device when parameter is set to value 85
- zwave.configurationV1.configurationSet(parameterNumber: 255, size: 1, scaledConfigurationValue: factoryReset as int),
- //7. query sensor data
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 17), //carbonDioxide (CO2)
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 39) //VOC (CO2)
- ]
- return commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
- }
- def refresh() {
- if (debugOutput) log.debug "in refresh"
- return commands([
- zwave.versionV1.versionGet(), // Retrieve version info (includes firmware version)
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 17), //carbonDioxide (CO2)
- zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 39) //VOC
- ])
- }
- /*
- Begin support methods
- */
- def getTimeOptionValueMap() { [
- "20 seconds" : 20,
- "30 seconds" : 30,
- "1 minute" : 60,
- "2 minutes" : 2*60,
- "3 minutes" : 3*60,
- "4 minutes" : 4*60,
- "5 minutes" : 5*60,
- "10 minutes" : 10*60,
- "15 minutes" : 15*60,
- "30 minutes" : 30*60,
- "1 hour" : 1*60*60,
- "6 hours" : 6*60*60,
- "12 hours" : 12*60*60,
- "18 hours" : 18*60*60,
- "24 hours" : 24*60*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 "}
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement