/**
* **************** Fan Hood Controller App ****************
*
* Usage:
* This was designed to run a converted stove hood fan using relays to control the fan with three speeds
* Each speed switch device is part of a 4-channel relay board, set to jog mode so that any other switches turn off when one is turned on
* The last relay is used as off, since the board will turn off any other speed relays when it is turned on in jog mode.
* Requirements:
* A four channel smart relay baord with jog mode
* Three of the relays have been physically attached to supply voltage to the appropriate motor wires for low, medium and high.
* The board I am using needs 5v usb, or 7-48v). An external power supply may be needed if the required voltage is not avaialbe on the hood's board.
*
* Version 1.0 - 11/14/24
* Version 1.1 - 11/15/24 - Added optional stove light device which if added in prefrences, will turn on when the fan turns on
**/
definition (
name: "Fan Hood Controller App",
namespace: "Hubitat",
author: "ChrisB",
description: "Controller for a Converted Stove Fan Hood using Relays",
category: "My Apps",
iconUrl: "",
iconX2Url: ""
)
preferences {
page name: "mainPage", title: "", install: true, uninstall: true
}
def mainPage() {
lock
dynamicPage(name: "mainPage") {
section("Hood Fan Speed Low Device") {
input (
name: "fanLow",
type: "capability.switch",
title: "Select Fan Speed Low Switch Device",
required: false,
multiple: false,
submitOnChange: true
)
}
section("Hood Fan Speed Medium Device") {
input (
name: "fanMedium",
type: "capability.switch",
title: "Select Fan Speed Medium Switch Device",
required: false,
multiple: false,
submitOnChange: true
)
}
section("Hood Fan Speed High Device") {
input (
name: "fanHigh",
type: "capability.switch",
title: "Select Fan Speed High Switch Device",
required: false,
multiple: false,
submitOnChange: true
)
}
section("Hood Fan Speed Off Device") {
input (
name: "fanOff",
type: "capability.switch",
title: "Select Fan Speed Off Switch Device",
required: false,
multiple: false,
submitOnChange: true
)
}
section("Fan Hood Driver Device") {
input (
name: "hoodDriver",
type: "capability.actuator",
title: "Select Fan Hood Driver Fan Device",
required: false,
multiple: false,
submitOnChange: true
)
}
section("Stove Hood Light") {
input (
name: "stoveLight",
type: "capability.switch",
title: "Select Stove Hood Light Switch Device (optional)",
required: false,
multiple: false,
submitOnChange: true
)
}
section("Fan Doubletap Switch") {
input (
name: "fanSwitch",
type: "capability.switch",
title: "Select Fan Doubletap Switch Device (optional)",
required: false,
multiple: false,
submitOnChange: true
)
}
section("Fan Control with 4-button Scene Switch") {
input (
name: "fanSceneSwitch",
type: "capability.actuator",
title: "Select Fan Scene Switch Device (optional)",
required: false,
multiple: false,
submitOnChange: true
)
}
section("") {
input (
name: "useAutoOff",
type: "bool",
title: "Enable Auto-Off",
required: true,
defaultValue: false
)
}
section("") {
input (
name: "autoOff",
type: "enum",
title: "Auto Off Minutes",
options: ["600":10, "1200":20,"1800":30,"2700":45,"3600":60,"5400":90,"7200":120],
multiple: false,
defaultValue: 60,
required: false
)
}
if (stoveLight) {
section("") {
input (
name: "useStoveLight",
type: "bool",
title: "Turn On Stove Light with Fan On",
required: false,
defaultValue: true
)
}
}
section("") {
input (
name: "debugMode",
type: "bool",
title: "Enable logging",
required: true,
defaultValue: false
)
}
}
}
def installed() {
updated()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
if (fanSwitch) subscribe(fanSwitch, "doubleTapped", fanSwitchHandler) // control with existing switch doubletap
if (fanSceneSwitch) subscribe(fanSceneSwitch, "pushed", fanSceneSwitchHandler) // control with a scene switch
if (hoodDriver) {
subscribe(hoodDriver, "speed", hoodSpeedHandler) // control from the driver events
subscribe(hoodDriver, "switch", hoodSwitchHandler) // control from the driver events
}
subscribe(fanOff, "switch", fanPowerHandler) // to catch an "on" event to start timer when fanOff is turned off
}
// driver switch change event
def hoodSwitchHandler(evt) {
if (evt.value == "on") {
def speed = hoodDriver.currentValue("speed")
setSpeed(speed)
}
if (evt.value == "off") {
turnFanOff()
}
}
// driver speed change event
def hoodSpeedHandler(evt) {
setSpeed(evt.value)
}
// set fan to the driver speed event value
def setSpeed(speed) {
def action = ""
if (speed == "low" && currentFanSpeed() != "low") {fanLow.on(); action = "low"}
if (speed == "medium" && currentFanSpeed() != "medium") {fanMedium.on(); action = "medium"}
if (speed == "high" && currentFanSpeed() != "high") {fanHigh.on(); action = "high"}
if (speed == "off" && currentFanSpeed() != "off") {turnFanOff(); action = "off"}
updateDriver(action)
}
// get current speed
String currentFanSpeed() {
if (fanLow.currentValue("switch") == "on") {return "low"}
if (fanMedium.currentValue("switch") == "on") {return "medium"}
if (fanHigh.currentValue("switch") == "on") {return "high"}
else {return "off"}
}
// doubletap switch handler for fan speeds
def fanSwitchHandler(evt) {
logDebug("fanSwitchHandler got ${evt.value}")
def doubletapped = (evt.value).toInteger()
def button = ""
if (doubletapped == 1) {button = "up"}
else {button = "down"}
def action = ""
fanOn = fanIsOn() // off relay on means fan is off
// Turn on fan to either high or low with switch buttons when it is off
if (button == "down" && !fanOn) {
logDebug("fanSwitchHandler got Down and fan Off")
fanLow.on()
action = "low"
}
if (button == "up" && !fanOn) {
logDebug("fanSwitchHandler got Up and fan Off")
fanHigh.on()
action = "high"
}
def fanSpeed = currentFanSpeed()
// Change speed when fan is alreay on based on current speed
if (button == "down" && fanOn) { // DOWN doubletap
logDebug("fanSwitchHandler got down and fan On")
if (fanSpeed == "low") {turnFanOff(); action = "off"} // turn off from low
if (fanSpeed == "medium") {fanLow.on(); action = "low"} // turn low from medium
if (fanSpeed == "high") {fanMedium.on(); action = "medium"} // turn medium from high
}
if (button == "up" && fanOn) { // UP doubletap
logDebug("fanSwitchHandler got up and fan On")
if (fanSpeed == "high") {turnFanOff(); action = "off"} // turn off from high
if (fanSpeed == "medium") {fanHigh.on(); action = "high"} // turn High from med
if (fanSpeed == "low") {fanMedium.on(); action = "medium"} // turn medium from low
}
updateDriver(action)
}
// Scene Switch Handler for fan speeds
def fanSceneSwitchHandler(evt) {
def pressed = evt.value.toInteger()
def action = ""
if (pressed == 1) {turnFanOff(); action = "off"}
if (pressed == 2) {fanLow.on(); action = "low"}
if (pressed == 3) {fanMedium.on(); action = "medium"}
if (pressed == 4) {fanHigh.on(); action = "high"}
updateDriver(action)
}
// activate fan power off switch (4th relay clears all speed relays on the board when on)
def turnFanOff() {
fanOff.on()
updateDriver("off")
}
// update the driver device attributes with speed being set
def updateDriver(speed) {
logDebug("updateDriver called with ${speed}")
if (speed != "") {
def driverSpeed = hoodDriver.currentValue("speed")
if (driverSpeed != speed) {hoodDriver.setSpeed(speed)}
}
}
// start timer when fan off relay triggers to off (meaning the fan was turned on)
def fanPowerHandler(evt) {
logDebug("fanPowerHandler called with ${evt.value}")
if (evt.value == "off") {
def timerMinutes = (settings?.autoOff).toInteger()
logDebug("timerMinutes is ${timerMinutes}")
if (settings?.useAutoOff) runIn(timerMinutes,turnFanOff)
if (stoveLight && useStoveLight) {stoveLight.on()}
}
if (evt.value == "on") {unschedule()}
}
// get bool to check if fan is on true/false
boolean fanIsOn() {
return fanOff.currentValue("switch") == "off"
}
def logDebug(txt){
try {
if (settings.debugMode) { log.debug("${app.label} - ${txt}") }
} catch(ex) {
log.error("bad debug message")
}
}