/** * **************** Fountain Controller App **************** * * Usage: * This was designed to keep a fountain water reseviour filled, using a smart valve and a leak sensor to detect fill level. * It also turns the fountain on and off based on a motion sensor in the fountain area. * * * v. 1.0 - 4/18/25 - Inital code * v. 1.3 - 5/02/25 - Added log to google device option to log valve, sensor, and pump states on changes * v. 1.5 - 5/10/25 - Added fill check while running, and overfill timer to go a bit above level when filling. * v. 1.7 - 5/13/25 - Added preference for overfill seconds and recheck minutes **/ definition ( name: "Fountain Controller App", namespace: "Hubitat", author: "Burgess", description: "", category: "My Apps", iconUrl: "", iconX2Url: "" ) preferences { page name: "mainPage", title: "", install: true, uninstall: true } def mainPage() { dynamicPage(name: "mainPage") { section("App Name") { label title: "", required: false } section("Fountain Pump Switch") { input ( name: "fountainSwitch", type: "capability.switch", title: "Select Fountain Pump Switch Device", required: true, multiple: false, submitOnChange: true ) } section("Water Filler Valve Switch") { input ( name: "fillValve", type: "capability.switch", title: "Select Water Valve Switch Device", required: true, multiple: false, submitOnChange: true ) } section("Filled Sensor") { input ( name: "fillSensor", type: "capability.contactSensor", title: "Select Filled Leak Sensor Device", required: true, multiple: false, submitOnChange: true ) } section("Back Door Motion Sensor") { input ( name: "motionSensor", type: "capability.motionSensor", title: "Select Back Door Motion Sensor Device", required: true, multiple: false, submitOnChange: true ) } section("Log To Google Device") { input ( name: "googleLogs", type: "capability.actuator", title: "Select Log to Google Device", required: false, multiple: false, submitOnChange: true ) } if (googleLogs) { section("") { input ( name: "googleLogging", type: "bool", title: "Enable Google logging", required: false, defaultValue: false ) } } section("") { input ( name: "autoOff", type: "enum", title: "Auto Off Minutes", options: ["600":10,"900":15,"1200":20,"1800":30,"2700":45,"3600":60,"5400":90,"7200":120], multiple: false, defaultValue: 30, required: true ) } section("") { input ( name: "autoStop", type: "enum", title: "Auto Fill Stop Seconds", options: ["60":60, "90":90,"120":120,"180":180,"240":240,"300":300,"360":360,"420":420,"480":480,"540":540,"600":600], multiple: false, defaultValue: 60, required: true ) } section("") { input ( name: "overFill", type: "enum", title: "Overfill Seconds (seconds to fill after sensor shows filled)", options: ["5":5, "10":10,"20":20,"30":30,"40":40,"50":50,"60":60], multiple: false, defaultValue: 30, required: true ) } section("") { input ( name: "fillCheck", type: "enum", title: "Minutes interval to re-check level when running", options: ["600":10,"900":15,"1200":20,"1800":30,"2700":45,"3600":60,"5400":90,"7200":120], multiple: false, defaultValue: 30, required: true ) } section("") { input ( name: "autoFill", type: "bool", title: "Enable Auto Fill", required: true, defaultValue: false ) } section("") { input ( name: "onMotion", type: "bool", title: "Enable Fountain On with Motion", required: true, defaultValue: false ) } section("") { input ( name: "debugMode", type: "bool", title: "Enable logging", required: true, defaultValue: false ) } section("") { input ( name: "logLevel", type: "enum", title: "Logging Level", options: [1:"Info", 2:"Warning", 3:"Debug"], multiple: false, defaultValue: 2, required: true ) } } } def installed() { initialize() } def updated() { if (settings?.debugMode) { if (settings?.logLevel == "3") { runIn(3600, logDebugOff) // one hour logDebug("Debug log level enabled",3) logDebug("Log Level will change from Debug to Info after 1 hour",2) } else if (settings?.logLevel == "1") { logDebug("Info logging Enabled",1) } else logDebug("Warning log level enabled",2) } unsubscribe() unschedule() initialize() } def initialize() { if (autoFill) {subscribe(fillSensor, "contact", waterSensorController)} if (onMotion) {subscribe(motionSensor, "motion", motionController)} subscribe(fillValve, "switch", valveController) subscribe(fountainSwitch, "switch", fountainPower) if (googleLogging) subscribe(fillValve, "waterConsumed", logWater) //if (autoFill) {schedule('0 0/50 * * * ?', checkFilled)} // check every hour at :50 } def logToGoogle() { if (googleLogging) { def valveState = 0 def sensorState = 0 def pumpState = 0 def water = fillValve.currentValue("waterConsumed") if (fillValve.currentValue("switch") == "on") {valveState = 2} if (fillSensor.currentValue("contact") == "closed") {sensorState = 1.5} // closed is dry if (fountainSwitch.currentValue("switch") == "on") {pumpState = 1} logParams = "Valve="+valveState+"&Sensor="+sensorState+"&Pump="+pumpState+"&water="+water // log to google logDebug("Fountain Log params are: ${logParams}") googleLogs.sendLog("Fountain", logParams) } } def logWater(evt) { logToGoogle() } // log valve def valveController(evt) { logToGoogle() } // check filled 30 seconds after fountain turns on def fountainPower(evt) { if (evt.value == "on") { runIn(30, checkFilled) } runIn(1,logToGoogle) } // trigger a stop fill when sensor open (filled) def waterSensorController(evt) { logDebug("waterSensorController called with ${evt.value}") def sensor = evt.value if (settings?.autoFill) { if (sensor == "open") { def overSecs = overFill.toInteger() runIn(overSecs, stopFill) // fill a bit over the sensor for 30 sec } else if (sensor == "closed") { logToGoogle() } } } // start auto fill def startFill() { logDebug("startFill Called") fillValve.on() runIn(60, stopFill) def secs = (settings?.autoStop).toInteger() logDebug("Stopping fill in ${secs} seconds") runIn(secs, stopFill) runIn(1,logToGoogle) } // stop auto fill def stopFill() { logDebug("stopFill Called") fillValve.off() runIn(1,logToGoogle) } // call start fountain with motion def motionController(evt) { logDebug("Motion event ${evt.value}") if (settings?.onMotion) { def sensor = evt.value if (sensor == "active") { startFountain() } } } // start fountain def startFountain() { logDebug("Starting Fountain for ${settings?.autoOff} minutes") if (fountainSwitch.currentValue("switch") == "off") { fountainSwitch.on() runIn(30, checkFilled) runIn(1,logToGoogle) } def timerMinutes = (settings?.autoOff).toInteger() runIn(timerMinutes, stopFountain) } // stop fountain on timeout def stopFountain() { logDebug("Turning off Fountain") fountainSwitch.off() unschedule() runIn(1,logToGoogle) runIn(2,confirmOff) } // send extra off to confirm def confirmOff() { if (fountainSwitch.currentValue("switch") == "on") { fountainSwitch.off() } } // check fill state to start Auto Fill def checkFilled() { logDebug("Check Filled called") def powerOn = fountainSwitch.currentValue("switch") == "on" if (settings?.autoFill && powerOn) { def sensor = fillSensor.currentValue("contact") logDebug("Sensor is ${sensor}") if (sensor == "closed") { startFill() } else if (sensor == "open") { stopFill() } } else {logDebug("autoFill is off")} def recheckMins = fillCheck.toInteger() * 60 if (powerOn) { runIn(recheckMins, checkFilled) // check every x minutes for fill as set } else {unschedule()} } // log debug if no logLevel added def logDebug(txt) { try { if (settings?.debugMode) { log.debug("${app.label} - ${txt}") // debug } } catch(ex) { log.error("bad debug message") } } // log by level when lvl supplied def logDebug(txt, lvl){ try { logLevel = settings?.logLevel.toInteger() if (settings?.debugMode) { if (lvl == 3 && logLevel == 3) log.debug("${app.label} - ${txt}") // debug else if (lvl >= 2 && logLevel >= 2) log.warn("${app.label} - ${txt}") // warn else if (lvl >= 1 && logLevel >= 1) log.info("${app.label} - ${txt}") // info } } catch(ex) { log.error("bad debug message") } } def logDebugOff() { logDebug("Turning off debugMode") app.updateSetting("logLevel",[value:"1",type:"enum"]) }