/**
* Bathroom Humidity Fan
*
* Turns on a fan when you start taking a shower... turns it back off when you are done.
* -Uses humidity change rate for rapid response
* -Timeout option when manaully controled (for stench mitigation)
* -Child/Parent with pause/resume (Thanks to Lewis.Heidrick!)
*
* Copyright 2018 Craig Romei
* GNU General Public License v2 (https://www.gnu.org/licenses/gpl-2.0.txt)
*
*/
import groovy.transform.Field
import hubitat.helper.RMUtils
def setVersion() {
state.version = "1.1.43" // Version number of this app
state.InternalName = "BathroomHumidityFan" // this is the name used in the JSON file for this app
}
definition(
name: "Bathroom Humidity Fan Child",
namespace: "Craig.Romei",
author: "Craig Romei",
description: "Control a fan (switch) based on relative humidity.",
category: "Convenience",
parent: "Craig.Romei:Bathroom Humidity Fan",
iconUrl: "",
iconX2Url: "",
iconX3Url: "",
importUrl: "https://raw.githubusercontent.com/napalmcsr/Hubitat_Napalmcsr/master/Apps/BathroomHumidityFan/BathroomHumidityChild.src")
preferences {
page(name: "mainPage")
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
}
def mainPage() {
dynamicPage(name: "", title: "", install: true, uninstall: true, refreshInterval:0) {
ifTrace("mainPage")
setPauseButtonName()
setCreateSmartSwitchButtonName()
section("") {
input name: "Pause", type: "button", title: state.pauseButtonName, submitOnChange:true
input name: "detailedInstructions", type: "bool", title: "Enable detailed instructions", defaultValue: false, submitOnChange: true
input name: "On", type: "button", title: "On", submitOnChange:true
input name: "Off", type: "button", title: "Off", submitOnChange:true
}
section("") {
if ((state.thisName == null) || (state.thisName == "null ")) {state.thisName = "Enter a name for this app."}
input name: "thisName", type: "text", title: "", required:true, submitOnChange:true, defaultValue: "Enter a name for this app."
state.thisName = thisName
updateLabel()
}
section("") {
input "refresh", "bool", title: "Click here to refresh the device status", submitOnChange:true
if (detailedInstructions == true) {paragraph "This is the device that is triggered when conditions are met."}
input "fanSwitch", "capability.switch", title: "${state.fanSwitchStatus}", required: true, submitOnChange:true
if (detailedInstructions == true) {paragraph "This humidity sensor is used to trigger any of the response methods."}
input "humiditySensor", "capability.relativeHumidityMeasurement", title: "${state.humiditySensorStatus} ${state.humiditySensorBatteryStatus}", required: true, submitOnChange:true
paragraph "NOTE: The humidity sensor you select will need to report about 5 min or less."
if (detailedInstructions == true) {paragraph "Rate of change: Triggers when the humidity sensors humidity value increases by more than the humidity increase rate."}
if (detailedInstructions == true) {paragraph "Humidity over fixed threshold: Triggers when the humidity sensors humidity value goes over the humidity threshold."}
if (detailedInstructions == true) {paragraph "Rate of change and humidity over comparison sensor: Triggers when the humidity is greater than the comparison humidity sensor + comparison offset trigger and the rate of change is greater than the humidity increase rate."}
if (detailedInstructions == true) {paragraph "Humidity over comparison senor: Triggers when the humidity sensors humidity value is greater than the comparison sensors humidity value + comparison offset trigger."}
input "humidityResponseMethod", "enum", title: "Humidity Response Method", options: humidityResponseMethodOptions, defaultValue: 1, required: true, multiple: true, submitOnChange:true
app.updateSetting("refresh",[value:"false",type:"bool"])
}
if (settings.humidityResponseMethod?.contains("3") || settings.humidityResponseMethod?.contains("4")) {
section("") {
if (detailedInstructions == true) {paragraph "Comparison sensor is used as a dynamic method of setting a humidity threshold. Combining multiple humidity sensors is a good way of providing a stable baseline humidity value that will adjust over the seasons. Take the comparison sensors humidity plus comparison offset trigger to get your target humidity that you want the fan to come on."}
input "compareHumiditySensor", "capability.relativeHumidityMeasurement", title: "${state.compareHumiditySensorStatus} ${state.compareHumiditySensorBatteryStatus}", required: true, submitOnChange:true
if (compareHumiditySensor) {compareHumiditySensor = (compareHumiditySensor.currentValue("humidity"))}
if (detailedInstructions == true) {paragraph "Comparison offset trigger is used to increase the comparison humidity by a fixed value. This is added to the comparison sensors humidity value to provide a threshold value to trigger the fan. How much deviation from the comparison sensor do you want to trigger the fan? This will set the comparison sensor to be the threshold plus this offset."}
input "compareHumiditySensorOffset", "decimal", title: "Comparison Offset Trigger, Range: 0-100, Default Value:10", required: true, submitOnChange:true, defaultValue: 10
}
}
section("Fan Activation"){
if (detailedInstructions == true) {paragraph "Humidity increase rate: This checks the change between humidity samplings. The sampling rate is device dependent, room size also plays a large part in how fast the humidity will increase. Typical values are around 3 to 6."}
if (settings.humidityResponseMethod?.contains("1") || settings.humidityResponseMethod?.contains("3")) {input "humidityIncreaseRate", "decimal", title: "Humidity Increase Rate, Range: 1-20, Default value: 3", required: true, defaultValue: 3}
if (detailedInstructions == true) {paragraph "Humidity threshold: This is the trigger point when humidity goes above or below this value."}
if (settings.humidityResponseMethod?.contains("2")) {input "humidityThreshold", "decimal", title: "Humidity Threshold (%), Range 1-100, Default Value: 65", required: false, defaultValue: 65}
if (detailedInstructions == true) {paragraph "Fan on delay: When a trigger tries to turn on the fan, it will wait for this delay before kicking on."}
input "fanOnDelay", "number", title: "Delay turning fan on (Minutes), Default Value: 0", required: false, defaultValue: 0
}
section("Fan Deactivation") {
input "humidityDropTimeout", "number", title: "How long after the humidity returns to normal should the fan turn off (minutes)? Default Value: 10", required: true, defaultValue: 10
if (humidityDropTimeout > 0) {input "humidityDropLimit", "decimal", title: "What percentage above the starting humidity before triggering the turn off delay? Default Value: 25", required: true, defaultValue: 25} else {humidityDropLimit = 0}
input "maxRunTime", "number", title: "Maximum time (minutes) for Fan to run when automatically turned on. Default Value: 120", required: false, defaultValue: 120
}
section("Manual Activation") {
input "manualControlMode", "enum", title: "When should the fan turn off when turned on manually?", submitOnChange:true, required: true, options: manualControlModeOptions, defaultValue: 2
if (detailedInstructions == true) { paragraph "When the fan is manually turned on, wait this delay before turning off."}
if (settings.manualControlMode?.contains("2")) {input "manualOffMinutes", "number", title: "How many minutes until the fan is auto-turned-off? Default Value: 20", submitOnChange:true, required: true, defaultValue: 20}
}
section(title: "Additional Features:", hideable: true, hidden: hideAdditionalFeaturesSection()) {
input "deviceActivation", "capability.switch", title: "Switches to turn on and off the fan immediately.", submitOnChange:true, required:false, multiple:true
paragraph ""
input name: "CreateSmartSwitch", type: "button", title: state.createSmartSwitchButtonName, submitOnChange:true, width: 5
paragraph "Create a virtual switch to keep lights on while the fan is running to use in other apps or rules."
paragraph "Note: You can use an existing switch. The app will turn on and off this switch in sync with the fan."
input "smartSwitch", "capability.switch", title: "${state.smartSwitchStatus}", required: false, submitOnChange:true
}
section(title: "Only Run When:", hideable: true, hidden: hideOptionsSection()) {
def timeLabel = timeIntervalLabel()
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false, options: daysOptions
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
input "disabledSwitch", "capability.switch", title: "Switch to Enable and Disable this app", submitOnChange:true, required:false, multiple:true
}
section(title: "Logging Options:", hideable: true, hidden: hideLoggingSection()) {
if (detailedInstructions == true) {paragraph "Enable Info logging for 30 minutes will enable info logs to show up in the Hubitat logs for 30 minutes after which it will turn them off. Useful for checking if the app is performing actions as expected."}
input name: "isInfo", type: "bool", title: "Enable Info logging for 30 minutes?", defaultValue: true
if (detailedInstructions == true) {paragraph "Enable Debug logging for 30 minutes will enable debug logs to show up in the Hubitat logs for 30 minutes after which it will turn them off. Useful for troubleshooting problems."}
input name: "isDebug", type: "bool", title: "Enable debug logging for 30 minutes?", defaultValue: true
if (detailedInstructions == true) {paragraph "Enable Trace logging for 30 minutes will enable trace logs to show up in the Hubitat logs for 30 minutes after which it will turn them off. Useful for following the logic inside the application but usually not neccesary."}
input name: "isTrace", type: "bool", title: "Enable Trace logging for 30 minutes?", defaultValue: true
if (detailedInstructions == true) {paragraph "Logging level is used to permanantly set your logging level for the application. This is useful if you prefer you logging set to a low level and then can use the logging toggles for specific use cases so you dont have to remember to go back in and change them later. It's also useful if you are experiencing issues and need higher logging enabled for longer than 30 minutes."}
input "ifLevel","enum", title: "Logging level", required: false, multiple: true, submitOnChange: false, options: logLevelOptions
paragraph "NOTE: Logging level overrides the temporary logging selections."
}
}
}
// Application settings and startup
@Field static List