/**
* Hubitat Import URL: https://raw.githubusercontent.com/HubitatCommunity/Auto_Off/main/Auto_Off_c.groovy
*/
/**
* Auto_Off Child
*
* Copyright 2020 C Steele, Mattias Fornander
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
public static String version() { return "v1.0.6" }
import groovy.time.*
// Set app Metadata for the Hub
definition(
name: "Auto_Off device",
namespace: "csteele",
author: "Mattias Fornander, CSteele",
description: "Automatically turn off/on devices after set amount of time on/off",
category: "Automation",
importUrl: "https://raw.githubusercontent.com/HubitatCommunity/Auto_Off/main/Auto_Off_c.groovy",
parent: "csteele:Auto_Off",
iconUrl: "",
iconX2Url: "",
iconX3Url: ""
)
preferences {
page (name: "mainPage")
}
/**
* Called after app is initially installed.
*/
def installed() {
initialize()
app.clearSetting("debugOutput") // app.updateSetting() only updates, won't create.
app.clearSetting("descTextEnable")
}
/**
* Called after any of the configuration settings are changed.
*/
def updated() {
if (descTextEnable) log.info "Updated with settings: ${settings}"
state.delay = Math.floor(autoTime * 60).toInteger()
String curPref = ("$autoTime${devices}${master}$invert").toString().bytes.encodeBase64()
if (curPref != state.prevPref) {
state.prevPref = curPref
unsubscribe()
unschedule()
if (descTextEnable) log.warn "This Auto_Off Child was reset."
}
initialize()
}
/**
* Internal helper function with shared code for installed() and updated().
*/
private initialize() {
if (state.offList == null) state.offList = [:]
subscribe(devices, "switch", switchHandler)
updateMyLabel("1")
}
/**
* Main configuration function declares the UI shown.
*/
def mainPage() {
updateMyLabel("2")
dynamicPage(name: "mainPage", install: true, uninstall: true) {
section("
${app.label ?: app.name}
"){
paragraph 'Automatically turn off/on devices after set amount of time on/off.'
input name: "autoTime", type: "number", range: "1..1440", title: "Time until auto-off (minutes) 24hrs max", required: true
input name: "devices", type: "capability.switch", title: "Devices", required: true, multiple: true
input name: "invert", type: "bool", title: "Invert logic (make app Auto On)", defaultValue: false
input name: "master", type: "capability.switch", title: "Master Switch", multiple: false
}
section (title: "Name/Rename") {
label title: "This child app's Name (optional)", required: false, submitOnChange: true
if (!app.label) {
app.updateLabel(app.name)
atomicState.appDisplayName = app.name
}
if (app.label.contains(' actionList.any { it.key == device.id } }
if (debugOutput) log.debug "scheduleHandler now:${oNow} offList:${state.offList} actionList:${actionList} deviceList:${deviceList}"
// Call off(), or on() if inverted, on all relevant devices and remove them from offList
if (!master || master.latestValue("switch") == "on") {
invert ? deviceList*.on() : deviceList*.off()
} else {
if (debugOutput) log.debug "Skipping actions because MasterSwitch '${master?.displayName}' is Off"
}
state.offList -= actionList
updateMyLabel("4")
}
def setDebug(dbg, inf) {
app.updateSetting("debugOutput",[value:dbg, type:"bool"])
app.updateSetting("descTextEnable",[value:inf, type:"bool"])
if (descTextEnable) log.info "Set by Parent: debugOutput: $debugOutput, descTextEnable: $descTextEnable"
}
def display()
{
section {
paragraph "\n
"
paragraph "Developed by: C Steele
Version Status: $state.Status
Current Version: ${version()} - ${thisCopyright}
"
}
}
def updateMyLabel(c) {
String flag = ' Active until " + fixDateTimeString(atomicState.cycleEnd) + ""
} else {
myLabel = myLabel + " Idle"
atomicState.cycleEnd = 0
}
} else {
myLabel = myLabel + " [-]"
atomicState.cycleEnd = 0
}
if (app.label != myLabel) app.updateLabel(myLabel) // log.debug "label: $myLabel"
}
String fixDateTimeString( eventDate) {
def today = new Date(now()).clearTime()
def target = new Date(eventDate).clearTime()
String resultStr = ''
String myDate = ''
String myTime = ''
boolean showTime = true
if (target == today) {
myDate = 'today'
} else if (target == today-1) {
myDate = 'yesterday'
} else if (target == today+1) {
myDate = 'tomorrow'
} else if (dateStr == '2035-01-01' ) { // to Infinity
myDate = 'a long time from now'
showTime = false
} else {
myDate = 'on '+target.format('MM-dd')
}
if (showTime) {
myTime = new Date(eventDate).format('h:mma').toLowerCase()
}
if (myDate || myTime) {
resultStr = myTime ? "${myDate} at ${myTime}" : "${myDate}"
}
if (debugOutput) log.debug "banner: ${resultStr}"
return resultStr
}
// Parent does the version2 JSON fetch and distributes it to each Child.
def updateCheck(respUD)
{
state.InternalName = "Auto_Off_c"
state.Status = "Unknown"
state.Copyright = "${thisCopyright} -- ${version()}"
if (respUD.application.(state.InternalName) == null) {
if (descTextEnable) log.info "This Application is not version tracked yet."
return
}
def newVer = padVer(respUD.application.(state.InternalName).ver)
def currentVer = padVer(version())
state.UpdateInfo = (respUD.application.(state.InternalName).updated)
// log.debug "updateCheck: ${respUD.application.(state.InternalName).ver}, $state.UpdateInfo, ${respUD.author}"
switch(newVer) {
case { it == "NLS"}:
state.Status = "** This Application is no longer supported by ${respUD.author} **"
log.warn "** This Application is no longer supported by ${respUD.author} **"
break
case { it > currentVer}:
state.Status = "New Version Available (Version: ${respUD.application.(state.InternalName).ver})"
log.warn "** There is a newer version of this Application available (Version: ${respUD.application.(state.InternalName).ver}) **"
log.warn "** $state.UpdateInfo **"
break
case { it < currentVer}:
state.Status = "You are using a Test version of this Application (Expecting: ${respUD.application.(state.InternalName).ver})"
break
default:
state.Status = "Current"
if (descTextEnable) log.info "You are using the current version of this Application"
break
}
}
/*
padVer
Version progression of 1.4.9 to 1.4.10 would mis-compare unless each column is padded into two-digits first.
*/
def padVer(ver) {
def pad = ""
ver.replaceAll( "[vV]", "" ).split( /\./ ).each { pad += it.padLeft( 2, '0' ) }
return pad
}
def getThisCopyright(){"© 2020 C Steele "}