Advertisement
matt123454321

Hubitat driver for Eurotronic Air Quality Sensor (v1.0))

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