/**
* Pool / Spa / Drainage Pump Scheduler (c) 2026 All rights reserved
*
* For swiming pool pumps that dont have a timer,Check your pools specs
* On how long it should run per day and its max run time.
*
* For drainage pumps. If the area cant be drained in several hrs the pump
* will over heat and trip the reset so a cool down time is needed.
* Set max run time and max time per day.
*
* Air pumps for septic systems. Once that expensive controler fails
* you can just add a relay and use your hub to control it.
* Check max air time per day and max ontime and max off time.
*
*
* v2.0.6 GUI rebuild. Start/Stop doesnt stop scheduler.Bug fix on daily time minor amounts
* v2.0.5 7/29/26 Fix reboot droping scvhedule
* v2.0.3 Reduced monitor scheduling to 30m.
* v2.0.1 Adjusting monitor.
* v2.0.0 Manual start stop added
* v1.9 7/25/2026 Log fixes and status page improvements
* v1.8 7/24/2026 Bugs fixed/ Manual buttons added.
* v1.6 7/23/2026 Insert into Hubitat Package Manager
* v1.5 7/22/2026 Much debuging changes in monitor and more debug code
* v1.2 7/19/2026 Int version
*
*/
definition(
name: "Pump Scheduler",
namespace: "tmastersmart",
author: "Tmaster",
importUrl: "https://raw.githubusercontent.com/tmastersmart/hubitat-code/main/pump_scheduler.groovy",
description: "Smart scheduler for pool pumps, spa pumps, septic aerators, drainage pumps, and other motorized equipment. Supports daily runtime targets, maximum cycle length, cooldown periods, sunrise/sunset scheduling, monitoring, and automatic recovery after hub reboots or app updates.",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
)
preferences {
page(name: "mainPage")
page(name: "setupPage")
page(name: "defaultsPage")
page(name: "advancedPage")
}
def version() { "2.0.6" }
def clientVersion() {
if (state.version != version()) {
logging("Pump - ${pump.displayName} Scheduler Updated to v${version()}","warn")
state.version = version()
}
}
def mainPage() {
dynamicPage(name: "mainPage", title: "Pump Scheduler",install: true, uninstall: true) {
section("Diagnostics - v${state.version}") {
def runtimeHours = (state.totalRuntimeToday ?: 0) / 3600
// Include current cycle runtime if running
if (state.isRunning && state.currentCycleStart) {
runtimeHours += (now() - state.currentCycleStart) / 3600000
}
def targetHours = dailyHours ?: 0
def remainingHours = Math.max(0, targetHours - runtimeHours)
def runtimeStr = String.format("%.2f", runtimeHours)
def targetStr = String.format("%.2f", targetHours)
def remainingStr = String.format("%.2f", remainingHours)
// Progress Bar
def percent = targetHours ? Math.min(100, (runtimeHours / targetHours) * 100) : 0
def percentInt = percent.toInteger()
int filled = Math.round(percentInt / 10.0)
int empty = 10 - filled
def progressBar = ("█" * filled) + ("░" * empty)
def mode = "⏳ Waiting"
if (state.manualMode) { mode = "🔧 Manual Mode"}
else if (state.schedulerPaused) { mode = "⛔ Scheduler Paused"}
else if (state.isRunning) { mode = "🟢 Running"}
else if (state.nextStartTime && remainingHours > 0) { mode = "⛔ Cooldown"}
else if (remainingHours <= 60) { mode = "✅ Daily Complete"}
def status = "
"
def td = "style='border:1px solid #666;padding:6px;'"
status += ""
status += "| Pump: ${pump?.displayName ?: "Not Selected"} | "
status += "Mode: ${mode} | "
status += "Today's Progress "
status += "${progressBar} ${percentInt}% "
status += "(${runtimeStr} of ${targetStr} hrs) | "
status += "Remaining: ${remainingStr} hrs | "
status += "Cycles: ${state.cyclesToday ?: 0}"
if (maxCyclesPerDay) {
status += " / ${maxCyclesPerDay} | "
}
status += "
"
status += ""
if (state.currentCycleStart) {status += "| Started: " + new Date(state.currentCycleStart).format("h:mm:ss a", location.timeZone) + " | "}
else{ status += " | " }
if (state.stopTime) { status += "Stops: " + new Date(state.stopTime).format("h:mm:ss a", location.timeZone) + " | " }
else{ status += " | " }
if (state.nextStartTime) { status += "Next Start: " + new Date(state.nextStartTime).format("h:mm:ss a", location.timeZone) + " | " }
else{ status += " | " }
if (state.lastRunDate){ status += "Last Run: ${state.lastRunDate} | " }
else{ status += " | " }
if (startOption == "Specific Time" && startTime) {
status += "Daily Start: " + timeToday(startTime, location.timeZone).format("h:mm a", location.timeZone)+" | "}
else if (startOption == "Sunrise") {
status += "Daily Start: Sunrise | "}
else if (startOption == "Sunset") {
status += "Daily Start: Sunset | "}
status += "
"
paragraph status
}
section("Maintenance") {
paragraph "Recovery tools for the scheduler. Start and Stop doesnt stop the schedule"
input "forceMonitor", "button", title: "Run Monitor"
input "restartMonitor", "button",title: "Restart Monitor"
input "restartDay", "button", title: "Restart Today's Schedule"
input "manualStart", "button", title: "▶ Start Pump"
input "manualStop", "button", title: "■ Stop Pump"
input "stopScheduler", "button", title: "⏹ Stop Scheduler"
input "resumeScheduler", "button", title: "↻ Resume Scheduler"
input "refreshStatus", "button", title: "Refresh Status"
}
section("Menu") {
href "setupPage", title: "Configuration", description: "Pump settings"
href "defaultsPage",title: "Equipment Defaults",description: "Load preset values"
}
footer()
}
}
def appButtonHandler(btn) {
switch(btn) {
case "restartDay":
logging("Pump - ${pump.displayName} Manual daily restart requested. Clear all", "warn")
unschedule("monitorPump")
state.lastRunDate = null
state.totalRuntimeToday = 0
state.cyclesToday = 0
state.stopTime = null
state.nextStartTime = null
state.currentCycleStart = null
state.isRunning = false
state.schedulerPaused = false
state.manualMode = false
initialize()
startDailySchedule()
return mainPage()
case "restartMonitor":
logging("Pump - ${pump.displayName} Manual monitor restart requested", "warn")
restoreMonitor()
return mainPage()
case "forceMonitor":
logging("Pump - ${pump.displayName} Force monitor requested", "warn")
monitorPump()
return mainPage()
case "manualStart":
startPumpMan()
return mainPage()
// Just stop dont stop the schedule
case "manualStop":
stopPumpMan()
return mainPage()
case "stopScheduler":
logging("Pump - ${pump.displayName} Scheduler stopped", "warn")
unschedule("monitorPump")
unschedule("startPumpCycle")
unschedule("stopPumpCycle")
unschedule("startDailySchedule")
state.schedulerPaused = true
state.manualMode = true
state.stopTime = null
state.nextStartTime = null
state.currentCycleStart = null
// Make sure the pump is off
if (pump.currentSwitch == "on") {
pump.off()
logging("Pump - ${pump.displayName} Pump turned OFF by stop scheduler ", "info")
}
return mainPage()
case "resumeScheduler":
logging("Pump - ${pump.displayName} Scheduler resumed", "warn")
state.schedulerPaused = false
state.manualMode = false
state.nextStartTime = now() + 2
// scheduleStartTime()
restoreMonitor()
runIn(1, "monitorPump")
return mainPage()
case "refreshStatus":
return mainPage()
}
}
def defaultsPage() {
dynamicPage(name: "defaultsPage", title: "Equipment Defaults", nextPage: "mainPage", uninstall: false, install: false) {
section("Choose Equipment") {
input "equipmentPreset","enum",title: "Preset",submitOnChange: true,options:["Pool Pump","Spa Pump","Septic Aerator","Drainage Pump","Other"]
}
if(equipmentPreset){
section("Load Defaults"){
paragraph "Press Done to save these defaults."
if(equipmentPreset=="Pool Pump"){
app.updateSetting("dailyHours",[value:"8",type:"decimal"])
app.updateSetting("maxRunPerCycle",[value:"4",type:"decimal"])
app.updateSetting("cooldownMinutes",[value:"30",type:"number"])
}
if(equipmentPreset=="Spa Pump"){
app.updateSetting("dailyHours",[value:"2",type:"decimal"])
app.updateSetting("maxRunPerCycle",[value:"2",type:"decimal"])
app.updateSetting("cooldownMinutes",[value:"15",type:"number"])
}
if(equipmentPreset=="Septic Aerator"){
app.updateSetting("dailyHours",[value:"12",type:"decimal"])
app.updateSetting("maxRunPerCycle",[value:"4",type:"decimal"])
app.updateSetting("cooldownMinutes",[value:"60",type:"number"])
}
if(equipmentPreset=="Drainage Pump"){
app.updateSetting("dailyHours",[value:"18",type:"decimal"])
app.updateSetting("maxRunPerCycle",[value:"4",type:"decimal"])
app.updateSetting("cooldownMinutes",[value:"30",type:"number"])
}
}
}
}
}
def setupPage() {
dynamicPage(name: "setupPage", title: "Pump Scheduler Configuration", nextPage: "mainPage") {
section("Pump") {
input "pump","capability.switch",title: "Select Pump Device",description: "Choose the switch that controls your pump.",required: true
input "deviceType","enum",title: "Pump Type",options: ["Pool Pump","Spa Pump","Drainage Pump","Septic Aerator","Other"],description: "Used for presets and status messages.",required: true
}
section("Daily Schedule") {
input "dailyHours","decimal",title: "Total Daily Runtime (hours)",description: "Total amount of time the pump should run each day.",defaultValue: 8.0,required: true
input "maxRunPerCycle","decimal",title: "Maximum Continuous Runtime (hours)",description: "Longest the pump may run before taking a cooldown break.",defaultValue: 4.0,required: true
input "startOption","enum",title: "Daily Start Time",options: ["Specific Time","Sunrise","Sunset"],description: "Choose when the first run of the day begins.",defaultValue: "Specific Time",required: true
input "startTime","time",title: "Start Time",description: "Only used when 'Specific Time' is selected.",required: false
}
section("Cooldown") {
input "cooldownMinutes","number",title: "Cooldown Between Cycles (minutes)",description: "Time the pump remains OFF before the next run begins.",defaultValue: 30,required: true
}
section("Limits") {
input "maxCyclesPerDay","number",title: "Maximum Cycles Per Day",description: "Number of run cycles allowed each day. Set to 0 for unlimited cycles.",defaultValue: 0,required: true
}
// log.info "Logging Info:[${infoLogging}] Debug:[${debugLogging}] Trace:[${traceLogging}]"
section("Logging") {
paragraph "Debug and Trace logging automatically turn off after a period of time unless 'Lock' is enabled."
input "infoLogging","bool",title: "Enable Info Logging",defaultValue: (settings.infoLogging != null ? settings.infoLogging : true)
input "debugLogging","bool",title: "Enable Debug Logging",defaultValue: (settings.debugLogging != null ? settings.debugLogging : false)
input "traceLogging","bool",title: "Enable Trace Logging",defaultValue: (settings.traceLogging != null ? settings.traceLogging : false)
input "lockLogging","bool",title: "Lock",defaultValue: (settings.lockLogging != null ? settings.lockLogging : false)
}
footer()
}
}
def installed() {
clientVersion()
logging("Pump - ${pump.displayName} Scheduler Installed","info")
initialize()
loggingUpdate()
}
def updated() {
unsubscribe()
unschedule()
initialize()
restoreMonitor()
logging("Pump - ${pump.displayName} Scheduler Updated - ${dailyHours} hrs/day | Max ${maxRunPerCycle} hrs per cycle","trace")
loggingUpdate()
}
def restoreMonitor() {
def runtime = state.totalRuntimeToday ?: 0
def target = dailyHours * 3600
def remaining = target - runtime
unschedule("monitorPump")
if (state.isRunning) {
schedule("0 0/25 * * * ?", "monitorPump")
logging("Pump - ${pump.displayName} Monitor restored (Pump Running)", "info")
}
else if (remaining <= 60) {
state.nextStartTime = null
logging("Pump - ${pump.displayName} Daily runtime complete Runtime=${String.format('%.2f', runtime/3600)}h","info")
}
else if (state.nextStartTime) {
schedule("0 0/25 * * * ?", "monitorPump")
logging("Pump - ${pump.displayName} Monitor restored (Cooldown active)","info")
logging("Pump - ${pump.displayName} NextStart=${new Date(state.nextStartTime).format('HH:mm:ss', location.timeZone)} Remaining=${String.format('%.2f', remaining/3600)}h","debug")
}
else {
logging("Pump - ${pump.displayName} Runtime remaining (${String.format('%.2f', remaining/3600)}h) but no NextStart exists","warn")
state.nextStartTime = now() + (cooldownMinutes * 60 * 1000)
schedule("0 0/25 * * * ?", "monitorPump")
logging("Pump - ${pump.displayName} Rebuilt cooldown. NextStart=${new Date(state.nextStartTime)}","info")
}
}
def initialize() {
if (state.totalRuntimeToday == null){ state.totalRuntimeToday = 0.0 }
if (state.cyclesToday == null){ state.cyclesToday = 0 }
if (state.isRunning == null){ state.isRunning = false }
clientVersion()
// fix the schedule after a reboot
scheduleStartTime()
logging("Pump - ${pump.displayName} Scheduler initialized - ${dailyHours} hrs/day | Max ${maxRunPerCycle} hrs per cycle", "info")
restoreMonitor()
runIn(30, "monitorPump")
}
def scheduleStartTime() {
// Remove any previous start schedule
unschedule("startDailySchedule")
switch(startOption) {
case "Sunrise":
subscribe(location, "sunriseTime", startDailySchedule)
logging("Pump - ${pump.displayName} Scheduled to start at Sunrise", "info")
break
case "Sunset":
subscribe(location, "sunsetTime", startDailySchedule)
logging("Pump - ${pump.displayName} Scheduled to start at Sunset", "info")
break
case "Specific Time":
if (startTime) {
schedule(startTime, startDailySchedule)
logging("Pump - ${pump.displayName} Scheduled to start at ${startTime}", "info")
}
break
default:
logging("Pump - ${pump.displayName} No valid start schedule configured", "warn")
break
}
}
def startDailySchedule(evt = null) {
// Always make sure the monitor is running
state.manualMode = false
unschedule("monitorPump")
schedule("0 0/25 * * * ?", "monitorPump")
logging("Pump - ${pump.displayName} Monitor reset", "debug")
// runIn(60, "monitorPump") // lets not do this now
def today = new Date().format("yyyy-MM-dd", location.timeZone)
if (state.lastRunDate == today) {
logging("Pump - ${pump.displayName} Today's schedule already started. Runtime ${state.totalRuntimeToday} Cycles ${state.cyclesToday}","warn")
return
}
state.monitorCount =0
state.lastRunDate = today
state.totalRuntimeToday = 0.0
state.cyclesToday = 0
state.isRunning = false
state.stopTime = null
state.nextStartTime = null
state.currentCycleStart = null
logging("Pump - ${pump.displayName} Daily cycle started","info")
startPumpCycle()
}
def startPumpMan() {
if (state.isRunning) return
pump.on()
state.isRunning = true
def debugRuntime = state.totalRuntimeToday ?: 0
def debugHours = debugRuntime / 3600
logging("Pump - ${pump.displayName} Manual ON. Runtime:${String.format('%.3f', debugHours)} hrs Cycle#${state.cyclesToday} ","warn")
}
def stopPumpMan() {
if (!state.isRunning) return
pump.off()
state.isRunning = false
def debugRuntime = state.totalRuntimeToday ?: 0
def debugHours = debugRuntime / 3600
logging("Pump - ${pump.displayName} Manual STOP. Runtime:${String.format('%.3f', debugHours)} hrs Cycle#${state.cyclesToday} ","warn")
}
def startPumpCycle() {
if (state.isRunning) return
def target = dailyHours * 3600
def remaining = target - (state.totalRuntimeToday ?: 0)
if (maxCyclesPerDay && state.cyclesToday >= maxCyclesPerDay) {
state.totalRuntimeToday = target
state.nextStartTime = null
state.stopTime = null
unschedule("monitorPump")
logging("Pump - ${pump.displayName} Max cycles reached! Runtime:${String.format('%.2f', state.totalRuntimeToday / 3600)}h Cycles#${state.cyclesToday}", "info")
return
}
if (remaining <= 60){
state.totalRuntimeToday = target
state.nextStartTime = null
state.stopTime = null
unschedule("monitorPump")
logging("Pump - ${pump.displayName} Daily runtime complete Runtime:${String.format('%.2f', target / 3600)}h Cycles#${state.cyclesToday}","info")
return
}
def runSeconds = Math.min(remaining, maxRunPerCycle * 3600)
pump.on()
state.isRunning = true
state.currentCycleStart = now()
state.stopTime = now() + (runSeconds.toInteger() * 1000)
state.nextStartTime = null
state.cyclesToday++
state.lastStopTime = null
def debugRuntime = state.totalRuntimeToday ?: 0
def debugHours = debugRuntime / 3600
def next = "Next event NULL"
if (state.nextStartTime) {
next = "Next Start:" + new Date(state.nextStartTime).format("h:mm a", location.timeZone)
}
if (state.stopTime) {
next = "Next Stop:" + new Date(state.stopTime).format("h:mm a", location.timeZone)
}
runIn(runSeconds.toInteger(), stopPumpCycle)
logging("Pump - ${pump.displayName} turned ON. Runtime:${String.format('%.3f', debugHours)} hrs Cycle#${state.cyclesToday} ${next}","info")
}
def stopPumpCycle() {
if (!state.isRunning){
logging("Pump - ${pump.displayName} Off time but was not running.","warn")
return
}
state.isRunning = false
def actualRun = (now() - state.currentCycleStart) / 1000
state.totalRuntimeToday += actualRun
state.stopTime = null
state.currentCycleStart = null
state.lastStopTime = now()
pump.off() // do after to make sure theres no bounce
def debugRuntime = state.totalRuntimeToday ?: 0
def debugHours = debugRuntime / 3600
def next = "Next event NULL"
if (state.nextStartTime) {
next = "Next Start:" + new Date(state.nextStartTime).format("h:mm a", location.timeZone)
}
if (state.stopTime) {
next = "Next Stop:" + new Date(state.stopTime).format("h:mm a", location.timeZone)
}
logging("Pump - ${pump.displayName} turned OFF after ${actualRun/3600} Hrs Runtime:${String.format('%.3f', debugHours)} hrs Cycle#${state.cyclesToday} ${next}","info")
if (state.totalRuntimeToday < dailyHours * 3600) {
logging("Pump - ${pump.displayName} Scheduling cooldown. Runtime:${state.totalRuntimeToday} Target:${dailyHours * 3600}", "debug")
state.nextStartTime = now() + (cooldownMinutes * 60 * 1000)
logging("Pump - ${pump.displayName} Next start will be ${new Date(state.nextStartTime)}", "debug")
runIn(cooldownMinutes * 60, startPumpCycle)
}
else {
unschedule("monitorPump")
logging("Pump - ${pump.displayName} Daily runtime complete", "info")
}
logging("Pump - ${pump.displayName} After Stop: Runtime:${String.format('%.3f', debugHours)} hrs Cycle#${state.cyclesToday} ${next}", "debug")
}
// monitor run by cron when active
def monitorPump() {
def debugRuntime = state.totalRuntimeToday ?: 0
def today = new Date().format("yyyy-MM-dd", location.timeZone)
if (state.lastMonitorRun && (now() - state.lastMonitorRun) < 60) {
logging("Pump - ${pump.displayName} Monitor skipped - ran recently", "debug")
return
}
if (state.schedulerPaused) {
logging("Pump - ${pump.displayName} Scheduler paused", "info")
return
}
if (state.manualMode) {
logging("Pump - ${pump.displayName} Manual mode active", "info")
return
}
if (state.lastRunDate != today) {
// unschedule("monitorPump")
logging("Pump - ${pump.displayName} Error New day not yet initialized ", "debug")
logging("Pump - ${pump.displayName} lastRunDate=${state.lastRunDate} today=${today}", "debug")
return
}
state.lastMonitorRun = now()
if (state.isRunning && state.currentCycleStart) {
debugRuntime += (now() - state.currentCycleStart) / 1000
}
def debugHours = debugRuntime / 3600
def next = "Next event NULL"
if (state.nextStartTime) {
next = "Next Start:" + new Date(state.nextStartTime).format("h:mm a", location.timeZone)
}
if (state.stopTime) {
next = "Next Stop:" + new Date(state.stopTime).format("h:mm a", location.timeZone)
}
state.monitorCount = (state.monitorCount ?: 0) + 1
// monitor now runs ever 30 not 10
// if(!debugLogging){
// state.monitorCount = (state.monitorCount ?: 0) + 1
// if (state.monitorCount >= 3) {
logging("Pump - ${pump.displayName} Monitor: Switch:${pump.currentSwitch} Runtime:${String.format('%.3f', debugHours)} hrs Cycle#${state.cyclesToday} ${next}","info")
// state.monitorCount =0
// }
// }
// else{
// logging("Pump - ${pump.displayName} Monitor: Switch:${pump.currentSwitch} Runtime:${String.format('%.3f', debugHours)} hrs Cycle#${state.cyclesToday} ${next}","debug")
// }
logging("Pump - ${pump.displayName} Monitor: Running:${state.isRunning} ${next}", "trace")
def target = dailyHours * 3600
def remaining = target - (state.totalRuntimeToday ?: 0)
if (state.isRunning) {
// Pump should be ON
if (pump.currentSwitch != "on") {
logging("Pump - ${pump.displayName} was OFF but schedule says ON - turning ON ${next}", "warn")
pump.on()
}
// Lost stop timer?
if (!state.stopTime) {
logging("Pump - ${pump.displayName} StopTime missing - rebuilding from current cycle", "warn")
state.stopTime = state.currentCycleStart + (maxRunPerCycle * 3600 * 1000)
}
// Stop timer expired?
if (now() >= state.stopTime) {
logging("Pump - ${pump.displayName} Fixing Stop timer expired ${next}", "warn")
stopPumpCycle()
}
// Debounce check
if (state.nextStartTime) {
logging("Pump - ${pump.displayName} Detected NextStart while running ${next}", "debug")
// Ignore. This is a debounce issue.
// state.nextStartTime = null
}
}
else {
// Pump should be OFF
if (pump.currentSwitch != "off") {
logging("Pump - ${pump.displayName} was ON but schedule says OFF - turning OFF ${next}", "warn")
pump.off()
}
// More than 60 seconds of runtime remaining?
if (remaining > 60) {
// Lost cooldown timer?
if (!state.nextStartTime) {
logging("Pump - ${pump.displayName} Next Start missing - rebuilding cooldown", "warn")
state.nextStartTime = now() + (cooldownMinutes * 60 * 1000)
}
// Cooldown complete?
if (state.nextStartTime && now() >= state.nextStartTime) {
logging("Pump - ${pump.displayName} Cooldown expired - starting next cycle ${next}", "warn")
startPumpCycle()
}
}
else {
// Close enough to today's target
state.totalRuntimeToday = target
state.nextStartTime = null
state.stopTime = null
logging("Pump - ${pump.displayName} Daily runtime complete Runtime:${String.format('%.2f', target / 3600)}h Cycles#${state.cyclesToday}","info")
unschedule("monitorPump")
logging("Pump - ${pump.displayName} Monitor stopped", "info")
}
}
}
def footer() {
section("Support Development") {
paragraph """
Enjoying this app? Your support helps fund new features, bug fixes, and future Hubitat apps.
Donate with PayPal
© 2026 TheMaster Smart Home Code
"""
}
}
// ====================== LOGGING ROUTINE (App Version) ======================
def loggingUpdate() {
log.info "Pump - ${pump.displayName} Logging Info:[${infoLogging}] Debug:[${debugLogging}] Trace:[${traceLogging}] Locked:[${lockLogging}]"
//lock code
if (!lockLogging){
def timeDebug = 60
def timeTrace = 60
def DEBUG_LOG_TIMEOUT = 60 * timeDebug
def TRACE_LOG_TIMEOUT = 60 * timeTrace
if (debugLogging) {
log.warn "Pump - ${pump.displayName} Debug logging will auto-disable in ${timeDebug} minutes"
runIn(DEBUG_LOG_TIMEOUT, debugLogOff)
}
if (traceLogging) {
log.warn "Pump - ${pump.displayName} Trace logging will auto-disable in ${timeTrace} minutes"
runIn(TRACE_LOG_TIMEOUT, traceLogOff)
}
}
}
void debugLogOff() {
app.updateSetting("debugLogging", [value: "false", type: "bool"])
log.debug "Pump - ${pump.displayName} Debug Logging Automatically Disabled"
}
void traceLogOff() {
app.updateSetting("traceLogging", [value: "false", type: "bool"])
log.trace "Pump - ${pump.displayName} Trace Logging Automatically Disabled"
}
private logging(String message, String level = "info") {
switch(level) {
case "error":
log.error message
break
case "warn":
log.warn message
break
case "trace":
if (traceLogging) log.trace message
break
case "debug":
if (debugLogging) log.debug message
break
case "info":
default:
if (infoLogging) log.info message
break
}
}