/** * 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> humidityResponseMethodOptions = [ ["1": "Rate of change"], ["2": "Humidity Over fixed threshold"], ["3": "Rate of change and humidity over comparison sensor"], ["4": "Humidity Over comparison sensor"] ] @Field static List> manualControlModeOptions = [ ["1": "By Humidity"], ["2": "After Set Time"], ["3": "Manually"], ["4": "Never"] ] // Application settings and startup @Field static List> daysOptions = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"] @Field static List> logLevelOptions = [ ["0": "None"], ["1": "Info"], ["2": "Debug"], ["3": "Trace"] ] def installed() { ifTrace("installed") state.installed = true state.defaultHumidityThresholdValue = 65 state.overThreshold = false updated() } def updated() { ifDebug("Bathroom Humidity Fan Updated") if (state?.installed == null) { state.installed = true } if (state?.createSmartSwitch == null) {(state.createSmartSwitch = false)} unsubscribe() unschedule() if (ifInfo) runIn(1800,infoOff) if (ifDebug) runIn(1800,debugOff) if (ifTrace) runIn(1800,traceOff) initialize() if (fanSwitch?.currentValue("switch") != null) {state.fanSwitchStatus = "[Fan: ${fanSwitch.currentValue("switch")}]" } else if (fanSwitch?.latestValue("switch") != null) {state.fanSwitchStatus = "[Fan: ${fanSwitch.latestValue("switch")}]" } else {state.fanSwitchStatus = "Fan:"} if (humiditySensor?.currentValue("humidity") != null) {state.humiditySensorStatus = "[Humidity: ${humiditySensor.currentValue("humidity")}]" } else if (humiditySensor?.latestValue("humidity") != null) {state.humiditySensorStatus = "[Humidity: ${humiditySensor.latestValue("humidity")}]" } else {state.humiditySensorStatus = "Humidity Sensor"} if (humiditySensor?.currentBattery != null) {state.humiditySensorBatteryStatus = "[Battery: ${humiditySensor.currentValue("battery")}]" } else if (humiditySensor?.currentValue("battery") != null) {state.humiditySensorBatteryStatus = "[Battery: ${humiditySensor.currentValue("battery")}]" } else if (humiditySensor?.latestValue("battery") != null) {state.humiditySensorBatteryStatus = "[Battery: ${humiditySensor.latestValue("battery")}]" } else if (state.humiditySensorBatteryStatus == null) {state.humiditySensorBatteryStatus = " "} if (compareHumiditySensor?.currentValue("humidity") != null) {state.compareHumiditySensorStatus = "[Humidity: ${compareHumiditySensor.currentValue("humidity")}]" } else if (compareHumiditySensor?.latestValue("humidity") != null) {state.compareHumiditySensorStatus = "[Humidity: ${compareHumiditySensor.latestValue("humidity")}]" } else {state.compareHumiditySensorStatus = "Comparison Humidity Sensor"} if (compareHumiditySensor?.currentBattery != null) {state.compareHumiditySensorBatteryStatus = "[Battery: ${compareHumiditySensor.currentBattery}]" } else if (compareHumiditySensor?.currentValue("battery") != null) {state.compareHumiditySensorBatteryStatus = "[Battery: ${compareHumiditySensor.currentValue("battery")}]" } else if (compareHumiditySensor?.latestValue("battery") != null) {state.compareHumiditySensorBatteryStatus = "[Battery: ${compareHumiditySensor.latestValue("battery")}]" } else {state.compareHumiditySensorBatteryStatus = " "} if (smartSwitch?.currentValue("switch") != null) {state.smartSwitchStatus = "[Smart Switch: ${smartSwitch.currentValue("switch")}]" } else if (smartSwitch?.latestValue("switch") != null) {state.smartSwitchStatus = "[Smart Switch: ${smartSwitch.latestValue("switch")}]" } else {state.smartSwitchStatus = "Smart Switch"} } def initialize() { ifTrace("initialize") ifDebug("Settings: ${settings}") subscribe(deviceActivation, "switch", deviceActivationHandler) subscribe(disabledSwitch, "switch", disabledHandler) subscribe(smartSwitch, "switch", smartSwitchHandler) subscribe(compareHumiditySensor, "humidity", compareHumidityHandler) subscribe(compareHumiditySensor, "battery", compareHumidityBatteryHandler) subscribe(fanSwitch, "switch", fanSwitchHandler) subscribe(humiditySensor, "humidity", humidityHandler) subscribe(humiditySensor, "battery", humidityBatteryHandler) if (getChildDevice("SmartSwitch_${app.id}")) {(getChildDevice("SmartSwitch_${app.id}")).currentValue(fanSwitch.currentValue("switch"))} setCreateSmartSwitchButtonName() } def modeChangeHandler() { ifTrace("modeChangeHandler") if (getAllOk() == false) { ifInfo("modeChangeHandler: Entered a disabled mode, turning off the Fan") fanSwitch.off() if (smartSwitch != null) {smartSwitch.off()} state.status = "(Off)" state.automaticallyTurnedOn = false state.turnOffLaterStarted = false unschedule(turnOnFan) unschedule(turnOffFan) updateLabel() } } def disableInfoIn30Handler(evt) { if (isInfo == true) { runIn(1800, infoOff) log.info "Info logging disabling in 30 minutes." } } def disableDebugIn30Handler(evt) { if (isDebug == true) { runIn(1800, debugOff) log.debug "Debug logging disabling in 30 minutes." } } def disableTraceIn30Handler(evt) { if (isTrace == true) { if (isTrace == true) {runIn(1800, traceOff)} log.trace "Trace logging disabling in 30 minutes." } } // Main Humidity Handler def humidityHandler(evt) { // Device status if (evt.value) {state.humiditySensorStatus = "[Humidity: ${evt.value}]" } else if (humiditySensor?.currentValue("humidity") != null) {state.humiditySensorStatus = "[Humidity: ${humiditySensor.currentValue("humidity")}]" } else if (humiditySensor?.latestValue("humidity") != null) {state.humiditySensorStatus = "[Humidity: ${humiditySensor.latestValue("humidity")}]" } else {state.humiditySensorStatus = "Humidity Sensor"} // humidityHandler Action ifTrace("humidityHandler: Running Humidity Check") humidityHandlerVariablesBefore() if (state?.currentHumidity) { state.lastHumidity = state.currentHumidity.toFloat() state.lastHumidityDate = state.currentHumidityDate if (evt?.value.contains("%")) {state.currentHumidity = Double.parseDouble(evt.value.replace("%", ""))} else {state.currentHumidity = evt.value.toFloat()} } // state.currentHumidity = evt.value state.currentHumidityDate = (evt.date.time) configureHumidityVariables() state.overThreshold = checkThreshold(evt) humidityHandlerVariablesAfter() if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("humidityHandler: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if ((getAllOk() != false) && (state?.paused != true) && (state?.disabled != true) && (state?.pausedOrDisabled != true)) { // Humidity On Checks if (settings.humidityResponseMethod?.contains("1")) {rateOfChangeOn()} if (settings.humidityResponseMethod?.contains("2")) {overFixedThresholdOn()} if (settings.humidityResponseMethod?.contains("3")) {compareRateOfChangeOn()} if (settings.humidityResponseMethod?.contains("4")) {overComparisonOn()} // Humidity Off Checks if (settings.humidityResponseMethod?.contains("1")) {rateOfChangeOff()} if (settings.humidityResponseMethod?.contains("2")) {overFixedThresholdOff()} if (settings.humidityResponseMethod?.contains("3")) {compareRateOfChangeOff()} if (settings.humidityResponseMethod?.contains("4")) {overComparisonOff()} } } def humidityBatteryHandler(evt) { // Device status if (evt.value) {state.humiditySensorBatteryStatus = "[Battery: ${evt.value}]" } else if (humiditySensor?.currentBattery != null) {state.humiditySensorBatteryStatus = "[Battery: ${humiditySensor.currentValue("battery")}]" } else if (humiditySensor?.currentValue("battery") != null) {state.humiditySensorBatteryStatus = "[Battery: ${humiditySensor.currentValue("battery")}]" } else if (humiditySensor?.latestValue("battery") != null) {state.humiditySensorBatteryStatus = "[Battery: ${humiditySensor.latestValue("battery")}]" } else if (state.humiditySensorStatus == null) {state.humiditySensorBatteryStatus = " " log.warn "${evt.value}" } // humidityBatteryHandler Action } // Event Handlers def checkThreshold(evt) { ifTrace("checkThreshold") if (Double.parseDouble(evt.value.replace("%", "")) >= humidityThreshold) { if (settings.humidityResponseMethod?.contains("2")) {ifDebug("checkThreshold: Humidity is above the Threshold")} return true } else { return false } } def compareHumidityHandler(evt) { ifTrace("compareHumidityHandler") // Device status if (evt.value) {state.compareHumiditySensorStatus = "[Humidity: ${evt.value}]" } else if (compareHumiditySensor?.currentValue("humidity") != null) {state.compareHumiditySensorStatus = "[Humidity: ${compareHumiditySensor.currentValue("humidity")}]" } else if (compareHumiditySensor?.latestValue("humidity") != null) {state.compareHumiditySensorStatus = "[Humidity: ${compareHumiditySensor.latestValue("humidity")}]" } else {state.compareHumiditySensorStatus = "Comparison Humidity Sensor"} configureHumidityVariables() // compareHumidtyHandler Action state.compareHumidityValue = Double.parseDouble(evt.value.replace("%", "")) if ((settings.humidityResponseMethod?.contains("3") || settings.humidityResponseMethod?.contains("4")) && compareHumiditySensor) { if ((state?.compareHumidityValue != null) && (compareHumiditySensorOffset != null)) {state.compareHumidity = (compareHumiditySensorOffset.toFloat() + state.compareHumidityValue.toFloat()) } else if (state?.compareHumidityValue != null) {state.compareHumidity = state.compareHumidityValue} } } def compareHumidityBatteryHandler(evt) { ifTrace("compareHumidityBatteryHandler") // Device status if (evt.value) {state.compareHumiditySensorBatteryStatus = "[Battery: ${evt.value}]" } else if (compareHumiditySensor?.currentBattery != null) {state.compareHumiditySensorBatteryStatus = "[Battery: ${compareHumiditySensor.currentBattery}]" } else if (compareHumiditySensor?.currentValue("battery") != null) {state.compareHumiditySensorBatteryStatus = "[Battery: ${compareHumiditySensor.currentValue("battery")}]" } else if (compareHumiditySensor?.latestValue("battery") != null) {state.compareHumiditySensorBatteryStatus = "[Battery: ${compareHumiditySensor.latestValue("battery")}]" } else {state.compareHumiditySensorBatteryStatus = " " log.warn "${evt.value}" } // compareHumidtyBatteryHandler Action } def fanSwitchHandler(evt) { ifTrace("fanSwitchHandler") // Device status if (evt.value) {state.fanSwitchStatus = "[Fan: ${evt.value}]" } else if (fanSwitch?.currentValue("switch") != null) {state.fanSwitchStatus = "[Fan: ${fanSwitch.currentValue("switch")}]" } else if (fanSwitch?.latestValue("switch") != null) {state.fanSwitchStatus = "[Fan: ${fanSwitch.latestValue("switch")}]" } else {state.fanSwitchStatus = "Fan:"} configureHumidityVariables() // fanSwitchHandler Action if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("fanSwitchHandler: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if (evt.value == "on") { if (settings.manualControlMode?.contains("2") && !state?.automaticallyTurnedOn && manualOffMinutes) { if (manualOffMinutes == 0) { // Manually turned on - Instant off ifDebug("fanSwitchHandler: Turning the Fan off now") unschedule(turnOffFan) turnOffFan() state.turnOffLaterStarted = false } else { // Manually turned on - Turn off in manualOffMinutes minutes ifDebug("Automatic cutoff for manual activation in ${manualOffMinutes} minutes.") i = (manualOffMinutes * 60) runIn(i, turnOffFan) state.turnOffLaterStarted = false state.status = "(On)" } } else if (state?.automaticallyTurnedOn && maxRunTime) { // Automatically turned on - Scheduled automatic cutoff in maxRunTime minutes ifDebug("Automatic cutoff scheduled in ${maxRunTime} minutes.") i = (maxRunTime * 60) runIn(i, turnOffFanMaxTimeout) } } else if (evt.value == "off") { ifDebug("fanSwitchHandler: Switch turned off") state.status = "(Off)" state.automaticallyTurnedOn = false state.turnOffLaterStarted = false unscheduleFanSwitchCommands() } if (smartSwitch && (evt.value == "on")) {smartSwitch.on()} else if (smartSwitch && (evt.value == "off")) {smartSwitch.off()} updateLabel() } def disabledHandler(evt) { ifTrace("disabledHandler") if (getAllOk() == false) { ifTrace("disabledHandler: getAllOk() = ${getAllOk()}") } else if (disabledSwitch) { disabledSwitch.each { it -> disabledSwitchState = it.currentValue("switch") } if (disabledSwitchState == "on") { state.disabled = false if (state?.paused == true) { state.status = "(Paused)" state.pausedOrDisabled = true } else { state.paused = false state.disabled = false state.pausedOrDisabled = false if (fanSwitch.currentValue("switch") == "off") { state.status = "(Off)" ifDebug("disabledHandler: App was enabled or unpaused and fan was off.") } } } else if (disabledSwitchState == "off") { state.pauseButtonName = "Disabled by Switch" state.status = "(Disabled)" state.disabled = true updateLabel() ifDebug("disabledHandler: App was disabled and fan is ${fanSwitch.currentValue("switch")}.") } } updateLabel() } def deviceActivationHandler(evt) { ifTrace("deviceActivationHandler") if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("deviceActivationHandler: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if (deviceActivation) { deviceActivation.each { it -> deviceActivationState = it.currentValue("switch") } if (evt.value == "on") { turnOnFan() state.turnOnLaterStarted = false state.turnOffLaterStarted = false ifTrace("deviceActivationHandler: Turning on the fan.") } else if (evt.value == "off") { unschedule(turnOnFan) turnOffFan() state.turnOnLaterStarted = false state.turnOffLaterStarted = false ifTrace("deviceActivationHandler: Turning off the fan.") } } } def smartSwitchHandler(evt) { ifTrace("smartSwitchHandler") // Device status if (evt.value) {state.smartSwitchStatus = "[Smart Switch: ${evt.value} ]" } else if (smartSwitch?.currentValue("switch") != null) {state.smartSwitchStatus = "[Smart Switch: ${smartSwitch.currentValue("switch")}]" } else if (smartSwitch?.latestValue("switch") != null) {state.smartSwitchStatus = "[Smart Switch: ${smartSwitch.latestValue("switch")}]" } else {state.smartSwitchStatus = "Smart Switch"} // smartSwitchHandler Action updateLabel() } // Application functions def rateOfChangeOn() { ifTrace("rateOfChangeOn") if (state?.humidityChangeRate == null) {state.humidityChangeRate = 0} if (settings.humidityIncreaseRate == null) {settings.humidityIncreaseRate = 3} if (state?.currentHumidity == null) {state.currentHumidity = 0} if (state?.targetHumidity == null) {state.targetHumidity = 0} if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("rateOfChangeOn: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if (settings.humidityResponseMethod?.contains("1") && (fanSwitch?.currentValue("switch") == "off") && ((state?.humidityChangeRate.toFloat() > settings.humidityIncreaseRate) || (state?.currentHumidity.toFloat() > state?.targetHumidity.toFloat()))) { ifTrace("Tthe humidity is high (or rising fast) and the fan is off, kick on the fan") if ((fanOnDelay > 0) && (fanOnDelay != null)) { ifDebug("rateOfChangeOn: Turning on fan later") unschedule(turnOffFan) i = (fanOnDelay * 60) runIn(i, turnOnFan) state.turnOnLaterStarted = true ifTrace("rateOfChangeOn: Turning on the fan") } else { ifDebug("rateOfChangeOn: Turning on fan due to humidity increase") state.automaticallyTurnedOn = true unschedule(turnOffFan) turnOnFan() state.turnOnLaterStarted = false ifTrace("rateOfChangeOn: Turning on fan") } state.startingHumidity = state.lastHumidity state.highestHumidity = state.currentHumidity } else if (settings.humidityResponseMethod?.contains("1") && (fanSwitch?.currentValue("switch") == "on") && (state.turnOffLaterStarted == true) && ((state?.humidityChangeRate.toFloat() > settings.humidityIncreaseRate) || (state?.currentHumidity.toFloat() > state?.targetHumidity.toFloat()))) { ifTrace("The humidity is high (or rising fast) and the fan is on but, scheduled to turn off. Leaving the fan on") unschedule(turnOffFan) state.turnOnLaterStarted = false state.turnOffLaterStarted = false } ifTrace("rateOfChangeOn: Complete") } def rateOfChangeOff() { ifTrace("rateOfChangeOff") if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("rateOfChangeOff: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if (settings.humidityResponseMethod?.contains("1") && state?.automaticallyTurnedOn && (fanSwitch?.currentValue("switch") == "on") && (state?.currentHumidity.toFloat() <= state?.targetHumidity.toFloat())) { if (humidityDropTimeout == 0) { ifDebug("rateOfChangeOff: Fan Off") unschedule(turnOnFan) turnOffFan() state.turnOffLaterStarted = false ifDebug("rateOfChangeOff: Turning off the fan. Humidity has returned to normal and it was turned on manually.") } else if (!state.turnOffLaterStarted){ ifDebug ("rateOfChangeOff: Turn Fan off in ${(humidityDropTimeout)} minutes.") unschedule(turnOnFan) i = (humidityDropTimeout * 60) runIn(i, turnOffFan) state.turnOffLaterStarted = true ifDebug("Turning off the fan in ${(humidityDropTimeout)} minutes.") } } else if (settings.manualControlMode?.contains("1") && !state?.automaticallyTurnedOn && (fanSwitch?.currentValue("switch") == "on") && (state?.currentHumidity.toFloat() <= state?.targetHumidity.toFloat())) { if (humidityDropTimeout == 0) { ifDebug("rateOfChangeOff: Fan Off") unschedule(turnOnFan) turnOffFan() state.turnOffLaterStarted = false ifDebug("rateOfChangeOff: Turning off the fan. Humidity has returned to normal and it was kicked on by the humidity sensor.") } else if (!state.turnOffLaterStarted){ ifDebug ("rateOfChangeOff: Turn Fan off in ${(humidityDropTimeout)} minutes.") unschedule(turnOnFan) i = (humidityDropTimeout * 60) runIn(i, turnOffFan) state.turnOffLaterStarted = true ifDebug("Turning off the fan in ${humidityDropTimeout} minutes.") } } ifTrace("rateOfChangeOff: Complete") } def overFixedThresholdOn() { ifTrace("overFixedThresholdOn") if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("overFixedThresholdOn: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if (settings.humidityResponseMethod?.contains("2") && (state?.overThreshold == true) && (fanSwitch?.currentValue("switch") == "off")) { ifTrace("If the humidity is over fixed threshold and the fan is off, kick on the fan") if ((fanOnDelay > 0) && (fanOnDelay != null)) { ifDebug("overFixedThresholdOn: Turning on fan later") unschedule(turnOffFan) i = (fanOnDelay * 60) runIn(i, turnOnFan) state.turnOnLaterStarted = true ifTrace("overFixedThresholdOn: Turning on the fan") } else { ifDebug("overFixedThresholdOn: Humidity over threshold. Turning on fan now") state.automaticallyTurnedOn = true unschedule(turnOffFan) turnOnFan() state.turnOnLaterStarted = false ifTrace("overFixedThresholdOn: Turning on fan") } state.startingHumidity = state.lastHumidity state.highestHumidity = state.currentHumidity } else if (settings.humidityResponseMethod?.contains("2") && (state?.overThreshold == true) && (fanSwitch?.currentValue("switch") == "on")) { ifTrace("The humidity is over fixed threshold and the fan is on but, scheduled to turn off. Leaving the fan on") unschedule(turnOffFan) state.turnOnLaterStarted = false state.turnOffLaterStarted = false } ifTrace("overFixedThresholdOn: Complete") } def overFixedThresholdOff() { ifTrace("overFixedThresholdOff") if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("overFixedThresholdOff: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if (settings.humidityResponseMethod?.contains("2") && (state?.overThreshold == false) && state?.automaticallyTurnedOn) { ifTrace("overFixedThresholdOff: state?.automaticallyTurnedOn = ${state?.automaticallyTurnedOn} settings.manualControlMode?.contains(2) = ${settings.manualControlMode?.contains("2")} !state.turnOffLaterStarted = ${!state.turnOffLaterStarted}") if (humidityDropTimeout.toInteger() == 0) { unschedule(turnOnFan) turnOffFan() state.turnOffLaterStarted = false ifDebug("overFixedThresholdOff: Turning off the fan. Humidity has returned to normal and it was kicked on by the humidity sensor.") } else if (!state.turnOffLaterStarted){ ifTrace ("overFixedThresholdOff: Turn Fan off in ${(humidityDropTimeout)} minutes.") unschedule(turnOnFan) i = (humidityDropTimeout.toInteger() * 60) runIn(i, turnOffFan) state.turnOffLaterStarted = true ifDebug("Turning off the fan in ${humidityDropTimeout} minutes.") ifTrace("overFixedThresholdOff: state.turnOffLaterStarted = ${state?.turnOffLaterStarted}") } } else if (settings.manualControlMode?.contains("1") && (state?.overThreshold == false) && !state?.automaticallyTurnedOn) { ifTrace("overFixedThresholdOff: state?.automaticallyTurnedOn = ${state?.automaticallyTurnedOn} !state.turnOffLaterStarted = ${!state.turnOffLaterStarted}") if (humidityDropTimeout == 0) { unschedule(turnOnFan) turnOffFan() state.turnOffLaterStarted = false ifDebug("overFixedThresholdOff: Turning off the fan. Humidity has returned to normal and it was turned on manually.") } else if (!state.turnOffLaterStarted){ ifTrace ("overFixedThresholdOff: Turn Fan off in ${(humidityDropTimeout)} minutes.") unschedule(turnOnFan) i = (humidityDropTimeout.toInteger() * 60) runIn(i, turnOffFan) state.turnOffLaterStarted = true ifDebug("Turning off the fan in ${humidityDropTimeout} minutes.") ifTrace("overFixedThresholdOff: state.turnOffLaterStarted = ${state?.turnOffLaterStarted}") } } ifTrace("overFixedThresholdOff: Complete") } def compareRateOfChangeOn() { ifTrace("compareRateOfChangeOn") if (state.compareHumidity == null) {getComparisonValue()} if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("compareRateOfChangeOn: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else { ifTrace("settings.humidityResponseMethod?.contains(3) = ${settings.humidityResponseMethod?.contains("3")} compareHumiditySensor = ${compareHumiditySensor} state?.compareHumidityValue = ${state?.compareHumidityValue} compareHumiditySensorOffset = ${compareHumiditySensorOffset} ") ifTrace("settings.humidityResponseMethod?.contains(3) = ${settings.humidityResponseMethod?.contains("3")} state.humidityChangeRate = ${state.humidityChangeRate} settings.humidityIncreaseRate = ${settings.humidityIncreaseRate} state?.currentHumidity.toFloat() = ${state?.currentHumidity.toFloat()} state?.compareHumidity.toFloat() = ${state?.currentHumidity.toFloat()} ") if (settings.humidityResponseMethod?.contains("3") && (fanSwitch?.currentValue("switch") == "off") && (state.humidityChangeRate > settings.humidityIncreaseRate) && (state?.currentHumidity.toFloat() > state?.compareHumidity.toFloat())) { if ((fanOnDelay > 0) && (fanOnDelay != null)) { ifDebug("compareRateOfChangeOn: Turning on fan later") unschedule(turnOffFan) i = (fanOnDelay * 60) runIn(i, turnOnFan) state.automaticallyTurnedOn = true state.turnOnLaterStarted = true } else { ifDebug("compareRateOfChangeOn: Turning on fan due to humidity increase and humidity over comparison sensor humidity") state.automaticallyTurnedOn = true unschedule(turnOffFan) turnOnFan() ifTrace("compareRateOfChangeOn: Turning on fan") state.turnOnLaterStarted = false } state.startingHumidity = state.lastHumidity state.highestHumidity = state.currentHumidity ifTrace("compareRateOfChangeOn: new state?.humidityChangeRate = ${state?.humidityChangeRate}") ifTrace("compareRateOfChangeOn: new settings.humidityIncreaseRate = ${settings.humidityIncreaseRate}") ifTrace("compareRateOfChangeOn: new state?.currentHumidity = ${state?.currentHumidity}") ifTrace("compareRateOfChangeOn: new state?.compareHumidity = ${state?.compareHumidity}") } else if ((settings.humidityResponseMethod?.contains("3") == true) && (fanSwitch?.currentValue("switch") == "on") && (state.turnOffLaterStarted == true) && (state?.humidityChangeRate.toFloat() > settings.humidityIncreaseRate) && (state?.currentHumidity.toFloat() > state?.compareHumidity.toFloat())) { ifDebug("compareRateOfChangeOn: Leaving the fan on due to humidity increase and humidity over comparison sensor humidity") unschedule(turnOffFan) state.turnOnLaterStarted = false state.turnOffLaterStarted = false } } ifTrace("compareRateOfChangeOn: Complete") } def compareRateOfChangeOff() { ifTrace("compareRateOfChangeOff") if (state?.compareHumidity == null) {getComparisonValue()} if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("compareRateOfChangeOff: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else { if (settings.humidityResponseMethod?.contains("3") && state?.automaticallyTurnedOn && (state?.currentHumidity <= state?.compareHumidity)) { if (humidityDropTimeout == 0) { unschedule(turnOnFan) turnOffFan() state.turnOffLaterStarted = false ifDebug("compareRateOfChangeOff: Turning off the fan. Humidity has returned to normal and it was kicked on by the humidity sensor.") } else if (!state.turnOffLaterStarted){ ifDebug ("compareRateOfChangeOff: Turn Fan off in ${(humidityDropTimeout)} minutes.") unschedule(turnOnFan) i = (humidityDropTimeout * 60) runIn(i, turnOffFan) state.turnOffLaterStarted = true ifDebug("Turning off the fan in ${humidityDropTimeout} minutes.") ifTrace("compareRateOfChangeOff: state.turnOffLaterStarted = ${state.turnOffLaterStarted}") } } else if (settings.manualControlMode?.contains("1") && !state?.automaticallyTurnedOn && (state?.currentHumidity <= state?.compareHumidity)) { ifTrace("compareRateOfChangeOff: state?.currentHumidity = ${state?.currentHumidity} state?.targetHumidity = ${state?.targetHumidity}") if (humidityDropTimeout == 0) { unschedule(turnOnFan) turnOffFan() state.turnOffLaterStarted = false ifDebug("compareRateOfChangeOff: Turning off the fan. Humidity has returned to normal and it was turned on manually.") } else if (!state.turnOffLaterStarted){ ifDebug ("compareRateOfChangeOff: Turn Fan off in ${(humidityDropTimeout)} minutes.") unschedule(turnOnFan) i = (humidityDropTimeout * 60) runIn(i, turnOffFan) state.turnOffLaterStarted = true ifDebug("Turning off the fan in ${humidityDropTimeout} minutes.") ifTrace("compareRateOfChangeOff: state.turnOffLaterStarted = ${state.turnOffLaterStarted}") } } } ifTrace("compareRateOfChangeOff: Complete") } def overComparisonOn() { ifTrace("overComparisonOn") if (state.compareHumidity == null) {getComparisonValue()} if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("overComparisonOn: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else { if (settings.humidityResponseMethod?.contains("4") && state?.currentHumidity && state?.compareHumidity && (fanSwitch?.currentValue("switch") == "off") && state.automaticallyTurnedOn && (state?.currentHumidity.toFloat() > state?.compareHumidity.toFloat())) { ifTrace("The humidity is higher than the comparison sensor and the fan is off, kick on the fan") ifTrace("state.currentHumidity = ${state?.currentHumidity} state?.compareHumidity = ${state?.compareHumidity}") if ((fanOnDelay > 0) && (fanOnDelay != null)) { ifDebug("overComparisonOn: Turning on fan later") unschedule(turnOffFan) i = (fanOnDelay * 60) runIn(i, turnOnFan) state.turnOnLaterStarted = true } else { ifInfo("overComparisonOn: Turning on fan due to humidity increase") state.automaticallyTurnedOn = true unschedule(turnOffFan) turnOnFan() ifTrace("overComparisonOn: Turning on fan") state.turnOnLaterStarted = false } state.startingHumidity = state.lastHumidity state.highestHumidity = state.currentHumidity ifTrace("overFixedThresholdOn: new state.startingHumidity = ${state?.startingHumidity}") ifTrace("overFixedThresholdOn: new state.highestHumidity = ${state?.highestHumidity}") ifTrace("overFixedThresholdOn: new state.targetHumidity = ${state?.targetHumidity}") } else if (settings.humidityResponseMethod?.contains("4") && state?.currentHumidity && state?.compareHumidity && (fanSwitch?.currentValue("switch") == "on") && state.automaticallyTurnedOn && (state.turnOffLaterStarted == true) && (state?.currentHumidity.toFloat() > state?.compareHumidity.toFloat())) { ifTrace("The humidity is higher than the comparison sensor and the fan is on but, scheduled to turn off. Leaving the fan on") unschedule(turnOffFan) state.turnOnLaterStarted = false state.turnOffLaterStarted = false } else if (settings.humidityResponseMethod?.contains("4") && state?.currentHumidity && state?.compareHumidity && (fanSwitch?.currentValue("switch") == "off") && !state.automaticallyTurnedOn && (state?.currentHumidity.toFloat() > state?.compareHumidity.toFloat())) { ifTrace("The humidity is higher than the comparison sensor and the fan is off, kick on the fan") ifTrace("state.currentHumidity = ${state?.currentHumidity} state?.compareHumidity = ${state?.compareHumidity}") if ((fanOnDelay > 0) && (fanOnDelay != null)) { ifDebug("overComparisonOn: Turning on fan later") unschedule(turnOffFan) i = (fanOnDelay * 60) runIn(i, turnOnFan) state.turnOnLaterStarted = true } else { ifInfo("overComparisonOn: Turning on fan due to humidity increase") state.automaticallyTurnedOn = true turnOnFan() unschedule(turnOffFan) state.turnOnLaterStarted = false state.startingHumidity = state.lastHumidity state.highestHumidity = state.currentHumidity ifTrace("overFixedThresholdOn: new state.startingHumidity = ${state?.startingHumidity}") ifTrace("overFixedThresholdOn: new state.highestHumidity = ${state?.highestHumidity}") ifTrace("overFixedThresholdOn: new state.targetHumidity = ${state?.targetHumidity}") } } } ifTrace("overComparisonOn: Complete") } def overComparisonOff() { ifTrace("overComparisonOff") if (state?.compareHumidity == null) {getComparisonValue()} if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("overComparisonOff: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else { if (settings.humidityResponseMethod?.contains("4") && state?.automaticallyTurnedOn && (state?.currentHumidity.toFloat() <= state?.compareHumidity.toFloat())) { if (humidityDropTimeout == 0) { turnOffFan() unschedule(turnOnFan) state.turnOffLaterStarted = false ifDebug("overComparisonOff: Turning off the fan. Humidity has returned to normal and it was kicked on by the humidity sensor.") } else if (!state.turnOffLaterStarted){ ifDebug ("overComparisonOff: Turn Fan off in ${(humidityDropTimeout)} minutes.") i = (humidityDropTimeout * 60) runIn(i, turnOffFan) unschedule(turnOnFan) state.turnOffLaterStarted = true ifDebug("Turning off the fan in ${humidityDropTimeout} minutes.") } } else if (settings.manualControlMode?.contains("1") && !state?.automaticallyTurnedOn && (state?.currentHumidity.toFloat() <= state?.compareHumidity.toFloat())) { if (humidityDropTimeout == 0) { turnOffFan() unschedule(turnOnFan) state.turnOffLaterStarted = false ifDebug("overComparisonOff: Turning off the fan. Humidity has returned to normal and it was kicked on by the humidity sensor.") } else if (!state.turnOffLaterStarted){ ifDebug ("overComparisonOff: Turn Fan off in ${(humidityDropTimeout)} minutes.") i = (humidityDropTimeout * 60) runIn(i, turnOffFan) state.turnOffLaterStarted = true unschedule(turnOnFan) ifDebug("Turning off the fan in ${humidityDropTimeout} minutes.") } } } ifTrace("overComparisonOff: Complete") } def getComparisonValue() { ifTrace("getComparisonValue") if ((compareHumiditySensor.currentValue("humidity") != null) && (compareHumiditySensor.currentValue("humidity").contains("%"))) {state.currentHumidityValue = Double.parseDouble(compareHumiditySensor.currentValue("humidity").replace("%", ""))} else {state.compareHumidityValue = Double.parseDouble(compareHumiditySensor.currentValue("humidity"))} if ((settings.humidityResponseMethod?.contains("3") || settings.humidityResponseMethod?.contains("4")) && compareHumiditySensor && state?.compareHumidityValue && compareHumiditySensorOffset) { if ((state?.compareHumidityValue != null) && (compareHumiditySensorOffset != null)) { state.compareHumidity = (compareHumiditySensorOffset + state.compareHumidityValue.toFloat()) } else if (state?.compareHumidityValue != null){ state.compareHumidity = (state.compareHumidityValue.toFloat()) } } ifTrace("getComparisonValue: Complete") } def turnOffFanHumidity() { ifTrace("turnOffFanHumidity") if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("turnOffFanHumidity: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if ((state?.currentHumidity > state.targetHumidity) && (fanSwitch?.currentValue("switch") == "on")) { ifInfo("turnOffFanHumidity: Didn't turn off fan because the humidity is higher than the target humidity ${state?.targetHumidity}") if (humidityDropTimeout == 0) { turnOffFan() unschedule(turnOnFan) state.turnOffLaterStarted = false ifDebug("turnOffFanHumidity: Turning off the fan. Humidity has returned to normal and it was kicked on by the humidity sensor.") } else { ifDebug("turnOffFanHumidity: Turn Fan off in ${(humidityDropTimeout)} minutes.") i = (humidityDropTimeout * 60) runIn(i, turnOffFan) unschedule(turnOnFan) state.turnOffLaterStarted = true ifDebug("Turning off the fan in ${humidityDropTimeout} minutes.") } } updateLabel() ifTrace("turnOffFanHumidity: Complete") } def turnOffFan() { ifTrace("turnOffFan") if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("turnOffFan: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if (fanSwitch?.currentValue("switch") == "on"){ ifInfo("Turning off the fan.") fanSwitch.off() unschedule(turnOffFan) unschedule(turnOnFan) state.status = "(Off)" if (fanSwitch?.currentValue("switch") == "off") { state.automaticallyTurnedOn = false state.turnOnLaterStarted = false state.turnOffLaterStarted = false } else { runIn(10, turnOffFan) } updateLabel() } ifTrace("turnOffFan: Complete") } def turnOffFanMaxTimeout() { ifTrace("turnOffFanMaxTimeout") if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("turnOffFanMaxTimeout: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else if (fanSwitch?.currentValue("switch") == "on") { ifInfo("Turning off the fan.") unschedule(turnOffFan) unschedule(turnOnFan) fanSwitch.off() runIn(10, turnOffFanMaxTimeout) state.status = "(Off)" } else if (fanSwitch?.currentValue("switch") == "off") { state.automaticallyTurnedOn = false state.turnOnLaterStarted = false state.turnOffLaterStarted = false } updateLabel() ifTrace("turnOffFanMaxTimeout: Complete") } def turnOnFan() { ifTrace("turnOnFan") if ((getAllOk() == false) || (state?.paused == true) || (state?.disabled == true) || (state?.pausedOrDisabled == true)) { ifTrace("turnOnFan: getAllOk() = ${getAllOk()} state.paused = ${state.paused} state.disabled = ${state.disabled} state.pausedOrDisabled = ${state.pausedOrDisabled}") } else { ifInfo("Turning on the fan.") state.automaticallyTurnedOn = true unschedule(turnOffFan) state.turnOffLaterStarted = false state.turnOnLaterStarted = false fanSwitch.on() state.status = "(On)" if (maxRunTime != null) { ifDebug("Maximum run time is ${maxRunTime} minutes") i = (maxRunTime.toInteger() * 60) runIn(i, turnOffFanMaxTimeout) unschedule(turnOnFan) } updateLabel() } ifTrace("turnOnFan: Complete") } def changeMode(mode) { ifTrace("changeMode") ifDebug("Changing Mode to: ${mode}") if (location?.mode != mode && location.modes?.find { it.name == mode}) setLocationMode(mode) updateLabel() ifTrace("changeMode: Complete") } def humidityHandlerVariablesBefore() { ifTrace("humidityHandlerVariablesBefore: Before") ifTrace("humidityHandlerVariablesBefore: state.overThreshold = ${state?.overThreshold}") ifTrace("humidityHandlerVariablesBefore: state.automaticallyTurnedOn = ${state?.automaticallyTurnedOn}") ifTrace("humidityHandlerVariablesBefore: state.turnOffLaterStarted = ${state?.turnOffLaterStarted}") ifTrace("humidityHandlerVariablesBefore: state.lastHumidity = ${state?.lastHumidity}") ifTrace("humidityHandlerVariablesBefore: state.lastHumidityDate = ${state?.lastHumidityDate}") ifTrace("humidityHandlerVariablesBefore: state.currentHumidity = ${state?.currentHumidity}") ifTrace("humidityHandlerVariablesBefore: state.currentHumidityDate = ${state?.currentHumidityDate}") ifTrace("humidityHandlerVariablesBefore: state.startingHumidity = ${state?.startingHumidity}") ifTrace("humidityHandlerVariablesBefore: state.highestHumidity = ${state?.highestHumidity}") ifTrace("humidityHandlerVariablesBefore: state.humidityChangeRate = ${state?.humidityChangeRate.toFloat().round(2)}") ifTrace("humidityHandlerVariablesBefore: state.targetHumidity = ${state?.targetHumidity}") if (settings.humidityResponseMethod?.contains("3") || settings.humidityResponseMethod?.contains("4")) {ifTrace("humidityHandlerVariablesBefore: state.compareHumidity = ${state.compareHumidity}")} if (settings.humidityResponseMethod?.contains("3")) {ifTrace("humidityHandlerVariablesBefore: state.compareHumidityValue = ${state.compareHumidityValue}")} if (settings.humidityResponseMethod?.contains("4")) {ifTrace("humidityHandlerVariablesBefore: compareHumiditySensorOffset = ${compareHumiditySensorOffset}")} ifTrace("humidityHandlerVariablesBefore: settings.humidityResponseMethod?.contains(1) = ${settings.humidityResponseMethod?.contains("1")}") ifTrace("humidityHandlerVariablesBefore: settings.humidityResponseMethod?.contains(2) = ${settings.humidityResponseMethod?.contains("2")}") ifTrace("humidityHandlerVariablesBefore: settings.humidityResponseMethod?.contains(3) = ${settings.humidityResponseMethod?.contains("3")}") ifTrace("humidityHandlerVariablesBefore: settings.humidityResponseMethod?.contains(4) = ${settings.humidityResponseMethod?.contains("4")}") ifTrace("humidityHandlerVariablesBefore: Complete") } def configureHumidityVariables() { // If bogus humidity reset to current humidity if (state?.currentHumidity == null) {state.currentHumidity = humiditySensor.currentValue("humidity")} if (state?.highestHumidity == null) {state.highestHumidity = state.currentHumidity } else if ((state?.highestHumidity > 99) || (state?.highestHumidity < 1)) {state.highestHumidity = state.currentHumidity.toFloat()} if (state?.targetHumidity == null) {state?.targetHumidity = state.currentHumidity.toFloat() } else if (state?.targetHumidity.toFloat() > 99) {state.targetHumidity = state.currentHumidity.toFloat()} if (state?.startingHumidity == null) {state?.startingHumidity = state.currentHumidity.toFloat() } else if (state?.startingHumidity.toFloat() > 99) {state.startingHumidity = state.currentHumidity.toFloat()} if ((state?.currentHumidity.toFloat() != null) && (state?.lastHumidity != null)) {state.humidityChangeRate = (state.currentHumidity.toFloat() - state.lastHumidity.toFloat())} else {state.humidityChangeRate = 0} if (state?.currentHumidity) {state.lastHumidity = state.currentHumidity} if (!state?.startingHumidity) {state.startingHumidity = state.currentHumidity.toFloat()} if (!state?.highestHumidity) {state.highestHumidity = state.currentHumidity.toFloat()} if (state?.currentHumidity.toFloat() > state?.highestHumidity.toFloat()) {state.highestHumidity = state.currentHumidity.toFloat()} state.targetHumidity = (state.startingHumidity.toFloat() + ((humidityDropLimit / 100) * (state.highestHumidity.toFloat() - state.startingHumidity.toFloat()))) ifTrace("configureHumidityVariables: Complete") } def humidityHandlerVariablesAfter() { ifTrace("humidityHandlerVariablesAfter: After") ifTrace("humidityHandlerVariablesAfter: state.overThreshold = ${state?.overThreshold}") ifTrace("humidityHandlerVariablesAfter: state.automaticallyTurnedOn = ${state?.automaticallyTurnedOn}") ifTrace("humidityHandlerVariablesAfter: state.turnOffLaterStarted = ${state.turnOffLaterStarted}") ifTrace("humidityHandlerVariablesAfter: state.lastHumidity = ${state?.lastHumidity}") ifTrace("humidityHandlerVariablesAfter: state.lastHumidityDate = ${state?.lastHumidityDate}") ifTrace("humidityHandlerVariablesAfter: state.currentHumidity = ${state?.currentHumidity}") ifTrace("humidityHandlerVariablesAfter: state.currentHumidityDate = ${state?.currentHumidityDate}") ifTrace("humidityHandlerVariablesAfter: state.startingHumidity = ${state?.startingHumidity}") ifTrace("humidityHandlerVariablesAfter: state.highestHumidity = ${state?.highestHumidity}") ifTrace("humidityHandlerVariablesAfter: state.humidityChangeRate = ${state?.humidityChangeRate.round(2)}") ifTrace("humidityHandlerVariablesAfter: state.targetHumidity = ${state?.targetHumidity}") if ((settings.humidityResponseMethod?.contains("3")) || (settings.humidityResponseMethod?.contains("4"))) {ifTrace("humidityHandlerVariablesAfter: state.compareHumidity = ${state.compareHumidity}")} if (settings.humidityResponseMethod?.contains("3")) {ifTrace("humidityHandlerVariablesAfter: state.compareHumidityValue = ${state.compareHumidityValue}")} if (settings.humidityResponseMethod?.contains("4")) {ifTrace("humidityHandlerVariablesAfter: compareHumiditySensorOffset = ${compareHumiditySensorOffset}")} ifTrace("humidityHandlerVariablesAfter: settings.humidityResponseMethod?.contains(1) = ${settings.humidityResponseMethod?.contains("1")}") ifTrace("humidityHandlerVariablesAfter: settings.humidityResponseMethod?.contains(2) = ${settings.humidityResponseMethod?.contains("2")}") ifTrace("humidityHandlerVariablesAfter: settings.humidityResponseMethod?.contains(3) = ${settings.humidityResponseMethod?.contains("3")}") ifTrace("humidityHandlerVariablesAfter: settings.humidityResponseMethod?.contains(4) = ${settings.humidityResponseMethod?.contains("4")}") ifTrace("humidityHandlerVariablesAfter: Complete") } //Label Updates void updateLabel() { ifTrace("updateLabel") // getVariableInfo() if ((state?.paused == true) || (state?.disabled == true)) {state.pausedOrDisabled = true} else {state.pausedOrDisabled = false} if (getAllOk() == false) { state.status = "(Disabled by Time, Day, or Mode)" appStatus = "(Disabled by Time, Day, or Mode)" } else if (state?.disabled == true) { state.status = "(Disabled)" appStatus = "(Disabled)" } else if (state?.paused == true) { state.status = "(Paused)" appStatus = "(Paused)" } else if (fanSwitch?.currentValue("switch") == "on") { state.status = "(On)" appStatus = "(On)" } else if (fanSwitch?.currentValue("switch") == "off") { state.status = "(Off)" appStatus = "(Off)" } else { initialize() state.pausedOrDisabled = false state.status = " " appStatus = " " } if ((state?.paused == true) || (state?.disabled == true)) {state.pausedOrDisabled = true} else {state.pausedOrDisabled = false} app.updateLabel("${state.thisName} ${appStatus}") } //Smart Switch, and Enable, Resume, Pause button def appButtonHandler(btn) { ifTrace("appButtonHandler ${(btn)}") if ((btn == "CreateSmartSwitch") && (state.createSmartSwitchButtonName = "Create Smart Switch") && !getChildDevice("SmartSwitch_${app.id}")) { ifTrace("Creating Smart Switch") addChildDevice("hubitat", "Virtual Switch", "SmartSwitch_${app.id}", null, [label: thisName, name: thisName]) setCreateSmartSwitchButtonName() } else if ((btn == "CreateSmartSwitch") && (state.createSmartSwitchButtonName = "Delete Smart Switch") && getChildDevice("SmartSwitch_${app.id}")) { ifTrace("Deleting Smart Switch") (deleteChildDevice("SmartSwitch_${app.id}")) setCreateSmartSwitchButtonName() } else if (btn == "On") { fanSwitch.on() ifDebug("On command sent") runIn(5, updateLabel) } else if (btn == "Off") { fanSwitch.off() ifDebug("Off command sent") runIn(5, updateLabel) } else if (btn == "Disabled by Switch") { state.disabled = false unschedule() unsubscribe() } else if (btn == "Resume") { state.disabled = false state.paused = !state.paused } else if (btn == "Pause") { state.paused = !state.paused if (state?.paused) { unschedule() unsubscribe() subscribe(disabledSwitch, "switch", disabledHandler) subscribe(fanSwitch, "switch", fanSwitchHandler) subscribe(humiditySensor, "humidity", humidityHandler) subscribe(humiditySensor, "battery", humidityBatteryHandler) subscribe(compareHumiditySensor, "humidity", compareHumidityHandler) subscribe(compareHumiditySensor, "battery", compareHumidityBatteryHandler) subscribe(smartSwitch, "switch", smartSwitchHandler) } else { initialize() state.pausedOrDisabled = false if (fanSwitch?.currentValue("switch") == "on") { ifTrace("appButtonHandler: App was enabled or unpaused and fan was on. Turning off the fan.") turnOffFan() } } } updateLabel() } def setCreateSmartSwitchButtonName() { if (getChildDevice("SmartSwitch_${app.id}")) { state.createSmartSwitchButtonName = "Delete Smart Switch" } else if (!getChildDevice("SmartSwitch_${app.id}")) { state.createSmartSwitchButtonName = "Create Smart Switch" } } def setPauseButtonName() { if (state?.disabled == true) { state.pauseButtonName = "Disabled by Switch" unsubscribe() unschedule() updateLabel() } else if (state?.paused == true) { state.pauseButtonName = "Resume" unsubscribe() unschedule() updateLabel() } else { state.pauseButtonName = "Pause" updated() subscribe(disabledSwitch, "switch", disabledHandler) subscribe(fanSwitch, "switch", fanSwitchHandler) subscribe(humiditySensor, "humidity", humidityHandler) subscribe(compareHumiditySensor, "humidity", compareHumidityHandler) subscribe(smartSwitch, "switch", smartSwitchHandler) updateLabel() } } def unscheduleFanSwitchCommands() { unschedule(turnOnFan) unschedule(turnOffFan) } // Application Page settings private hideComparisonSensorSection() {(compareHumiditySensor || compareHumiditySensorOffset) ? false : true} private hideNotificationSection() {(notifyOnLowBattery || lowBatteryNotificationDevices || lowBatteryDevicesToNotifyFor || lowBatteryAlertThreshold || notifyOnFailure || failureNotificationDevices || failureNotifications) ? false : true} private hideAdditionalFeaturesSection() {(deviceActivation || Create || smartSwitch) ? false : true} private hideOptionsSection() {(timeIntervalInput || starting || ending || days || modes || disabledSwitch) ? false : true} private hideLoggingSection() {(isInfo || isDebug || isTrace || ifLevel) ? false : true} def getAllOk() {if ((modeOk && daysOk && timeOk) == true) {return true} else {return false}} private getModeOk() { def result = (!modes || modes.contains(location.mode)) result } private getDaysOk() { def result = true if (days) { def df = new java.text.SimpleDateFormat("EEEE") if (location.timeZone) {df.setTimeZone(location.timeZone)} else {df.setTimeZone(TimeZone.getTimeZone("America/New_York"))} def day = df.format(new Date()) result = days.contains(day) } result } private getTimeOk() { def result = true if ((starting != null) && (ending != null)) { def currTime = now() def start = timeToday(starting).time def stop = timeToday(ending).time result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start } result } private hhmm(time, fmt = "h:mm a") { def t = timeToday(time, location.timeZone) def f = new java.text.SimpleDateFormat(fmt) f.setTimeZone(location.timeZone ?: timeZone(time)) f.format(t) } private timeIntervalLabel() { (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : "" } // Logging functions def infoOff() { app.updateSetting("isInfo",[value:"false",type:"bool"]) if (isInfo == false) {log.warn "${state.thisName}: Info logging disabled."} } def debugOff() { app.updateSetting("isDebug",[value:"false",type:"bool"]) if (isDebug == false) {log.warn "${state.thisName}: Debug logging disabled."} } def traceOff() { app.updateSetting("isTrace",[value:"false",type:"bool"]) if (isTrace == false) {log.warn "${state.thisName}: Trace logging disabled."} } def ifWarn(msg) {log.warn "${state.thisName}: ${msg}"} def ifInfo(msg) { if (!settings.ifLevel?.contains("1") && (isInfo != true)) {return}//bail else if (settings.ifLevel?.contains("1") || (isInfo == true)) {log.info "${state.thisName}: ${msg}"} } def ifDebug(msg) { if (!settings.ifLevel?.contains("2") && (isDebug != true)) {return}//bail else if (settings.ifLevel?.contains("2") || (isDebug == true)) {log.debug "${state.thisName}: ${msg}"} } def ifTrace(msg) { if (!settings.ifLevel?.contains("3") && (isTrace != true)) {return}//bail else if (settings.ifLevel?.contains("3") || (isTrace == true)) {log.trace "${state.thisName}: ${msg}"} }