Created
September 27, 2019 12:42
-
-
Save imnotbob/013c08d197eda567aa456725a9dd0839 to your computer and use it in GitHub Desktop.
bug fix test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************************** | |
| Application Name: NST Automations | | |
| Copyright (C) 2017, 2018, 2019 Anthony S. | | |
| Authors: Anthony S. (@tonesto7), Eric S. (@E_sch) | | |
| Contributors: Ben W. (@desertblade) | | |
| A few code methods are modeled from those in CoRE by Adrian Caramaliu | | |
| | | |
| September 26, 2019 | | |
| License Info: https://github.com/tonesto7/nest-manager/blob/master/app_license.txt | | |
|********************************************************************************************/ | |
import groovy.json.* | |
import java.text.SimpleDateFormat | |
definition( | |
name: "NST Automations", | |
namespace: "tonesto7", | |
author: "Anthony S.", | |
parent: "tonesto7:NST Manager", | |
description: "This App is used to enable built-in automations for NST Manager", | |
category: "Convenience", | |
iconUrl: "", | |
iconX2Url: "", | |
iconX3Url: "", | |
importUrl: "https://raw.githubusercontent.com/tonesto7/nst-manager-he/master/apps/nstAutomations.groovy") | |
String appVersion() { "2.0.6" } | |
preferences { | |
page(name: "startPage") | |
//Automation Pages | |
page(name: "notAllowedPage") | |
page(name: "selectAutoPage") | |
page(name: "mainAutoPage") | |
page(name: "mainAutoPage1") | |
page(name: "mainAutoPage2") | |
page(name: "remSenShowTempsPage") | |
page(name: "nestModePresPage") | |
page(name: "schMotModePage") | |
page(name: "watchDogPage") | |
//shared pages | |
page(name: "schMotSchedulePage") | |
page(name: "schMotSchedulePage1") | |
page(name: "schMotSchedulePage2") | |
page(name: "schMotSchedulePage3") | |
page(name: "schMotSchedulePage4") | |
page(name: "schMotSchedulePage5") | |
page(name: "schMotSchedulePage6") | |
page(name: "schMotSchedulePage7") | |
page(name: "schMotSchedulePage8") | |
page(name: "scheduleConfigPage") | |
page(name: "tstatConfigAutoPage") | |
page(name: "tstatConfigAutoPage1") | |
page(name: "tstatConfigAutoPage2") | |
page(name: "tstatConfigAutoPage3") | |
page(name: "tstatConfigAutoPage4") | |
page(name: "tstatConfigAutoPage5") | |
page(name: "tstatConfigAutoPage6") | |
page(name: "tstatConfigAutoPage7") | |
page(name: "setNotificationPage") | |
page(name: "setNotificationPage1") | |
page(name: "setNotificationPage2") | |
page(name: "setNotificationPage3") | |
page(name: "setNotificationPage4") | |
page(name: "setNotificationPage5") | |
page(name: "setDayModeTimePage") | |
page(name: "setDayModeTimePage1") | |
page(name: "setDayModeTimePage2") | |
page(name: "setDayModeTimePage3") | |
page(name: "setDayModeTimePage4") | |
page(name: "setDayModeTimePage5") | |
//page(name: "setNotificationTimePage") | |
} | |
/****************************************************************************** | |
| Application Pages | | |
*******************************************************************************/ | |
def startPage() { | |
//log.info "startPage" | |
if(parent) { | |
Boolean t0 = parent.getStateVal("ok2InstallAutoFlag") | |
if( /* !state?.isInstalled && */ t0 != true) { | |
//Logger("Not installed ${t0}") | |
notAllowedPage() | |
} else { | |
state?.isParent = false | |
selectAutoPage() | |
} | |
} else { | |
notAllowedPage() | |
} | |
} | |
def notAllowedPage () { | |
dynamicPage(name: "notAllowedPage", title: "This install Method is Not Allowed", install: false, uninstall: true) { | |
section() { | |
paragraph imgTitle(getAppImg("disable_icon2.png"), paraTitleStr("WE HAVE A PROBLEM!\n\nNST Automations can't be directly installed.\n\nPlease use the Nest Integrations App to configure them.")), required: true, state: null | |
} | |
} | |
} | |
private boolean isHubitat(){ | |
return hubUID != null | |
} | |
void installed() { | |
log.debug "${app.getLabel()} Installed with settings: ${settings}" // MUST BE log.debug | |
if(isHubitat() && !app.id) return | |
initialize() | |
return | |
} | |
void updated() { | |
log.debug "${app.getLabel()} Updated...with settings: ${settings}" | |
state?.isInstalled = true | |
String appLbl = getCurAppLbl() | |
if(appLbl?.contains("Watchdog")) { | |
if(!state?.autoTyp) { state.autoTyp = "watchDog" } | |
} | |
initialize() | |
state?.lastUpdatedDt = getDtNow() | |
return | |
} | |
void uninstalled() { | |
log.debug "uninstalled" | |
uninstAutomationApp() | |
} | |
void initialize() { | |
log.debug "${app.label} Initialize..." // Must be log.debug | |
if(!state?.isInstalled) { state?.isInstalled = true } | |
Boolean settingsReset = parent.getSettingVal("resetAllData") | |
//if(state?.resetAllData || settingsReset) { | |
// if(fixState()) { return } // runIn of fixState will call initAutoApp() | |
//} | |
runIn(6, "initAutoApp", [overwrite: true]) | |
} | |
def subscriber() { | |
} | |
private adj_temp(tempF) { | |
if(getTemperatureScale() == "C") { | |
return ((tempF - 32) * ( 5/9 )) as Double | |
} else { | |
return tempF | |
} | |
} | |
void setMyLockId(val) { | |
if(state?.myID == null && parent && val) { | |
state.myID = val | |
} | |
} | |
String getMyLockId() { | |
if(parent) { return state?.myID } else { return null } | |
} | |
/* | |
def fixState() { | |
def result = false | |
LogTrace("fixState") | |
def before = getStateSizePerc() | |
if(!state?.resetAllData && parent.getSettingVal("resetAllData")) { // automation cleanup called from update() -> initAutoApp() | |
def data = getState()?.findAll { !(it?.key in [ "autoTyp", "autoDisabled", "scheduleList", "resetAllData", "autoDisabledDt", | |
"leakWatRestoreMode", "leakWatTstatOffRequested", | |
"conWatRestoreMode", "conWatlastMode", "conWatTstatOffRequested", | |
"oldremSenTstat", | |
"haveRunFan", "fanCtrlRunDt", "fanCtrlFanOffDt", | |
"extTmpRestoreMode", "extTmpTstatOffRequested", "extTmpSavedTemp", "extTmplastMode", "extTmpSavedCTemp", "extTmpSavedHTemp", "extTmpChgWhileOnDt", "extTmpChgWhileOffDt", | |
// "remDiagLogDataStore", | |
// "restoreId", "restoredFromBackup", "restoreCompleted", "autoTypFlag", "installData", "usageMetricsStore" | |
]) } | |
// "watchDogAlarmActive", "extTmpAlarmActive", "conWatAlarmActive", "leakWatAlarmActive", | |
data.each { item -> | |
state.remove(item?.key.toString()) | |
} | |
setAutomationStatus() | |
unschedule() | |
unsubscribe() | |
result = true | |
} else if(state?.resetAllData && !parent.getSettingVal("resetAllData")) { | |
LogAction("fixState: resetting ALL toggle", "info", true) | |
state.resetAllData = false | |
} | |
if(result) { | |
state.resetAllData = true | |
LogAction("fixState: State Data: before: $before after: ${getStateSizePerc()}", "info", true) | |
runIn(20, "finishFixState", [overwrite: true]) | |
} | |
return result | |
} | |
void finishFixState(migrate=false) { | |
LogTrace("finishFixState") | |
if(state?.resetAllData || migrate) { | |
def tstat = settings?.schMotTstat | |
if(tstat) { | |
LogAction("finishFixState found tstat", "info", true) | |
getTstatCapabilities(tstat, schMotPrefix()) | |
if(!getMyLockId()) { | |
setMyLockId(app.id) | |
} | |
if(settings?.schMotRemoteSensor) { | |
LogAction("finishFixState found remote sensor", "info", true) | |
if( parent?.remSenLock(tstat?.deviceNetworkId, getMyLockId()) ) { // lock new ID | |
state?.remSenTstat = tstat?.deviceNetworkId | |
} | |
if(isRemSenConfigured() && settings?.remSensorDay) { | |
LogAction("finishFixState found remote sensor configured", "info", true) | |
if(settings?.vthermostat != null) { parent?.addRemoveVthermostat(tstat.deviceNetworkId, vthermostat, getMyLockId()) } | |
} | |
} | |
} | |
if(!migrate) { initAutoApp() } | |
//updated() | |
} | |
} | |
*/ | |
def selectAutoPage() { | |
//LogTrace("selectAutoPage()") | |
if(!state?.autoTyp) { | |
return dynamicPage(name: "selectAutoPage", title: "Choose an Automation Type", uninstall: false, install: true, nextPage: null) { | |
boolean thereIsChoice = !parent.automationNestModeEnabled(null) | |
if(thereIsChoice) { | |
section("Set Nest Presence Based on location Modes, Presence Sensor, or Switches:") { | |
href "mainAutoPage1", title: imgTitle(getAppImg("mode_automation_icon.png"), inputTitleStr("Nest Mode Automations")), description: ""//, params: ["aTyp": "nMode"] | |
} | |
} | |
section("Thermostat Automations: Setpoints, Remote Sensor, External Temp, Humidifier, Contact Sensor, Leak Sensor, Fan Control") { | |
href "mainAutoPage2", title: imgTitle(getAppImg("thermostat_automation_icon.png"), inputTitleStr("Thermostat Automations")), description: "" //, params: ["aTyp": "schMot"] | |
} | |
} | |
} | |
else { return mainAutoPage( [aTyp: state?.autoTyp]) } | |
} | |
String sectionTitleStr(title) { return "<h3>$title</h3>" } | |
String inputTitleStr(title) { return "<u>$title</u>" } | |
String pageTitleStr(title) { return "<h1>$title</h1>" } | |
String paraTitleStr(title) { return "<b>$title</b>" } | |
//def imgTitle(imgSrc, imgWidth=30, imgHeight=null, titleStr, color=null) { | |
String imgTitle(String imgSrc, String titleStr, String color=null, imgWidth=30, imgHeight=null) { | |
String imgStyle = "" | |
imgStyle += imgWidth ? "width: ${imgWidth}px !important;" : "" | |
imgStyle += imgHeight ? "${imgWidth ? " " : ""}height: ${imgHeight}px !important;" : "" | |
if(color) { return """<div style="color: ${color}; font-weight: bold;"><img style="${imgStyle}" src="${imgSrc}"> ${titleStr}</img></div>""" } | |
else { return """<img style="${imgStyle}" src="${imgSrc}"> ${titleStr}</img>""" } | |
} | |
// string table for titles | |
String titles(String name, Object... args) { | |
def page_titles = [ | |
// "page_main": "${lname} setup and management", | |
// "page_add_new_cid_confirm": "Add new CID switch : %s", | |
// "input_selected_devices": "Select device(s) (%s found)", | |
"t_dtse": "Delay to set ECO (in Minutes)", | |
"t_dr": "Delay Restore (in Minutes)", | |
"t_ca": "Configured Alerts", | |
"t_cr": "Configured Restrictions", | |
"t_nt": "Notifications:", | |
"t_nlw": "Nest Location Watchdog" | |
] | |
if (args) | |
return String.format(page_titles[name], args) | |
else | |
return page_titles[name] | |
} | |
// string table for descriptions | |
String descriptions(name, Object... args) { | |
def element_descriptions = [ | |
"d_ttc": "Tap to configure", | |
"d_ttm": "\n\nTap to modify" | |
] | |
if (args) | |
return String.format(element_descriptions[name],args) | |
else | |
return element_descriptions[name] | |
} | |
String icons(String name, napp="App") { | |
def icon_names = [ | |
"i_dt": "delay_time", | |
"i_not": "notification", | |
"i_calf": "cal_filter", | |
"i_set": "settings", | |
"i_sw": "switch_on", | |
"i_mod": "mode", | |
"i_hmod": "hvac_mode", | |
"i_inst": "instruct", | |
"i_err": "error", | |
"i_cfg": "configure", | |
"i_t": "temperature" | |
//ERS | |
] | |
//return icon_names[name] | |
String t0 = icon_names?."${name}" | |
//LogAction("t0 ${t0}", "warn", true) | |
if(t0) return "https://raw.githubusercontent.com/${gitPath()}/Images/$napp/${t0}_icon.png" | |
else return "https://raw.githubusercontent.com/${gitPath()}/Images/$napp/${name}" | |
} | |
String getAppImg(String imgName, on = null) { | |
//return (!disAppIcons || on) ? "https://raw.githubusercontent.com/${gitPath()}/Images/App/$imgName" : "" | |
return (!disAppIcons || on) ? icons(imgName) : "" | |
} | |
String getDevImg(String imgName, on = null) { | |
//return (!disAppIcons || on) ? "https://raw.githubusercontent.com/${gitPath()}/Images/Devices/$imgName" : "" | |
return (!disAppIcons || on) ? icons(imgName, "Devices") : "" | |
} | |
def mainAutoPage1(params) { | |
//LogTrace("mainAutoPage1()") | |
def t0 = [:] | |
t0.aTyp = "nMode" | |
return mainAutoPage( t0 ) //[autoType: "nMode"]) | |
} | |
def mainAutoPage2(params) { | |
//LogTrace("mainAutoPage2()") | |
def t0 = [:] | |
t0.aTyp = "schMot" | |
return mainAutoPage( t0 ) //[autoType: "schMot"]) | |
} | |
def mainAutoPage(params) { | |
//LogTrace("mainAutoPage()") | |
String t0 = getTemperatureScale()?.toString() | |
state.tempUnit = (t0 != null) ? t0 : state?.tempUnit | |
if(!state.autoDisabled) { state.autoDisabled = false } | |
String autoType = (String) null | |
//If params.aTyp is not null then save to state. | |
if(!state.autoTyp) { | |
if(!params?.aTyp) { Logger("nothing is set mainAutoPage") } | |
else { | |
//Logger("setting autoTyp") | |
state.autoTyp = params?.aTyp | |
autoType = params?.aTyp; | |
} | |
} else { | |
//Logger("setting autoTyp") | |
autoType = state.autoTyp | |
} | |
//Logger("mainPage: ${state.autoTyp} ${autoType}") | |
// If the selected automation has not been configured take directly to the config page. Else show main page | |
//Logger("in mainAutoPage ${autoType} ${state?.autoTyp}") | |
if(autoType == "nMode" && !isNestModesConfigured()) { return nestModePresPage() } | |
else if(autoType == "watchDog" && !isWatchdogConfigured()) { return watchDogPage() } | |
else if(autoType == "schMot" && !isSchMotConfigured()) { return schMotModePage() } | |
else { | |
//Logger("in main page") | |
// Main Page Entries | |
//return dynamicPage(name: "mainAutoPage", title: "Automation Configuration", uninstall: false, install: false, nextPage: "nameAutoPage" ) { | |
return dynamicPage(name: "mainAutoPage", title: pageTitleStr("Automation Configuration"), uninstall: true, install: true, nextPage:null ) { | |
section() { | |
if(settings?.autoDisabledreq) { | |
paragraph imgTitle(getAppImg("i_inst"), paraTitleStr("This Automation is currently disabled!\nTurn it back on to to make changes or resume operation")), required: true, state: null | |
} else { | |
if(getIsAutomationDisabled()) { paragraph imgTitle(getAppImg("i_inst"), paraTitleStr("This Automation is still disabled!\nPress Next and Done to Activate this Automation Again")), state: "complete" } | |
} | |
if(!getIsAutomationDisabled()) { | |
if(autoType == "nMode") { | |
//paragraph paraTitleStr("Set Nest Presence Based on location Modes, Presence Sensor, or Switches:") | |
String nDesc = "" | |
nDesc += isNestModesConfigured() ? "Nest Mode:\n • Status: (${strCapitalize(getNestLocPres())})" : "" | |
if(((!nModePresSensor && !nModeSwitch) && (nModeAwayModes && nModeHomeModes))) { | |
nDesc += nModeHomeModes ? "\n • Home Modes: (${nModeHomeModes.size()})" : "" | |
nDesc += nModeAwayModes ? "\n • Away Modes: (${nModeAwayModes.size()})" : "" | |
} | |
nDesc += (nModePresSensor && !nModeSwitch) ? "\n\n${nModePresenceDesc()}" : "" | |
nDesc += (nModeSwitch && !nModePresSensor) ? "\n • Using Switch: (State: ${isSwitchOn(nModeSwitch) ? "ON" : "OFF"})" : "" | |
nDesc += (settings?.nModeDelay && settings?.nModeDelayVal) ? "\n • Change Delay: (${getEnumValue(longTimeSecEnum(), settings?.nModeDelayVal)})" : "" | |
nDesc += (isNestModesConfigured() ) ? "\n • Restrictions Active: (${autoScheduleOk(getAutoType()) ? "NO" : "YES"})" : "" | |
if(isNestModesConfigured()) { | |
nDesc += "\n • Set Thermostats to ECO: (${nModeSetEco ? "On" : "Off"})" | |
if(parent.getSettingVal("cameras")) { | |
nDesc += "\n • Cams On when Away: (${nModeCamOnAway ? "On" : "Off"})" | |
nDesc += "\n • Cams Off when Home: (${nModeCamOffHome ? "On" : "Off"})" | |
if(settings?.nModeCamsSel) { | |
nDesc += "\n • Nest Cams Selected: (${nModeCamsSel.size()})" | |
} | |
} | |
} | |
String t1 = getNotifConfigDesc("nMode") | |
nDesc += t1 ? "\n\n${t1}" : "" | |
nDesc += t1 || (nModePresSensor || nModeSwitch) || (!nModePresSensor && !nModeSwitch && (nModeAwayModes && nModeHomeModes)) ? descriptions("d_ttm") : "" | |
String nModeDesc = isNestModesConfigured() ? "${nDesc}" : null | |
//Logger("nModeDesc ${nModeDesc}") | |
href "nestModePresPage", title: imgTitle(getAppImg("mode_automation_icon.png"), inputTitleStr("Nest Mode Automation Config")), description: nModeDesc ?: descriptions("d_ttc"), state: (nModeDesc ? "complete" : null) | |
} | |
if(autoType == "schMot") { | |
//Logger("calling schMot config and page") | |
//Logger("in mainAutoPage7") | |
String sModeDesc = getSchMotConfigDesc() | |
href "schMotModePage", title: imgTitle(getAppImg("thermostat_automation_icon.png"), inputTitleStr("Thermostat Automation Config")), description: sModeDesc ?: descriptions("d_ttc"), state: (sModeDesc ? "complete" : null) | |
} | |
if(autoType == "watchDog") { | |
//paragraph paraTitleStr("Watch your Nest Location for Events:") | |
String watDesc = "" | |
String t1 = getNotifConfigDesc("watchDog") | |
if(t1) { | |
def tstats = parent.getSettingVal("thermostats") | |
def prots = parent.getSettingVal("protects") | |
def cams = parent.getSettingVal("cameras") | |
if(tstats || prots || cams) { | |
if(settings?.onlineStatMon != false) { | |
t1 += "\n\nWatchDog Monitors:" | |
t1 += "\n • Notify if device is offline" | |
if(tstats) { | |
t1 += "\n • Notify on low temperature extremes" | |
if(settings?.thermMissedEco != false) { | |
t1 += "\n • Notify When Away and Thermostat not in Eco Mode" | |
} | |
} | |
if(cams && (settings?.onlineStatMon != false)) { | |
def camStreamNotif = parent.getSettingVal("camStreamNotifMsg") | |
if(camStreamNotif != false) { | |
t1 += "\n • Notify on Camera Streaming status changes" | |
} | |
} | |
} | |
} | |
def camStreamNotif = parent.getSettingVal("locPresChangeMsg") | |
if(camStreamNotif != false) { | |
t1 += "\n • Notify Nest Home/Away Status changes" | |
} | |
} | |
watDesc += t1 ? "${t1}" + descriptions("d_ttm") : "" | |
String watDogDesc = isWatchdogConfigured() ? "${watDesc}" : null | |
href "watchDogPage", title: imgTitle(getAppImg("watchdog_icon.png"), inputTitleStr(titles("t_nlw"))), description: watDogDesc ?: descriptions("d_ttc"), state: (watDogDesc ? "complete" : null) | |
} | |
} | |
} | |
section(sectionTitleStr("Automation Options:")) { | |
if(/* state?.isInstalled && */ (isNestModesConfigured() || isWatchdogConfigured() || isSchMotConfigured())) { | |
//paragraph paraTitleStr("Enable/Disable this Automation") | |
input "autoDisabledreq", "bool", title: imgTitle(getAppImg("disable_icon2.png"), inputTitleStr("Disable this Automation?")), required: false, defaultValue: false /* state?.autoDisabled */, submitOnChange: true | |
setAutomationStatus() | |
} | |
input ("showDebug", "bool", title: imgTitle(getAppImg("debug_icon.png"), inputTitleStr("Debug Option")), description: "Show ${app?.name} Logs in the IDE?", required: false, defaultValue: false, submitOnChange: true) | |
if(showDebug) { | |
input (name: "advAppDebug", type: "bool", title: imgTitle(getAppImg("list_icon.png"), inputTitleStr("Show Verbose Logs?")), required: false, defaultValue: false, submitOnChange: true) | |
} else { | |
settingUpdate("advAppDebug", "false", "bool") | |
} | |
} | |
section(paraTitleStr("Automation Name:")) { | |
String newName = getAutoTypeLabel() | |
if(!app?.label) { app?.updateLabel("${newName}") } | |
label title: imgTitle(getAppImg("name_tag_icon.png"), inputTitleStr("Label this Automation: Suggested Name: ${newName}")), defaultValue: "${newName}", required: true //, wordWrap: true | |
if(!state?.isInstalled) { | |
paragraph "Make sure to name it something that you can easily recognize." | |
} | |
} | |
} | |
} | |
} | |
String getSchMotConfigDesc(retAsList=false) { | |
def list = [] | |
if(settings?.schMotWaterOff) { list.push("Turn Off if Leak Detected") } | |
if(settings?.schMotContactOff) { list.push("Set ECO if Contact Open") } | |
if(settings?.schMotExternalTempOff) { list.push("Set ECO based on External Temp") } | |
if(settings?.schMotRemoteSensor) { list.push("Use Remote Temp Sensors") } | |
if(isTstatSchedConfigured()) { list.push("Setpoint Schedules Created") } | |
if(settings?.schMotOperateFan) { list.push("Control Fans with HVAC") } | |
if(settings?.schMotHumidityControl) { list.push("Control Humidifier") } | |
if(retAsList) { | |
return isSchMotConfigured() ? list : null | |
} else { | |
String sDesc = "" | |
sDesc += settings?.schMotTstat ? "${settings?.schMotTstat?.label}" : "" | |
list?.each { ls -> | |
sDesc += "\n • ${ls}" | |
} | |
String t1 = getNotifConfigDesc("schMot") | |
sDesc += t1 ? "\n\n${t1}" : "" | |
sDesc += settings?.schMotTstat ? descriptions("d_ttm") : "" | |
return isSchMotConfigured() ? "${sDesc}" : null | |
} | |
} | |
void setAutomationStatus(upd=false) { | |
Boolean myDis = (settings?.autoDisabledreq == true) | |
Boolean settingsReset = (parent.getSettingVal("disableAllAutomations") == true) | |
Boolean storAutoType = getAutoType() == "storage" ? true : false | |
if(settingsReset && !storAutoType) { | |
if(!myDis && settingsReset) { LogAction("setAutomationStatus: Nest Integrations forcing disable", "info", true) } | |
myDis = true | |
} else if(storAutoType) { | |
myDis = false | |
} | |
if(!getIsAutomationDisabled() && myDis) { | |
LogAction("Automation Disabled at (${getDtNow()})", "info", true) | |
state?.autoDisabledDt = getDtNow() | |
} else if(getIsAutomationDisabled() && !myDis) { | |
LogAction("Automation Enabled at (${getDtNow()})", "info", true) | |
state?.autoDisabledDt = null | |
} | |
state?.autoDisabled = myDis | |
if(upd) { app.update() } | |
} | |
void settingUpdate(String name, value, String type=null) { | |
//LogTrace("settingUpdate($name, $value, $type)...") | |
if(name) { | |
if(value == "" || value == null || value == []) { | |
settingRemove(name) | |
return | |
} | |
} | |
if(name && type) { app?.updateSetting("$name", [type: "$type", value: value]) } | |
else if (name && type == null) { app?.updateSetting(name.toString(), value) } | |
} | |
void settingRemove(String name) { | |
//LogTrace("settingRemove($name)...") | |
if(name) { app?.clearSetting(name.toString()) } | |
} | |
def stateUpdate(String key, value) { | |
if(key) { state?."${key}" = value; return true } | |
//else { LogAction("stateUpdate: null key $key $value", "error", true); return false } | |
} | |
void stateRemove(String key) { | |
state.remove(key?.toString()) | |
} | |
def initAutoApp() { | |
//log.debug "${app.label} initAutoApp..." // Must be log.debug | |
if(settings["watchDogFlag"]) { | |
state?.autoTyp = "watchDog" | |
} | |
String autoType = getAutoType() | |
if(autoType == "nMode") { | |
parent.automationNestModeEnabled(true) | |
} | |
unschedule() | |
unsubscribe() | |
//def autoDisabled = getIsAutomationDisabled() | |
setAutomationStatus() | |
automationsInst() | |
if(autoType == "schMot" && isSchMotConfigured()) { | |
updateScheduleStateMap() | |
def schedList = getScheduleList() | |
boolean timersActive = false | |
def sLbl | |
int cnt = 1 | |
int numact = 0 | |
schedList?.each { scd -> | |
sLbl = "schMot_${scd}_" | |
stateRemove("sched${cnt}restrictions") | |
stateRemove("schedule${cnt}SwEnabled") | |
stateRemove("schedule${cnt}PresEnabled") | |
stateRemove("schedule${cnt}MotionEnabled") | |
stateRemove("schedule${cnt}SensorEnabled") | |
stateRemove("schedule${cnt}TimeActive") | |
stateRemove("${sLbl}MotionActiveDt") | |
stateRemove("${sLbl}MotionInActiveDt") | |
stateRemove("${sLbl}oldMotionActive") | |
stateRemove("motion${cnt}UseMotionSettings") | |
stateRemove("motion${cnt}LastisBtwn") | |
def newscd = [:] | |
def act = settings["${sLbl}SchedActive"] | |
if(act) { | |
newscd = cleanUpMap([ | |
m: settings["${sLbl}rstrctMode"], | |
tf: settings["${sLbl}rstrctTimeFrom"], | |
tfc: settings["${sLbl}rstrctTimeFromCustom"], | |
tfo: settings["${sLbl}rstrctTimeFromOffset"], | |
tt: settings["${sLbl}rstrctTimeTo"], | |
ttc: settings["${sLbl}rstrctTimeToCustom"], | |
tto: settings["${sLbl}rstrctTimeToOffset"], | |
w: settings["${sLbl}restrictionDOW"], | |
p1: buildDeviceNameList(settings["${sLbl}rstrctPHome"], "and"), | |
p0: buildDeviceNameList(settings["${sLbl}rstrctPAway"], "and"), | |
s1: buildDeviceNameList(settings["${sLbl}rstrctSWOn"], "and"), | |
s0: buildDeviceNameList(settings["${sLbl}rstrctSWOff"], "and"), | |
ctemp: roundTemp(settings["${sLbl}CoolTemp"]), | |
htemp: roundTemp(settings["${sLbl}HeatTemp"]), | |
hvacm: settings["${sLbl}HvacMode"], | |
sen0: settings["schMotRemoteSensor"] ? buildDeviceNameList(settings["${sLbl}remSensor"], "and") : null, | |
thres: settings["schMotRemoteSensor"] ? settings["${sLbl}remSenThreshold"] : null, | |
m0: buildDeviceNameList(settings["${sLbl}Motion"], "and"), | |
mctemp: settings["${sLbl}Motion"] ? roundTemp(settings["${sLbl}MCoolTemp"]) : null, | |
mhtemp: settings["${sLbl}Motion"] ? roundTemp(settings["${sLbl}MHeatTemp"]) : null, | |
mhvacm: settings["${sLbl}Motion"] ? settings["${sLbl}MHvacMode"] : null, | |
// mpresHome: settings["${sLbl}Motion"] ? settings["${sLbl}MPresHome"] : null, | |
// mpresAway: settings["${sLbl}Motion"] ? settings["${sLbl}MPresAway"] : null, | |
mdelayOn: settings["${sLbl}Motion"] ? settings["${sLbl}MDelayValOn"] : null, | |
mdelayOff: settings["${sLbl}Motion"] ? settings["${sLbl}MDelayValOff"] : null | |
]) | |
numact += 1 | |
//LogTrace("initAutoApp: [Schedule: $scd | sLbl: $sLbl | act: $act | newscd: $newscd]") | |
state."sched${cnt}restrictions" = newscd | |
state."schedule${cnt}SwEnabled" = (newscd?.s1 || newscd?.s0) ? true : false | |
state."schedule${cnt}PresEnabled" = (newscd?.p1 || newscd?.p0) ? true : false | |
state."schedule${cnt}MotionEnabled" = (newscd?.m0) ? true : false | |
state."schedule${cnt}SensorEnabled" = (newscd?.sen0) ? true : false | |
//state."schedule${cnt}FanCtrlEnabled" = (newscd?.fan0) ? true : false | |
state."schedule${cnt}TimeActive" = (newscd?.tf || newscd?.tfc || newscd?.tfo || newscd?.tt || newscd?.ttc || newscd?.tto || newscd?.w) ? true : false | |
def newact = isMotionActive(settings["${sLbl}Motion"]) | |
if(newact) { state."${sLbl}MotionActiveDt" = getDtNow() } | |
else { state."${sLbl}MotionInActiveDt" = getDtNow() } | |
state."${sLbl}oldMotionActive" = newact | |
state?."motion${cnt}UseMotionSettings" = null // clear automation state of schedule in use motion state | |
state?."motion${cnt}LastisBtwn" = false | |
} | |
timersActive = (timersActive || state?."schedule${cnt}TimeActive") ? true : false | |
cnt += 1 | |
} | |
state.scheduleTimersActive = timersActive | |
state.schedLast = null // clear automation state of schedule in use | |
state.scheduleActiveCnt = numact | |
} | |
subscribeToEvents() | |
scheduler() | |
app.updateLabel(getAutoTypeLabel()) | |
LogAction("Automation Label: ${getAutoTypeLabel()}", "info", false) | |
stateRemove("motionnullLastisBtwn") | |
//state.remove("motion1InBtwn") | |
//state.remove("motion2InBtwn") | |
//state.remove("motion3InBtwn") | |
//state.remove("motion4InBtwn") | |
//state.remove("TstatTurnedOff") | |
//state.remove("schedule{1}TimeActive") | |
//state.remove("schedule{2}TimeActive") | |
//state.remove("schedule{3}TimeActive") | |
//state.remove("schedule{4}TimeActive") | |
//state.remove("schedule{5}TimeActive") | |
//state.remove("schedule{6}TimeActive") | |
//state.remove("schedule{7}TimeActive") | |
//state.remove("schedule{8}TimeActive") | |
//state.remove("lastaway") | |
stateRemove("evalSched") | |
stateRemove("dbgAppndName") // cause Automations to re-check with parent for value | |
stateRemove("wDevInst") // cause Automations to re-check with parent for value after updated is called | |
stateRemove("enRemDiagLogging") // cause recheck | |
settingUpdate("showDebug", "true", "bool") | |
//settingUpdate("advAppDebug", "false", "bool") | |
stateRemove("detailEventHistory") | |
stateRemove("detailExecutionHistory") | |
scheduleAutomationEval(30) | |
if(settings?.showDebug || settings?.advAppDebug) { runIn(1800, logsOff) } | |
} | |
void logsOff() { | |
log.warn "debug logging disabled..." | |
settingUpdate("showDebug", "false", "bool") | |
settingUpdate("advAppDebug", "false", "bool") | |
} | |
def uninstAutomationApp() { | |
//LogTrace("uninstAutomationApp") | |
String autoType = getAutoType() | |
if(autoType == "schMot") { | |
removeVstat("uninstAutomationApp") | |
} | |
if(autoType == "nMode") { | |
parent?.automationNestModeEnabled(false) | |
} | |
} | |
String getCurAppLbl() { return app?.label?.toString() } | |
String getAutoTypeLabel() { | |
//LogTrace("getAutoTypeLabel()") | |
String type = state?.autoTyp | |
String appLbl = getCurAppLbl() | |
String newName = appName() == "${appLabel()}" ? "NST Automations" : "${appName()}" | |
String typeLabel = "" | |
String newLbl | |
String dis = (getIsAutomationDisabled() == true) ? "\n(Disabled)" : "" | |
if(type == "nMode") { typeLabel = "${newName} (NestMode)" } | |
else if(type == "watchDog") { typeLabel = "Nest Location ${location.name} Watchdog"} | |
else if(type == "schMot") { typeLabel = "${newName} (${settings?.schMotTstat?.label})" } | |
//log.info "getAutoTypeLabel: ${type} ${appLbl} ${appName()} ${appLabel()} ${typeLabel}" | |
if(appLbl != "" && appLbl && appLbl != "Nest Manager" && appLbl != "${appLabel()}") { | |
if(appLbl?.contains("\n(Disabled)")) { | |
newLbl = appLbl?.replaceAll('\\\n\\(Disabled\\)', '') | |
} else { | |
newLbl = appLbl | |
} | |
} else { | |
newLbl = typeLabel | |
} | |
return "${newLbl}${dis}" | |
} | |
/* | |
def getAppStateData() { | |
return getState() | |
} | |
*/ | |
def getSettingsData() { | |
def sets = [] | |
settings?.sort().each { st -> | |
sets << st | |
} | |
return sets | |
} | |
def getSettingVal(var) { | |
if(var == null) { return settings } | |
return settings[var] ?: null | |
} | |
def getStateVal(var) { | |
return state[var] ?: null | |
} | |
public automationsInst() { | |
state.isNestModesConfigured = (isNestModesConfigured() == true) | |
state.isWatchdogConfigured = (isWatchdogConfigured() == true) | |
state.isSchMotConfigured = (isSchMotConfigured() == true) | |
state.isLeakWatConfigured = (isLeakWatConfigured() == true) | |
state.isConWatConfigured = (isConWatConfigured() == true) | |
state.isHumCtrlConfigured = (isHumCtrlConfigured() == true) | |
state.isExtTmpConfigured = (isExtTmpConfigured() == true) | |
state.isRemSenConfigured = (isRemSenConfigured() == true) | |
state.isTstatSchedConfigured = (isTstatSchedConfigured() == true) | |
state.isFanCtrlConfigured = (isFanCtrlSwConfigured() == true) | |
state.isFanCircConfigured = (isFanCircConfigured() == true) | |
state?.isInstalled = true | |
} | |
List getAutomationsInstalled() { | |
List list = [] | |
String aType = state?.autoTyp | |
switch(aType) { | |
case "nMode": | |
list.push(aType) | |
break | |
case "schMot": | |
def tmp = [:] | |
tmp[aType] = [] | |
if(isLeakWatConfigured()) { tmp[aType].push("leakWat") } | |
if(isConWatConfigured()) { tmp[aType].push("conWat") } | |
if(isHumCtrlConfigured()) { tmp[aType].push("humCtrl") } | |
if(isExtTmpConfigured()) { tmp[aType].push("extTmp") } | |
if(isRemSenConfigured()) { tmp[aType].push("remSen") } | |
if(isTstatSchedConfigured()) { tmp[aType].push("tSched") } | |
if(isFanCtrlSwConfigured()) { tmp[aType].push("fanCtrl") } | |
if(isFanCircConfigured()) { tmp[aType].push("fanCirc") } | |
if(tmp?.size()) { list.push(tmp) } | |
break | |
case "watchDog": | |
list.push(aType) | |
break | |
} | |
//LogTrace("getAutomationsInstalled List: $list") | |
return list | |
} | |
String getAutomationType() { | |
return state?.autoTyp ?: null | |
} | |
String getAutoType() { return !parent ? "" : state?.autoTyp } | |
boolean getIsAutomationDisabled() { | |
def dis = state?.autoDisabled | |
return (dis != null && dis == true) ? true : false | |
} | |
void subscribeToEvents() { | |
//Remote Sensor Subscriptions | |
String autoType = getAutoType() | |
List swlist = [] | |
//Nest Mode Subscriptions | |
if(autoType == "nMode") { | |
if(isNestModesConfigured()) { | |
if(!settings?.nModePresSensor && !settings?.nModeSwitch && (settings?.nModeHomeModes || settings?.nModeAwayModes)) { subscribe(location, "mode", nModeGenericEvt) } | |
if(settings?.nModePresSensor && !settings?.nModeSwitch) { subscribe(nModePresSensor, "presence", nModeGenericEvt) } | |
if(settings?.nModeSwitch && !settings?.nModePresSensor) { subscribe(nModeSwitch, "switch", nModeGenericEvt) } | |
/* | |
def tstats = parent.getSettingVal("thermostats") | |
def foundTstats | |
if(tstats) { | |
foundTstats = tstats?.collect { dni -> | |
def d1 = parent.getDevice(dni) | |
if(d1) { | |
//LogAction("Found: ${d1?.displayName} with (Id: ${dni?.key})", "debug", false) | |
//subscribe(d1, "ThermostatMode", automationGenericEvt) // this is not needed for nMode | |
//subscribe(d1, "presence", automationGenericEvt) // this is not needed, tracking only | |
} | |
return d1 | |
} | |
} | |
*/ | |
List t0 = [] | |
if(settings["nModerstrctSWOn"]) { t0 = t0 + settings["nModerstrctSWOn"] } | |
if(settings["nModerstrctSWOff"]) { t0 = t0 + settings["nModerstrctSWOff"] } | |
for(sw in t0) { | |
if(swlist?.contains(sw)) { | |
//log.trace "found $sw" | |
} else { | |
swlist.push(sw) | |
subscribe(sw, "switch", automationGenericEvt) | |
} | |
} | |
} | |
} | |
//ST Thermostat Motion | |
if(autoType == "schMot") { | |
def needThermTemp | |
def needThermMode | |
def needThermPres | |
if(isSchMotConfigured()) { | |
if(settings?.schMotWaterOff) { | |
if(isLeakWatConfigured()) { | |
setInitialVoiceMsgs(leakWatPrefix()) | |
setCustomVoice(leakWatPrefix()) | |
subscribe(leakWatSensors, "water", leakWatSensorEvt) | |
} | |
} | |
if(settings?.schMotContactOff) { | |
if(isConWatConfigured()) { | |
setInitialVoiceMsgs(conWatPrefix()) | |
setCustomVoice(conWatPrefix()) | |
subscribe(conWatContacts, "contact", conWatContactEvt) | |
List t0 = [] | |
if(settings["conWatrstrctSWOn"]) { t0 = t0 + settings["conWatrstrctSWOn"] } | |
if(settings["conWatrstrctSWOff"]) { t0 = t0 + settings["conWatrstrctSWOff"] } | |
for(sw in t0) { | |
if(swlist?.contains(sw)) { | |
//log.trace "found $sw" | |
} else { | |
swlist.push(sw) | |
subscribe(sw, "switch", automationGenericEvt) | |
} | |
} | |
} | |
} | |
if(settings?.schMotHumidityControl) { | |
if(isHumCtrlConfigured()) { | |
subscribe(humCtrlSwitches, "switch", automationGenericEvt) | |
subscribe(humCtrlHumidity, "humidity", automationGenericEvt) | |
if(!settings?.humCtrlUseWeather && settings?.humCtrlTempSensor) { subscribe(humCtrlTempSensor, "temperature", automationGenericEvt) } | |
if(settings?.humCtrlUseWeather) { | |
//state.needWeathUpd = true | |
def weather = parent.getSettingVal("weatherDevice") | |
if(weather) { | |
subscribe(weather, "temperature", extTmpGenericEvt) | |
} else { LogAction("No weather device found", "error", true) } | |
} | |
def t0 = [] | |
if(settings["humCtrlrstrctSWOn"]) { t0 = t0 + settings["humCtrlrstrctSWOn"] } | |
if(settings["humCtrlrstrctSWOff"]) { t0 = t0 + settings["humCtrlrstrctSWOff"] } | |
for(sw in t0) { | |
if(swlist?.contains(sw)) { | |
//log.trace "found $sw" | |
} else { | |
swlist.push(sw) | |
subscribe(sw, "switch", automationGenericEvt) | |
} | |
} | |
} | |
} | |
if(settings?.schMotExternalTempOff) { | |
if(isExtTmpConfigured()) { | |
setInitialVoiceMsgs(extTmpPrefix()) | |
setCustomVoice(extTmpPrefix()) | |
if(settings?.extTmpUseWeather) { | |
//state.needWeathUpd = true | |
def weather = parent.getSettingVal("weatherDevice") | |
if(weather) { | |
subscribe(weather, "temperature", extTmpGenericEvt) | |
subscribe(weather, "humidity", extTmpGenericEvt) | |
} else { LogAction("No weather device found", "error", true) } | |
} | |
def t0 = [] | |
if(settings["extTmprstrctSWOn"]) { t0 = t0 + settings["extTmprstrctSWOn"] } | |
if(settings["extTmprstrctSWOff"]) { t0 = t0 + settings["extTmprstrctSWOff"] } | |
for(sw in t0) { | |
if(swlist?.contains(sw)) { | |
//log.trace "found $sw" | |
} else { | |
swlist.push(sw) | |
subscribe(sw, "switch", automationGenericEvt) | |
} | |
} | |
if(!settings?.extTmpUseWeather && settings?.extTmpTempSensor) { subscribe(extTmpTempSensor, "temperature", extTmpGenericEvt) } | |
state.extTmpChgWhileOnDt = getDtNow() | |
state.extTmpChgWhileOffDt = getDtNow() | |
} | |
} | |
def senlist = [] | |
if(settings?.schMotRemoteSensor) { | |
if(isRemSenConfigured()) { | |
if(settings?.remSensorDay) { | |
for(sen in settings?.remSensorDay) { | |
if(senlist?.contains(sen)) { | |
//log.trace "found $sen" | |
} else { | |
senlist.push(sen) | |
subscribe(sen, "temperature", automationGenericEvt) | |
subscribe(sen, "humidity", automationGenericEvt) | |
if(settings?.schMotExternalTempOff) { | |
if(isExtTmpConfigured()) { | |
subscribe(sen, "temperature", extTmpGenericEvt) | |
subscribe(sen, "humidity", extTmpGenericEvt) | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
if(isTstatSchedConfigured()) { } | |
if(settings?.schMotOperateFan) { | |
if(isFanCtrlSwConfigured() && fanCtrlFanSwitches) { | |
subscribe(fanCtrlFanSwitches, "switch", automationGenericEvt) | |
subscribe(fanCtrlFanSwitches, "level", automationGenericEvt) | |
} | |
def t0 = [] | |
if(settings["fanCtrlrstrctSWOn"]) { t0 = t0 + settings["fanCtrlrstrctSWOn"] } | |
if(settings["fanCtrlrstrctSWOff"]) { t0 = t0 + settings["fanCtrlrstrctSWOff"] } | |
for(sw in t0) { | |
if(swlist?.contains(sw)) { | |
//log.trace "found $sw" | |
} else { | |
swlist.push(sw) | |
subscribe(sw, "switch", automationGenericEvt) | |
} | |
} | |
} | |
Boolean hasFan = (state?.schMotTstatHasFan == true) | |
if(hasFan && (settings?.schMotOperateFan || settings?.schMotRemoteSensor || settings?.schMotHumidityControl)) { | |
subscribe(settings.schMotTstat, "thermostatFanMode", automationGenericEvt) | |
} | |
List schedList = getScheduleList() | |
String sLbl | |
Integer cnt = 1 | |
List prlist = [] | |
List mtlist = [] | |
schedList?.each { scd -> | |
sLbl = "schMot_${scd}_" | |
def restrict = state?."sched${cnt}restrictions" | |
def act = settings["${sLbl}SchedActive"] | |
if(act) { | |
if(state?."schedule${cnt}SwEnabled") { | |
if(restrict?.s1) { | |
for(sw in settings["${sLbl}rstrctSWOn"]) { | |
if(swlist?.contains(sw)) { | |
//log.trace "found $sw" | |
} else { | |
swlist.push(sw) | |
subscribe(sw, "switch", automationGenericEvt) | |
} | |
} | |
} | |
if(restrict?.s0) { | |
for(sw in settings["${sLbl}rstrctSWOff"]) { | |
if(swlist?.contains(sw)) { | |
//log.trace "found $sw" | |
} else { | |
swlist.push(sw) | |
subscribe(sw, "switch", automationGenericEvt) | |
} | |
} | |
} | |
} | |
if(state?."schedule${cnt}PresEnabled") { | |
if(restrict?.p1) { | |
for(pr in settings["${sLbl}rstrctPHome"]) { | |
if(prlist?.contains(pr)) { | |
//log.trace "found $pr" | |
} else { | |
prlist.push(pr) | |
subscribe(pr, "presence", automationGenericEvt) | |
} | |
} | |
} | |
if(restrict?.p0) { | |
for(pr in settings["${sLbl}rstrctPAway"]) { | |
if(prlist?.contains(pr)) { | |
//log.trace "found $pr" | |
} else { | |
prlist.push(pr) | |
subscribe(pr, "presence", automationGenericEvt) | |
} | |
} | |
} | |
} | |
if(state?."schedule${cnt}MotionEnabled") { | |
if(restrict?.m0) { | |
for(mt in settings["${sLbl}Motion"]) { | |
if(mtlist?.contains(mt)) { | |
//log.trace "found $mt" | |
} else { | |
mtlist.push(mt) | |
subscribe(mt, "motion", automationMotionEvt) | |
} | |
} | |
} | |
} | |
if(state?."schedule${cnt}SensorEnabled") { | |
if(restrict?.sen0) { | |
for(sen in settings["${sLbl}remSensor"]) { | |
if(senlist?.contains(sen)) { | |
//log.trace "found $sen" | |
} else { | |
senlist.push(sen) | |
subscribe(sen, "temperature", automationGenericEvt) | |
} | |
} | |
} | |
} | |
} | |
cnt += 1 | |
} | |
subscribe(settings.schMotTstat, "thermostatMode", automationGenericEvt) | |
subscribe(settings.schMotTstat, "thermostatOperatingState", automationGenericEvt) | |
subscribe(settings.schMotTstat, "temperature", automationGenericEvt) | |
subscribe(settings.schMotTstat, "presence", automationGenericEvt) | |
def canCool = state?.schMotTstatCanCool | |
if(canCool) { | |
subscribe(settings.schMotTstat, "coolingSetpoint", automationGenericEvt) | |
} | |
def canHeat = state?.schMotTstatCanHeat | |
if(canHeat) { | |
subscribe(settings.schMotTstat, "heatingSetpoint", automationGenericEvt) | |
} | |
subscribe(location, "sunset", automationGenericEvt) | |
subscribe(location, "sunrise", automationGenericEvt) | |
subscribe(location, "mode", automationGenericEvt) | |
} | |
} | |
//watchDog Subscriptions | |
if(autoType == "watchDog") { | |
// if(isWatchdogConfigured()) | |
def tstats = parent.getSettingVal("thermostats") | |
def foundTstats | |
if(tstats) { | |
foundTstats = tstats?.collect { dni -> | |
def d1 = parent.getDevice(dni) | |
if(d1) { | |
//LogAction("Found: ${d1?.displayName} with (Id: ${dni?.key})", "debug", false) | |
subscribe(d1, "temperature", automationGenericEvt) | |
subscribe(d1, "thermostatMode", automationGenericEvt) | |
subscribe(d1, "presence", automationGenericEvt) | |
subscribe(d1, "onlineStatus", automationGenericEvt) | |
subscribe(location, "mode", automationGenericEvt) | |
} | |
return d1 | |
} | |
} | |
def prots = parent.getSettingVal("protects") | |
def foundProts | |
if(prots) { | |
foundProts = prots?.collect { dni -> | |
def d1 = parent.getDevice(dni) | |
if(d1) { | |
//LogAction("Found: ${d1?.displayName} with (Id: ${dni?.key})", "debug", false) | |
subscribe(d1, "onlineStatus", automationGenericEvt) | |
} | |
return d1 | |
} | |
} | |
def cams = parent.getSettingVal("cameras") | |
def foundCams | |
if(cams) { | |
foundCams = cams?.collect { dni -> | |
def d1 = parent.getDevice(dni) | |
if(d1) { | |
//LogAction("Found: ${d1?.displayName} with (Id: ${dni?.key})", "debug", false) | |
subscribe(d1, "onlineStatus", automationGenericEvt) | |
subscribe(d1, "isStreaming", automationGenericEvt) | |
} | |
return d1 | |
} | |
} | |
} | |
//Alarm status monitoring if any automation has alarm notification enabled | |
if(settings["${autoType}AlarmDevices"] && settings?."${pName}AllowAlarmNotif") { | |
if(settings["${autoType}_Alert_1_Use_Alarm"] || settings["${autoType}_Alert_2_Use_Alarm"]) { | |
subscribe(settings["${autoType}AlarmDevices"], "alarm", alarmAlertEvt) | |
} | |
} | |
} | |
void scheduler() { | |
def random = new Random() | |
int random_int = random.nextInt(60) | |
int random_dint = random.nextInt(9) | |
String autoType = getAutoType() | |
if(autoType == "schMot" && state?.scheduleActiveCnt && state?.scheduleTimersActive) { | |
LogTrace("${autoType} scheduled (${random_int} ${random_dint}/5 * * * ?)") | |
schedule("${random_int} ${random_dint}/5 * * * ?", heartbeatAutomation) | |
} else if(autoType != "remDiag" && autoType != "storage") { | |
LogTrace("${autoType} scheduled (${random_int} ${random_dint}/30 * * * ?)") | |
schedule("${random_int} ${random_dint}/30 * * * ?", heartbeatAutomation) | |
} | |
} | |
void heartbeatAutomation() { | |
String autoType = getAutoType() | |
String str = "heartbeatAutomation() ${autoType}" | |
int val = 900 | |
if(autoType == "schMot") { | |
val = 220 | |
} | |
if(getAutoRunInSec() > val) { | |
LogTrace("${str} RUN") | |
runAutomationEval() | |
} else { | |
LogTrace("${str} NOT NEEDED") | |
} | |
} | |
int defaultAutomationTime() { | |
return 20 | |
} | |
void scheduleAutomationEval(schedtime = defaultAutomationTime()) { | |
int theTime = schedtime | |
if(theTime < defaultAutomationTime()) { theTime = defaultAutomationTime() } | |
String autoType = getAutoType() | |
def random = new Random() | |
int random_int = random.nextInt(6) // this randomizes a bunch of automations firing at same time off same event | |
boolean waitOverride = false | |
switch(autoType) { | |
case "nMode": | |
if(theTime == defaultAutomationTime()) { | |
theTime = 14 + random_int // this has nMode fire first as it may change the Nest Mode | |
} | |
break | |
case "schMot": | |
if(theTime == defaultAutomationTime()) { | |
theTime += random_int | |
} | |
int schWaitVal = settings?.schMotWaitVal?.toInteger() ?: 60 | |
if(schWaitVal > 120) { schWaitVal = 120 } | |
int t0 = getAutoRunSec() | |
if((schWaitVal - t0) >= theTime ) { | |
theTime = (schWaitVal - t0) | |
waitOverride = true | |
} | |
//theTime = Math.min( Math.max(theTime,defaultAutomationTime()), 120) | |
break | |
case "watchDog": | |
if(theTime == defaultAutomationTime()) { | |
theTime = 35 + random_int // this has watchdog fire last so other automations can finish changes | |
} | |
break | |
} | |
if(!state?.evalSched) { | |
runIn(theTime, "runAutomationEval", [overwrite: true]) | |
state?.autoRunInSchedDt = getDtNow() | |
state.evalSched = true | |
state.evalSchedLastTime = theTime | |
} else { | |
String str = "scheduleAutomationEval: " | |
def t0 = state?.evalSchedLastTime | |
if(t0 == null) { t0 = 0 } | |
int timeLeftPrev = t0 - getAutoRunInSec() | |
if(timeLeftPrev < 0) { timeLeftPrev = 100 } | |
String str1 = " Schedule change: from (${timeLeftPrev}sec) to (${theTime}sec)" | |
if(timeLeftPrev > (theTime + 5) || waitOverride) { | |
if(Math.abs(timeLeftPrev - theTime) > 3) { | |
runIn(theTime, "runAutomationEval", [overwrite: true]) | |
LogTrace("${str}Performing${str1}") | |
state?.autoRunInSchedDt = getDtNow() | |
state.evalSched = true | |
state.evalSchedLastTime = theTime | |
} | |
} else { LogTrace("${str}Skipping${str1}") } | |
} | |
} | |
//def getAutoRunInSec() { return !state?.autoRunInSchedDt ? 100000 : GetTimeDiffSeconds(state?.autoRunInSchedDt, null, "getAutoRunInSec").toInteger() } | |
int getAutoRunInSec() { return getTimeSeconds("autoRunInSchedDt", 100000, "getAutoRunInSec") } | |
void runAutomationEval() { | |
LogTrace("runAutomationEval") | |
String autoType = getAutoType() | |
state.evalSched = false | |
switch(autoType) { | |
case "nMode": | |
if(isNestModesConfigured()) { | |
checkNestMode() | |
} | |
break | |
case "schMot": | |
/* not needed if streaming | |
if(state?.needChildUpdate) { | |
state?.needChildUpdate = false | |
parent?.setNeedChildUpdate() | |
} | |
*/ | |
if(isSchMotConfigured()) { | |
schMotCheck() | |
} | |
break | |
case "watchDog": | |
if(isWatchdogConfigured()) { | |
watchDogCheck() | |
} | |
break | |
default: | |
LogAction("runAutomationEval: Invalid Option Received ${autoType}", "warn", true) | |
break | |
} | |
} | |
/* | |
void sendAutoChgToDevice(dev, autoType, chgDesc) { | |
if(dev && autoType && chgDesc) { | |
try { | |
dev?.whoMadeChanges(autoType?.toString(), chgDesc?.toString(), getDtNow().toString()) | |
} catch (ex) { | |
log.error "sendAutoChgToDevice Exception:", ex | |
} | |
} | |
} | |
*/ | |
/* | |
def sendEcoActionDescToDevice(dev, desc) { | |
if(dev && desc) { | |
try { | |
dev?.ecoDesc(desc) // THIS ONLY WORKS ON NEST THERMOSTATS | |
} catch (ex) { | |
log.error "sendEcoActionDescToDevice Exception:", ex | |
} | |
} | |
} | |
*/ | |
/* | |
def getAutomationStats() { | |
return [ | |
"lastUpdatedDt":state?.lastUpdatedDt, | |
"lastEvalDt":state?.autoRunDt, | |
"lastEvent":state?.lastEventData, | |
"lastActionData":getAutoActionData(), | |
"lastSchedDt":state?.autoRunInSchedDt, | |
"lastExecVal":state?.autoExecMS, | |
"execAvgVal":(state?.evalExecutionHistory != [] ? getAverageValue(state?.evalExecutionHistory) : null) | |
] | |
} | |
*/ | |
void storeLastAction(String actionDesc, String actionDt, String autoType, dev=null) { | |
if(actionDesc && actionDt) { | |
def newVal = ["actionDesc":actionDesc, "dt":actionDt, "autoType":autoType] | |
state?.lastAutoActionData = newVal | |
def list = state?.detailActionHistory ?: [] | |
int listSize = 30 | |
if(list?.size() < listSize) { | |
list.push(newVal) | |
} | |
else if(list?.size() > listSize) { | |
def nSz = (list?.size()-listSize) + 1 | |
def nList = list?.drop(nSz) | |
nList?.push(newVal) | |
list = nList | |
} | |
else if(list?.size() == listSize) { | |
def nList = list?.drop(1) | |
nList?.push(newVal) | |
list = nList | |
} | |
if(list) { state?.detailActionHistory = list } | |
/* | |
if(dev) { | |
sendAutoChgToDevice(dev, autoType, actionDesc) // THIS ONLY WORKS ON NEST THERMOSTATS | |
} | |
*/ | |
} | |
} | |
/* | |
def getAutoActionData() { | |
if(state?.lastAutoActionData) { | |
return state?.lastAutoActionData | |
} | |
} | |
*/ | |
def automationGenericEvt(evt) { | |
def startTime = now() | |
def eventDelay = startTime - evt.date.getTime() | |
LogAction("${evt?.name.toUpperCase()} Event | Device: ${evt?.displayName} | Value: (${strCapitalize(evt?.value)}) with a delay of ${eventDelay}ms", "info", false) | |
/* if streaming, this is not needed | |
if(isRemSenConfigured() && settings?.vthermostat) { | |
state.needChildUpdate = true | |
} | |
if(settings?.humCtrlUseWeather && isHumCtrlConfigured()) { | |
state.needWeathUpd = true | |
} | |
*/ | |
doTheEvent(evt) | |
} | |
def doTheEvent(evt) { | |
if(getIsAutomationDisabled()) { return } | |
else { | |
scheduleAutomationEval() | |
storeLastEventData(evt) | |
} | |
} | |
/****************************************************************************** | |
| WATCHDOG AUTOMATION LOGIC CODE | | |
*******************************************************************************/ | |
String watchDogPrefix() { return "watchDog" } | |
def watchDogPage() { | |
String pName = watchDogPrefix() | |
dynamicPage(name: "watchDogPage", title: pageTitleStr(titles("t_nlw")), uninstall: false, install: true) { | |
section(sectionTitleStr(titles("t_nt"))) { | |
String t0 = getNotifConfigDesc(pName) | |
String pageDesc = t0 ? "${t0}" + descriptions("d_ttm") : "" | |
href "setNotificationPage1", title: imgTitle(getAppImg("i_not"), inputTitleStr(titles("t_nt"))), description: pageDesc, state: (pageDesc ? "complete" : null)//, params: ["pName":"${pName}", "allowSpeech":true, "allowAlarm":true, "showSchedule":true] | |
//ERS | |
if(settings?."${pName}NotifOn") { | |
def tstats = parent.getSettingVal("thermostats") | |
def prots = parent.getSettingVal("protects") | |
def cams = parent.getSettingVal("cameras") | |
if(tstats || prots || cams) { | |
input "onlineStatMon", "bool", title: paraTitleStr("Notify When Devices are offline?"), required: false, defaultValue: true, submitOnChange: true | |
} | |
if(tstats && (settings?.onlineStatMon != false)) { | |
paragraph imgTitle(getAppImg("i_sw"), paraTitleStr("Temperature warnings on")) | |
input "thermMissedEco", "bool", title: paraTitleStr("Notify When Away and Thermostat Not in Eco Mode?"), required: false, defaultValue: true, submitOnChange: true | |
} | |
if(cams && (settings?.onlineStatMon != false)) { | |
def camStreamNotif = parent.getSettingVal("camStreamNotifMsg") | |
def mys = camStreamNotif == false ? false : true | |
def iiact = mys ? "i_sw" : "switch_off_icon.png" | |
//settingUpdate("camStNot", mys.toString(), "bool") | |
paragraph imgTitle(getAppImg(iiact), paraTitleStr("Stream Notification (setting from mgr) ${mys}")) | |
//input "camStNot", "bool", title: imgTitle(getAppImg("i_sw"), inputTitleStr("Stream Notification (setting from mgr)")), required: false, defaultValue: mys, submitOnChange: true | |
} | |
def locPres = parent.getSettingVal("locPresChangeMsg") | |
boolean myp = locPres == false ? false : true | |
String iact = myp ? "i_sw" : "switch_off_icon.png" | |
paragraph imgTitle(getAppImg(iact), paraTitleStr("Nest Location Home/Away changes (setting from mgr) ${myp}")) | |
} else { | |
settingRemove("thermMissedEco") | |
settingRemove("onlineStatMon") | |
} | |
} | |
} | |
} | |
/* | |
def automationSafetyTempEvt(evt) { | |
def startTime = now() | |
def eventDelay = startTime - evt.date.getTime() | |
LogTrace("Event | Thermostat Safety Temp Exceeded: '${evt.displayName}' (${evt.value}) with a delay of ${eventDelay}ms") | |
if(getIsAutomationDisabled()) { return } | |
else { | |
if(evt?.value == "true") { | |
scheduleAutomationEval() | |
} | |
} | |
storeLastEventData(evt) | |
} | |
*/ | |
// Alarms will repeat every watDogRepeatMsgDelay (1 hr default) ALL thermostats | |
def watchDogCheck() { | |
if(getIsAutomationDisabled()) { return } | |
else { | |
long execTime = now() | |
state?.autoRunDt = getDtNow() | |
def tstats = parent.getSettingVal("thermostats") | |
def foundTstats | |
if(tstats) { | |
foundTstats = tstats?.collect { dni -> | |
if(checkOnline(dni)) { | |
def d1 = parent.getDevice(dni) | |
if(d1) { | |
if(!getSafetyTempsOk(d1)) { | |
watchDogAlarmActions(d1.displayName, dni, "temp") | |
//LogAction("watchDogCheck: | Thermostat: ${d1?.displayName} Safety Temp Exceeded: ${exceeded}", "warn", true) | |
} | |
// This is allowing for warning if Nest has problem of system coming out of ECO while away | |
boolean nestModeAway = (d1?.currentPresence?.toString() == "not present") ? true : false | |
//def nestModeAway = (getNestLocPres() == "home") ? false : true | |
if(nestModeAway) { | |
String curMode = d1?.currentThermostatMode?.toString() | |
if(!(curMode in ["eco", "off" ])) { | |
watchDogAlarmActions(d1.displayName, dni, "eco") | |
def pres = d1?.currentPresence?.toString() | |
//LogAction("watchDogCheck: | Thermostat: ${d1?.displayName} is Away and Mode Is Not in ECO | CurMode: (${curMode}) | CurrentPresence: (${pres})", "warn", true) | |
} | |
} | |
} | |
return d1 | |
} | |
} | |
} | |
def prots = parent.getSettingVal("protects") | |
def foundProts | |
if(prots) { | |
foundProts = prots?.collect { dni -> | |
checkOnline(dni) | |
return dni | |
} | |
} | |
def cams = parent.getSettingVal("cameras") | |
def foundCams | |
if(cams) { | |
foundCams = cams?.collect { dni -> | |
if(checkOnline(dni)) { | |
def d1 = parent.getDevice(dni) | |
if(d1) { | |
String lastStr = state?."lastStr${dni}" | |
String curStream = d1?.currentIsStreaming?.toString() | |
lastStr = lastStr ?: curStream | |
if(curStream) { | |
if(curStream != lastStr) { | |
watchDogAlarmActions(d1.displayName, dni, "stream", curStream, lastStr) | |
//LogAction("watchDogCheck: | ${d1?.displayName} streaming changed | CurStream: (${curStream}) | prev: (${lastStr})", "warn", true) | |
} | |
state?."lastStr${dni}" = curStream | |
} | |
return dni | |
} | |
} | |
} | |
} | |
def locPres = parent.getSettingVal("locPresChangeMsg") | |
boolean myp = locPres == false ? false : true | |
String curPres = parent.getLocationPresence() ?: "" | |
String lastPres = state?.lastPresence ?: "" | |
if(lastpres && (lastPres != curPres)) { | |
state.lastPresence = curPres | |
watchDogAlarmActions(location.name, "Location", "locPres", curPres, lastPres) | |
//LogAction("watchDogCheck: | Nest Location changed | Cur: (${curPres}) | prev: (${lastPres})", "info", true) | |
} | |
storeExecutionHistory((now()-execTime), "watchDogCheck") | |
} | |
} | |
boolean checkOnline(dni) { | |
def d1 = parent.getDevice(dni) | |
if(d1) { | |
def curOnline = d1?.currentOnlineStatus?.toString() | |
if(curOnline != "online") { | |
watchDogAlarmActions(d1.displayName, dni, "online") | |
//LogAction("watchDogCheck: | ${d1?.displayName} is not online | CurOnline: (${curOnline})", "warn", true) | |
return false | |
} | |
return true | |
} | |
return false | |
} | |
private void watchDogAlarmActions(dev, String dni, String actType, p1=null, p2=null) { | |
String pName = watchDogPrefix() | |
String evtNotifMsg = "" | |
String eventType = "Warning" | |
int lvl | |
switch(actType) { | |
case "temp": | |
evtNotifMsg = "Safety Temp exceeded on ${dev}." | |
break | |
case "eco": | |
if(settings["thermMissedEco"] != false) { | |
evtNotifMsg = "Nest Location Home/Away Mode is 'Away' and thermostat [${dev}] is not in ECO." | |
} else {return} | |
break | |
case "online": | |
if(settings["onlineStatMon"] != false) { | |
evtNotifMsg = "Device offline ${dev}." | |
} else {return} | |
break | |
case "stream": | |
evtNotifMsg = "Camera streaming changed for ${dev} New: ${p1} Old:${p2}." | |
lvl = 3 | |
break | |
case "locPres": | |
evtNotifMsg = "${dev} Nest Location has changed New: ${p1} Old: ${p2}." | |
lvl = 2 | |
eventType = "Info" | |
break | |
} | |
boolean allowNotif = settings["${pName}NotifOn"] ? true : false | |
boolean canNotif = (allowNotif && (getWatDogSafetyAlertDtSec("${dni}") > getWatDogRepeatMsgDelayVal())) ? true : false | |
if(canNotif) { | |
sendNofificationMsg(evtNotifMsg, eventType, pName, lvl) // this uses parent | |
def allowAlarm = allowNotif && settings?."${pName}AllowAlarmNotif" ? true : false | |
if(allowAlarm) { | |
scheduleAlarmOn(pName) | |
} | |
state?."watDogSafetyAlDt${dni}" = getDtNow() | |
} | |
String t0 = eventType == "Info" ? "info" : "warn" | |
LogAction("watchDogAlarmActions() | SENT: ${canNotif} | ${evtNotifMsg}", t0, true) | |
} | |
//def getWatDogSafetyAlertDtSec(dni) { return !state?."watDogSafetyAlDt${dni}" ? 10000 : GetTimeDiffSeconds(state?."watDogSafetyAlDt${dni}", null, "getWatDogSafetyAlertDtSec").toInteger() } | |
int getWatDogSafetyAlertDtSec(dni) { return getTimeSeconds("watDogSafetyAlDt${dni}", 10000, "getWatDogSafetyAlertDtSec") } | |
def getWatDogRepeatMsgDelayVal() { return !watDogRepeatMsgDelay ? 3600 : watDogRepeatMsgDelay.toInteger() } | |
boolean isWatchdogConfigured() { | |
return (state?.autoTyp == "watchDog") ? true : false | |
} | |
/////////////////////THERMOSTAT AUTOMATION CODE LOGIC /////////////////////// | |
/**************************************************************************** | |
| REMOTE SENSOR AUTOMATION CODE | | |
*****************************************************************************/ | |
String remSenPrefix() { return "remSen" } | |
void removeVstat(callerStr) { | |
String autoType = getAutoType() | |
if(autoType == "schMot") { | |
String mycallerStr = "${callerStr} removeVstat: Could " | |
String t0 = mycallerStr | |
String myID = getMyLockId() | |
if(!myID) { | |
setMyLockId(app.id) | |
myID = getMyLockId() | |
} | |
def toRemove = state?.remSenTstat | |
if(settings?.schMotTstat && myID && parent && toRemove) { | |
if(!parent?.addRemoveVthermostat(toRemove, false, myID)) { | |
t0 += "NOT " | |
} | |
t0 += "cleanup virtual thermostat\n" | |
state.oldremSenTstat = state?.remSenTstat | |
state?.remSenTstat = null | |
t0 += mycallerStr | |
if( !parent?.remSenUnlock(toRemove, myID) ) { // attempt unlock old ID | |
t0 += "NOT " | |
} | |
LogAction("${t0}Release remote sensor lock", "info", false) | |
} | |
} | |
} | |
//Requirements Section | |
boolean remSenCoolTempsReq() { return (settings?.remSenRuleType in [ "Cool", "Heat_Cool", "Cool_Circ", "Heat_Cool_Circ" ]) ? true : false } | |
boolean remSenHeatTempsReq() { return (settings?.remSenRuleType in [ "Heat", "Heat_Cool", "Heat_Circ", "Heat_Cool_Circ" ]) ? true : false } | |
boolean remSenDayHeatTempOk() { return (!remSenHeatTempsReq() || (remSenHeatTempsReq() && remSenDayHeatTemp)) ? true : false } | |
boolean remSenDayCoolTempOk() { return (!remSenCoolTempsReq() || (remSenCoolTempsReq() && remSenDayCoolTemp)) ? true : false } | |
boolean isRemSenConfigured() { | |
boolean devOk = (settings?.remSensorDay) ? true : false | |
return (settings?.schMotRemoteSensor && devOk && settings?.remSenRuleType && remSenDayHeatTempOk() && remSenDayCoolTempOk() ) ? true : false | |
} | |
int getMotionActiveSec(mySched) { | |
def sLbl = "schMot_${mySched}_" | |
return !state?."${sLbl}MotionActiveDt" ? 0 : GetTimeDiffSeconds(state?."${sLbl}MotionActiveDt", null, "getMotionActiveSec").toInteger() | |
} | |
int getMotionInActiveSec(mySched) { | |
def sLbl = "schMot_${mySched}_" | |
return !state?."${sLbl}MotionInActiveDt" ? 0 : GetTimeDiffSeconds(state?."${sLbl}MotionInActiveDt", null, "getMotionInActiveSec").toInteger() | |
} | |
def automationMotionEvt(evt) { | |
long startTime = now() | |
long eventDelay = startTime - evt.date.getTime() | |
LogAction("${evt?.name.toUpperCase()} Event | Device: '${evt?.displayName}' | Motion: (${strCapitalize(evt?.value)}) with a delay of ${eventDelay}ms", "info", false) | |
if(getIsAutomationDisabled()) { return } | |
else { | |
storeLastEventData(evt) | |
boolean dorunIn = false | |
int delay = 120 | |
String sLbl | |
def mySched = getCurrentSchedule() | |
def schedList = getScheduleList() | |
String schName = "" | |
for (cnt in schedList) { | |
sLbl = "schMot_${cnt}_" | |
def act = settings["${sLbl}SchedActive"] | |
if(act && settings["${sLbl}Motion"]) { | |
def str = settings["${sLbl}Motion"].toString() | |
if(str.contains( evt.displayName)) { | |
def oldActive = state?."${sLbl}oldMotionActive" | |
def newActive = isMotionActive(settings["${sLbl}Motion"]) | |
state."${sLbl}oldMotionActive" = newActive | |
if(oldActive != newActive) { | |
if(newActive) { | |
if(cnt == mySched) { delay = settings."${sLbl}MDelayValOn"?.toInteger() ?: 60 } | |
state."${sLbl}MotionActiveDt" = getDtNow() | |
} else { | |
if(cnt == mySched) { delay = settings."${sLbl}MDelayValOff"?.toInteger() ?: 30*60 } | |
state."${sLbl}MotionInActiveDt" = getDtNow() | |
} | |
} | |
LogAction("Updating Schedule Motion Sensor State | Schedule: (${cnt} - ${getSchedLbl(cnt)}) | Previous Active: (${oldActive}) | Current Status: ($newActive)", "info", false) | |
if(cnt == mySched) { dorunIn = true } | |
} | |
} | |
} | |
/* | |
if(settings["${sLbl}MPresHome"] || settings["${sLbl}MPresAway"]) { | |
if(settings["${sLbl}MPresHome"]) { if(!isSomebodyHome(settings["${sLbl}MPresHome"])) { dorunIn = false } } | |
if(settings["${sLbl}MPresAway"]) { if(isSomebodyHome(settings["${sLbl}MPresAway"])) { dorunIn = false } } | |
} | |
*/ | |
if(dorunIn) { | |
def val = Math.min( Math.max(delay,defaultAutomationTime()), 60) | |
LogTrace("Automation Schedule Motion | Scheduling Delay Check: ($delay sec) | adjusted (${val}) | Schedule: ($mySched - ${getSchedLbl(mySched)})") | |
scheduleAutomationEval(val) | |
} else { | |
def str = "Motion Event | Skipping Motion Check: " | |
if(mySched) { | |
str += "Motion Sensor is Not Used in Active Schedule (#${mySched} - ${getSchedLbl(getCurrentSchedule())})" | |
} else { | |
str += "No Active Schedule" | |
} | |
LogTrace(str) | |
} | |
} | |
} | |
def isMotionActive(sensors) { | |
def result | |
sensors?.each { sen -> | |
if(sen) { | |
def sval = sen?.currentMotion | |
if(sval == "active") { result = true } | |
} | |
} | |
return result | |
//return sensors?.currentMotion?.equals("active") ? true : false | |
} | |
def getDeviceVarAvg(items, var) { | |
def tmpAvg = [] | |
def tempVal = 0 | |
if(!items) { return tempVal } | |
else { | |
tmpAvg = items*."${var}" | |
if(tmpAvg && tmpAvg?.size() > 0) { tempVal = (tmpAvg?.sum().toDouble() / tmpAvg?.size().toDouble()).round(1) } | |
} | |
return tempVal.toDouble() | |
} | |
def getDeviceTempAvg(items) { | |
return getDeviceVarAvg(items, "currentTemperature") | |
} | |
def getDeviceTemp(dev) { | |
return getDeviceVarAvg(dev, "currentTemperature") | |
} | |
def remSenShowTempsPage() { | |
dynamicPage(name: "remSenShowTempsPage", uninstall: false) { | |
if(settings?.remSensorDay) { | |
def t0 = "${tUnitStr()}" | |
section("Default Sensor Temps: (Schedules can override)") { | |
def cnt = 0 | |
def rCnt = settings?.remSensorDay?.size() | |
def str = "" | |
str += "Sensor Temp (average): (${getDeviceTempAvg(settings?.remSensorDay)}${t0})\n│" | |
settings?.remSensorDay?.each { t -> | |
cnt = cnt+1 | |
str += "${(cnt >= 1) ? "${(cnt == rCnt) ? "\n└" : "\n├"}" : "\n└"} ${t?.label}: ${(t?.label?.toString()?.length() > 10) ? "\n${(rCnt == 1 || cnt == rCnt) ? " " : "│"}└ " : ""}(${getDeviceTemp(t)}${t0})" | |
} | |
paragraph imgTitle(getAppImg("i_t"), sectionTitleStr("${str}")), state: "complete" | |
} | |
} | |
} | |
} | |
def remSendoSetCool(chgval, onTemp, offTemp) { | |
def remSenTstat = settings?.schMotTstat | |
def remSenTstatMir = settings?.schMotTstatMir | |
try { | |
def hvacMode = remSenTstat ? remSenTstat?.currentThermostatMode?.toString() : null | |
def curCoolSetpoint = getTstatSetpoint(remSenTstat, "cool") | |
def curHeatSetpoint = getTstatSetpoint(remSenTstat, "heat") | |
def tempChangeVal = !remSenTstatTempChgVal ? 5.0 : Math.min(Math.max(remSenTstatTempChgVal.toDouble(), 2.0), 5.0) | |
def maxTempChangeVal = tempChangeVal * 3 | |
chgval = (chgval > (onTemp + maxTempChangeVal)) ? onTemp + maxTempChangeVal : chgval | |
chgval = (chgval < (offTemp - maxTempChangeVal)) ? offTemp - maxTempChangeVal : chgval | |
def t0 = "${tUnitStr()}" | |
if(chgval != curCoolSetpoint) { | |
scheduleAutomationEval(70) | |
def cHeat = null | |
if(hvacMode in ["auto"]) { | |
if(curHeatSetpoint >= (offTemp-tempChangeVal)) { | |
cHeat = offTemp - tempChangeVal | |
LogAction("Remote Sensor: HEAT - Adjusting HeatSetpoint to (${cHeat}${t0}) to allow COOL setting", "info", false) | |
if(remSenTstatMir) { remSenTstatMir*.setHeatingSetpoint(cHeat) } | |
} | |
} | |
if(setTstatAutoTemps(remSenTstat, chgval, cHeat, "remSen")) { | |
//LogAction("Remote Sensor: COOL - Adjusting CoolSetpoint to (${chgval}${t0}) ", "info", true) | |
//storeLastAction("Adjusted Cool Setpoint to (${chgval}${t0}) Heat Setpoint to (${cHeat}${t0})", getDtNow(), "remSen") | |
if(remSenTstatMir) { remSenTstatMir*.setCoolingSetpoint(chgval) } | |
} | |
return true // let all this take effect | |
} else { | |
LogAction("Remote Sensor: COOL - CoolSetpoint is already (${chgval}${t0}) ", "info", false) | |
} | |
} catch (ex) { | |
log.error "remSendoSetCool Exception: ${ex?.message}" | |
} | |
return false | |
} | |
def remSendoSetHeat(chgval, onTemp, offTemp) { | |
def remSenTstat = settings?.schMotTstat | |
def remSenTstatMir = settings?.schMotTstatMir | |
try { | |
def hvacMode = remSenTstat ? remSenTstat?.currentThermostatMode?.toString() : null | |
def curCoolSetpoint = getTstatSetpoint(remSenTstat, "cool") | |
def curHeatSetpoint = getTstatSetpoint(remSenTstat, "heat") | |
def tempChangeVal = !remSenTstatTempChgVal ? 5.0 : Math.min(Math.max(remSenTstatTempChgVal.toDouble(), 2.0), 5.0) | |
def maxTempChangeVal = tempChangeVal * 3 | |
chgval = (chgval < (onTemp - maxTempChangeVal)) ? onTemp - maxTempChangeVal : chgval | |
chgval = (chgval > (offTemp + maxTempChangeVal)) ? offTemp + maxTempChangeVal : chgval | |
def t0 = "${tUnitStr()}" | |
if(chgval != curHeatSetpoint) { | |
scheduleAutomationEval(70) | |
def cCool = null | |
if(hvacMode in ["auto"]) { | |
if(curCoolSetpoint <= (offTemp+tempChangeVal)) { | |
cCool = offTemp + tempChangeVal | |
LogAction("Remote Sensor: COOL - Adjusting CoolSetpoint to (${cCool}${t0}) to allow HEAT setting", "info", false) | |
if(remSenTstatMir) { remSenTstatMir*.setCoolingSetpoint(cCool) } | |
} | |
} | |
if(setTstatAutoTemps(remSenTstat, cCool, chgval, "remSen")) { | |
//LogAction("Remote Sensor: HEAT - Adjusting HeatSetpoint to (${chgval}${t0})", "info", false) | |
//storeLastAction("Adjusted Heat Setpoint to (${chgval}${t0}) Cool Setpoint to (${cCool}${t0})", getDtNow(), "remSen") | |
if(remSenTstatMir) { remSenTstatMir*.setHeatingSetpoint(chgval) } | |
} | |
return true // let all this take effect | |
} else { | |
LogAction("Remote Sensor: HEAT - HeatSetpoint is already (${chgval}${t0})", "info", false) | |
} | |
} catch (ex) { | |
log.error "remSendoSetHeat Exception: ${ex?.message}" | |
} | |
return false | |
} | |
/* | |
def getRemSenModeOk() { | |
def result = false | |
if(settings?.remSensorDay ) { result = true } | |
//log.debug "getRemSenModeOk: $result" | |
return result | |
} | |
*/ | |
private remSenCheck() { | |
LogTrace("remSenCheck") | |
if(getIsAutomationDisabled()) { return } | |
try { | |
def remSenTstat = settings?.schMotTstat | |
def remSenTstatMir = settings?.schMotTstatMir | |
long execTime = now() | |
String noGoDesc = "" | |
if( !settings?.remSensorDay || !remSenTstat) { | |
noGoDesc += !settings?.remSensorDay ? "Missing Required Sensor Selections" : "" | |
noGoDesc += !remSenTstat ? "Missing Required Thermostat device" : "" | |
LogTrace("Remote Sensor NOT Evaluating Status: ${noGoDesc}") | |
} else { | |
//log.info "remSenCheck: Evaluating Event" | |
def tempScaleStr = "${tUnitStr()}" | |
def hvacMode = remSenTstat ? remSenTstat?.currentThermostatMode?.toString() : null | |
if(hvacMode in [ "off", "eco"] ) { | |
LogAction("Remote Sensor: Skipping Evaluation; The Current Thermostat Mode is '${strCapitalize(hvacMode)}'", "info", false) | |
disableOverrideTemps() | |
storeExecutionHistory((now() - execTime), "remSenCheck") | |
return | |
} | |
def reqSenHeatSetPoint = getRemSenHeatSetTemp(hvacMode) | |
def reqSenCoolSetPoint = getRemSenCoolSetTemp(hvacMode) | |
def threshold = getRemoteSenThreshold() | |
if(hvacMode in ["auto"]) { | |
// check that requested setpoints make sense & notify | |
def coolheatDiff = Math.abs(reqSenCoolSetPoint - reqSenHeatSetPoint) | |
if( !((reqSenCoolSetPoint > reqSenHeatSetPoint) && (coolheatDiff >= 2)) ) { | |
LogAction("remSenCheck: Invalid Setpoints with auto mode: (${reqSenCoolSetPoint})/(${reqSenHeatSetPoint}, ${threshold})", "warn", true) | |
storeExecutionHistory((now() - execTime), "remSenCheck") | |
return | |
} | |
} | |
def tempChangeVal = !remSenTstatTempChgVal ? 5.0 : Math.min(Math.max(remSenTstatTempChgVal.toDouble(), 2.0), 5.0) | |
def maxTempChangeVal = tempChangeVal * 3 | |
def curTstatTemp = getDeviceTemp(remSenTstat).toDouble() | |
def curSenTemp = getRemoteSenTemp().toDouble() | |
def curTstatOperState = remSenTstat?.currentThermostatOperatingState.toString() | |
def curTstatFanMode = remSenTstat?.currentThermostatFanMode.toString() | |
def fanOn = (curTstatFanMode == "on" || curTstatFanMode == "circulate") ? true : false | |
def curCoolSetpoint = getTstatSetpoint(remSenTstat, "cool") | |
def curHeatSetpoint = getTstatSetpoint(remSenTstat, "heat") | |
def acRunning = (curTstatOperState == "cooling") ? true : false | |
def heatRunning = (curTstatOperState == "heating") ? true : false | |
/* | |
LogAction("remSenCheck: Rule Type: ${getEnumValue(remSenRuleEnum("heatcool"), settings?.remSenRuleType)}", "info", false) | |
LogAction("remSenCheck: Sensor Temp: ${curSenTemp}", "info", false) | |
LogAction("remSenCheck: Thermostat Info - ( Temperature: (${curTstatTemp}) | HeatSetpoint: (${curHeatSetpoint}) | CoolSetpoint: (${curCoolSetpoint}) | HvacMode: (${hvacMode}) | OperatingState: (${curTstatOperState}) | FanMode: (${curTstatFanMode}) )", "info", false) | |
LogAction("remSenCheck: Desired Temps - Heat: ${reqSenHeatSetPoint} | Cool: ${reqSenCoolSetPoint}", "info", false) | |
LogAction("remSenCheck: Threshold Temp: ${threshold} | Change Temp Increments: ${tempChangeVal}", "info", false) | |
*/ | |
def chg = false | |
def chgval = 0 | |
if(hvacMode in ["cool","auto"]) { | |
//Changes Cool Setpoints | |
if(settings?.remSenRuleType in ["Cool", "Heat_Cool", "Heat_Cool_Circ"]) { | |
def onTemp = reqSenCoolSetPoint + threshold | |
def offTemp = reqSenCoolSetPoint | |
def turnOn = false | |
def turnOff = false | |
LogTrace("Remote Sensor: COOL - (Sensor Temp: ${curSenTemp} - CoolSetpoint: ${reqSenCoolSetPoint})") | |
if(curSenTemp <= offTemp) { | |
turnOff = true | |
} else if(curSenTemp >= onTemp) { | |
turnOn = true | |
} | |
if(turnOff && acRunning) { | |
chgval = curTstatTemp + tempChangeVal | |
chg = true | |
LogAction("Remote Sensor: COOL - Adjusting CoolSetpoint to Turn Off Thermostat", "info", false) | |
acRunning = false | |
state?.remSenCoolOn = false | |
} else if(turnOn && !acRunning) { | |
chgval = curTstatTemp - tempChangeVal | |
chg = true | |
acRunning = true | |
state.remSenCoolOn = true | |
LogAction("Remote Sensor: COOL - Adjusting CoolSetpoint to Turn On Thermostat", "info", false) | |
} else { | |
// logic to decide if we need to nudge thermostat to keep it on or off | |
if(acRunning) { | |
chgval = curTstatTemp - tempChangeVal | |
state.remSenCoolOn = true | |
} else { | |
chgval = curTstatTemp + tempChangeVal | |
state?.remSenCoolOn = false | |
} | |
def coolDiff1 = Math.abs(curTstatTemp - curCoolSetpoint) | |
//LogAction("Remote Sensor: COOL - coolDiff1: ${coolDiff1} tempChangeVal: ${tempChangeVal}", "info", false) | |
if(coolDiff1 < (tempChangeVal / 2)) { | |
chg = true | |
LogAction("Remote Sensor: COOL - Adjusting CoolSetpoint to maintain state", "info", false) | |
} | |
} | |
if(chg) { | |
if(remSendoSetCool(chgval, onTemp, offTemp)) { | |
storeExecutionHistory((now() - execTime), "remSenCheck") | |
return // let all this take effect | |
} | |
} | |
//else { LogAction("Remote Sensor: NO CHANGE TO COOL - CoolSetpoint is (${curCoolSetpoint}${tempScaleStr}) ", "info", false) } | |
} | |
} | |
chg = false | |
chgval = 0 | |
//LogAction("remSenCheck: Thermostat Info - ( Temperature: (${curTstatTemp}) | HeatSetpoint: (${curHeatSetpoint}) | CoolSetpoint: (${curCoolSetpoint}) | HvacMode: (${hvacMode}) | OperatingState: (${curTstatOperState}) | FanMode: (${curTstatFanMode}) )", "info", false) | |
//Heat Functions. | |
if(hvacMode in ["heat", "emergency heat", "auto"]) { | |
if(settings?.remSenRuleType in ["Heat", "Heat_Cool", "Heat_Cool_Circ"]) { | |
def onTemp = reqSenHeatSetPoint - threshold | |
def offTemp = reqSenHeatSetPoint | |
def turnOn = false | |
def turnOff = false | |
//LogAction("Remote Sensor: HEAT - (Sensor Temp: ${curSenTemp} - HeatSetpoint: ${reqSenHeatSetPoint})", "info", false) | |
if(curSenTemp <= onTemp) { | |
turnOn = true | |
} else if(curSenTemp >= offTemp) { | |
turnOff = true | |
} | |
if(turnOff && heatRunning) { | |
chgval = curTstatTemp - tempChangeVal | |
chg = true | |
LogAction("Remote Sensor: HEAT - Adjusting HeatSetpoint to Turn Off Thermostat", "info", false) | |
heatRunning = false | |
state.remSenHeatOn = false | |
} else if(turnOn && !heatRunning) { | |
chgval = curTstatTemp + tempChangeVal | |
chg = true | |
LogAction("Remote Sensor: HEAT - Adjusting HeatSetpoint to Turn On Thermostat", "info", false) | |
state.remSenHeatOn = true | |
heatRunning = true | |
} else { | |
// logic to decide if we need to nudge thermostat to keep it on or off | |
if(heatRunning) { | |
chgval = curTstatTemp + tempChangeVal | |
state.remSenHeatOn = true | |
} else { | |
chgval = curTstatTemp - tempChangeVal | |
state.remSenHeatOn = false | |
} | |
def heatDiff1 = Math.abs(curTstatTemp - curHeatSetpoint) | |
//LogAction("Remote Sensor: HEAT - heatDiff1: ${heatDiff1} tempChangeVal: ${tempChangeVal}", "info", false) | |
if(heatDiff1 < (tempChangeVal / 2)) { | |
chg = true | |
LogAction("Remote Sensor: HEAT - Adjusting HeatSetpoint to maintain state", "info", false) | |
} | |
} | |
if(chg) { | |
if(remSendoSetHeat(chgval, onTemp, offTemp)) { | |
storeExecutionHistory((now() - execTime), "remSenCheck") | |
return // let all this take effect | |
} | |
} | |
//else { LogAction("Remote Sensor: NO CHANGE TO HEAT - HeatSetpoint is already (${curHeatSetpoint}${tempScaleStr})", "info", false) } | |
} | |
} | |
} | |
/* | |
// | |
// if all thermostats (primary and mirrors) are Nest, then AC/HEAT & fan may be off (or set back) with away mode. (depends on user's home/away assist settings in Nest) | |
// if thermostats were not all Nest, then non Nest units could still be on for AC/HEAT or FAN | |
// current presumption in this implementation is: | |
// they are all nests or integrated with Nest (Works with Nest) as we don't have away/home temps for each mirror thermostats. (They could be mirrored from primary) | |
// all thermostats in an automation are in the same Nest structure, so that all share home/away settings | |
// | |
*/ | |
storeExecutionHistory((now() - execTime), "remSenCheck") | |
} catch (ex) { | |
log.error "remSenCheck Exception: ${ex?.message}" | |
//parent?.sendExceptionData(ex, "remSenCheck", true, getAutoType()) | |
} | |
} | |
def getRemSenTempsToList() { | |
def mySched = getCurrentSchedule() | |
def sensors | |
if(mySched) { | |
def sLbl = "schMot_${mySched}_" | |
if(settings["${sLbl}remSensor"]) { | |
sensors = settings["${sLbl}remSensor"] | |
} | |
} | |
if(!sensors) { sensors = settings?.remSensorDay } | |
if(sensors?.size() >= 1) { | |
def t0 = "${tUnitStr()}" | |
def info = [] | |
sensors?.sort().each { | |
info.push("${it?.displayName}": " ${it?.currentTemperature.toString()}${t0}") | |
} | |
return info | |
} | |
} | |
def getTstatSetpoint(tstat, type) { | |
if(tstat) { | |
if(type == "cool") { | |
def coolSp = tstat?.currentCoolingSetpoint | |
//log.debug "getTstatSetpoint(cool): $coolSp" | |
return coolSp ? coolSp?.toDouble() : 0 | |
} else { | |
def heatSp = tstat?.currentHeatingSetpoint | |
//log.debug "getTstatSetpoint(heat): $heatSp" | |
return heatSp ? heatSp?.toDouble() : 0 | |
} | |
} | |
else { return 0 } | |
} | |
def getRemoteSenThreshold() { | |
def threshold = settings?.remSenTempDiffDegrees | |
def mySched = getCurrentSchedule() | |
if(mySched) { | |
def sLbl = "schMot_${mySched}_" | |
if(settings["${sLbl}remSenThreshold"]) { | |
threshold = settings["${sLbl}remSenThreshold"] | |
} | |
} | |
def theMin = getTemperatureScale() == "C" ? 0.3 : 0.6 | |
threshold = !threshold ? 2.0 : Math.min(Math.max(threshold.toDouble(),theMin), 4.0) | |
return threshold.toDouble() | |
} | |
def getRemoteSenTemp() { | |
def mySched = getCurrentSchedule() | |
if(state?.remoteTempSourceStr != null) { state.remoteTempSourceStr = null } | |
if(state?.currentSchedNum != null) { state.currentSchedNum = null } | |
def sens | |
if(mySched) { | |
String sLbl = "schMot_${mySched}_" | |
if(settings["${sLbl}remSensor"]) { | |
state.remoteTempSourceStr = "Schedule" | |
state.currentSchedNum = mySched | |
sens = settings["${sLbl}remSensor"] | |
return getDeviceTempAvg(sens).toDouble() | |
} | |
} | |
if(isRemSenConfigured()) { | |
state.remoteTempSourceStr = "Remote Sensor" | |
state.currentSchedNum = null | |
return getDeviceTempAvg(settings?.remSensorDay).toDouble() | |
} else { | |
state.remoteTempSourceStr = "Thermostat" | |
state.currentSchedNum = null | |
return getDeviceTemp(settings?.schMotTstat).toDouble() | |
/* | |
else { | |
LogAction("getRemoteSenTemp: No Temperature Found!", "warn", true) | |
return 0.0 | |
*/ | |
} | |
} | |
def fixTempSetting(temp) { | |
Double newtemp = temp?.toDouble() | |
if(temp != null) { | |
if(getTemperatureScale() == "C") { | |
if(temp > 35) { // setting was done in F | |
newtemp = roundTemp( ((newtemp - 32.0) * (5 / 9)) as Double) | |
} | |
} else if(getTemperatureScale() == "F") { | |
if(temp < 40) { // setting was done in C | |
newtemp = roundTemp( (((newtemp * (9 / 5)) as Double) + 32.0) ).toInteger() | |
} | |
} | |
} | |
return newtemp | |
} | |
def setRemoteSenTstat(val) { | |
LogAction("setRemoteSenTstat $val", "info", false) | |
state.remSenTstat = val | |
} | |
def getRemSenCoolSetTemp(curMode=null, isEco=false, useCurrent=true) { | |
Double coolTemp | |
def theMode = curMode != null ? curMode : null | |
if(theMode == null) { | |
def tstat = settings?.schMotTstat | |
theMode = tstat ? tstat?.currentThermostatMode.toString() : null | |
} | |
state.remoteCoolSetSourceStr = "" | |
if(theMode != "eco") { | |
if(getOverrideCoolSec() < (3600 * 4)) { | |
if(state?.remSenCoverride != null) { | |
coolTemp = fixTempSetting(state?.remSenCoverride.toDouble()) | |
state.remoteCoolSetSourceStr = "Remote Sensor Override" | |
} | |
} else { state?.remSenCoverride = null } | |
if(coolTemp == null) { | |
def mySched = getCurrentSchedule() | |
if(mySched) { | |
def useMotion = state?."motion${mySched}UseMotionSettings" | |
def hvacSettings = state?."sched${mySched}restrictions" | |
coolTemp = !useMotion ? hvacSettings?.ctemp : hvacSettings?.mctemp ?: hvacSettings?.ctemp | |
state.remoteCoolSetSourceStr = "Schedule" | |
} | |
// ERS if Remsensor is enabled | |
if(isRemSenConfigured()) { | |
if(theMode == "cool" && coolTemp == null /* && isEco */) { | |
if(state?.extTmpSavedTemp) { | |
coolTemp = state?.extTmpSavedTemp.toDouble() | |
state.remoteCoolSetSourceStr = "Last Desired Temp" | |
} | |
} | |
if(theMode == "auto" && coolTemp == null /* && isEco */) { | |
if(state?.extTmpSavedCTemp) { | |
coolTemp = state?.extTmpSavedCTemp.toDouble() | |
state.remoteCoolSetSourceStr = "Last Desired CTemp" | |
} | |
} | |
if(coolTemp == null && remSenDayCoolTemp) { | |
coolTemp = remSenDayCoolTemp.toDouble() | |
state.remoteCoolSetSourceStr = "RemSen Day Cool Temp" | |
} | |
if(coolTemp == null) { | |
def desiredCoolTemp = getGlobalDesiredCoolTemp() | |
if(desiredCoolTemp) { | |
coolTemp = desiredCoolTemp.toDouble() | |
state.remoteCoolSetSourceStr = "Global Desired Cool Temp" | |
} | |
} | |
if(coolTemp) { | |
coolTemp = fixTempSetting(coolTemp) | |
} | |
} | |
} | |
} | |
if(coolTemp == null && useCurrent) { | |
coolTemp = settings?.schMotTstat ? getTstatSetpoint(settings?.schMotTstat, "cool") : coolTemp | |
state.remoteCoolSetSourceStr = "Thermostat" | |
} | |
return coolTemp | |
} | |
def getRemSenHeatSetTemp(curMode=null, isEco=false, useCurrent=true) { | |
Double heatTemp | |
def theMode = curMode != null ? curMode : null | |
if(theMode == null) { | |
def tstat = settings?.schMotTstat | |
theMode = tstat ? tstat?.currentThermostatMode.toString() : null | |
} | |
state.remoteHeatSetSourceStr = "" | |
if(theMode != "eco") { | |
if(getOverrideHeatSec() < (3600 * 4)) { | |
if(state?.remSenHoverride != null) { | |
heatTemp = fixTempSetting(state.remSenHoverride.toDouble()) | |
state.remoteHeatSetSourceStr = "Remote Sensor Override" | |
} | |
} else { state?.remSenHoverride = null } | |
if(heatTemp == null) { | |
def mySched = getCurrentSchedule() | |
if(mySched) { | |
def useMotion = state?."motion${mySched}UseMotionSettings" | |
def hvacSettings = state?."sched${mySched}restrictions" | |
heatTemp = !useMotion ? hvacSettings?.htemp : hvacSettings?.mhtemp ?: hvacSettings?.htemp | |
state.remoteHeatSetSourceStr = "Schedule" | |
} | |
// ERS if Remsensor is enabled | |
if(isRemSenConfigured()) { | |
if(theMode == "heat" && heatTemp == null /* && isEco */) { | |
if(state?.extTmpSavedTemp) { | |
heatTemp = state?.extTmpSavedTemp.toDouble() | |
state.remoteHeatSetSourceStr = "Last Desired Temp" | |
} | |
} | |
if(theMode == "auto" && heatTemp == null /* && isEco */) { | |
if(state?.extTmpSavedHTemp) { | |
heatTemp = state?.extTmpSavedHTemp.toDouble() | |
state.remoteHeatSetSourceStr = "Last Desired HTemp" | |
} | |
} | |
if(heatTemp == null && remSenDayHeatTemp) { | |
heatTemp = remSenDayHeatTemp.toDouble() | |
state.remoteHeatSetSourceStr = "RemSen Day Heat Temp" | |
} | |
if(heatTemp == null) { | |
def desiredHeatTemp = getGlobalDesiredHeatTemp() | |
if(desiredHeatTemp) { | |
heatTemp = desiredHeatTemp.toDouble() | |
state.remoteHeatSetSourceStr = "Global Desired Heat Temp" | |
} | |
} | |
if(heatTemp) { | |
heatTemp = fixTempSetting(heatTemp) | |
} | |
} | |
} | |
} | |
if(heatTemp == null && useCurrent) { | |
heatTemp = settings?.schMotTstat ? getTstatSetpoint(settings?.schMotTstat, "heat") : heatTemp | |
state.remoteHeatSetSourceStr = "Thermostat" | |
} | |
return heatTemp | |
} | |
// When a temp change is sent to virtual device, it lasts for 4 hours, next turn off, or next schedule change, then we return to automation settings | |
// Other choices could be to change the schedule setpoint permanently if one is active, or allow folks to set timer | |
def getOverrideCoolSec() { return !state?.remSenCoverrideDt ? 100000 : GetTimeDiffSeconds(state?.remSenCoverrideDt, null, "getOverrideCoolSec").toInteger() } | |
def getOverrideHeatSec() { return !state?.remSenHoverrideDt ? 100000 : GetTimeDiffSeconds(state?.remSenHoverrideDt, null, "getOverrideHeatSec").toInteger() } | |
def disableOverrideTemps() { | |
if(state?.remSenHoverride || state?.remSenCoverride) { | |
stateRemove("remSenCoverride") | |
stateRemove("remSenHoverride") | |
stateRemove("remSenCoverrideDt") | |
stateRemove("remSenHoverrideDt") | |
LogAction("disableOverrideTemps: Disabling Override temps", "info", false) | |
} | |
} | |
def remSenTempUpdate(temp, mode) { | |
//LogAction("remSenTempUpdate(${temp}, ${mode})", "info", false) | |
def res = false | |
if(getIsAutomationDisabled()) { return res } | |
switch(mode) { | |
case "heat": | |
if(remSenHeatTempsReq()) { | |
//LogAction("remSenTempUpdate Set Heat Override to: ${temp} for 4 hours", "info", false) | |
state?.remSenHoverride = temp.toDouble() | |
state?.remSenHoverrideDt = getDtNow() | |
res = true | |
} | |
break | |
case "cool": | |
if(remSenCoolTempsReq()) { | |
//LogAction("remSenTempUpdate Set Cool Override to: ${temp} for 4 hours", "info", false) | |
state?.remSenCoverride = temp.toDouble() | |
state?.remSenCoverrideDt = getDtNow() | |
res = true | |
} | |
break | |
default: | |
LogAction("remSenTempUpdate Invalid Request: ${mode}, ${temp}", "warn", true) | |
break | |
} | |
if(res) { | |
scheduleAutomationEval() | |
LogAction("remSenTempUpdate Set ${mode} Override to: ${temp} for 4 hours", "info", false) | |
} | |
return res | |
} | |
def remSenRuleEnum(type=null) { | |
// Determines that available rules to display based on the selected thermostats capabilites. | |
def canCool = state?.schMotTstatCanCool ? true : false | |
def canHeat = state?.schMotTstatCanHeat ? true : false | |
def hasFan = state?.schMotTstatHasFan ? true : false | |
//log.debug "remSenRuleEnum -- hasFan: $hasFan (${state?.schMotTstatHasFan} | canCool: $canCool (${state?.schMotTstatCanCool} | canHeat: $canHeat (${state?.schMotTstatCanHeat}" | |
def vals = [] | |
if (type) { | |
if (type == "fan") { | |
vals = ["Circ":"Eco/Circulate(Fan)"] | |
if(canCool) { vals << ["Cool_Circ":"Cool/Circulate(Fan)"] } | |
if(canHeat) { vals << ["Heat_Circ":"Heat/Circulate(Fan)"] } | |
if(canHeat && canCool) { vals << [ "Heat_Cool_Circ":"Auto/Circulate(Fan)"] } | |
} | |
else if (type == "heatcool") { | |
if (!canCool && canHeat) { vals = ["Heat":"Heat"] } | |
else if (canCool && !canHeat) { vals = ["Cool":"Cool"] } | |
else { vals = ["Heat_Cool":"Auto", "Heat":"Heat", "Cool":"Cool"] } | |
} | |
else { LogAction("remSenRuleEnum: Invalid Type ($type)", "error", true) } | |
} | |
else { | |
if (canCool && !canHeat && hasFan) { vals = ["Cool":"Cool", "Circ":"Eco/Circulate(Fan)", "Cool_Circ":"Cool/Circulate(Fan)"] } | |
else if (canCool && !canHeat && !hasFan) { vals = ["Cool":"Cool"] } | |
else if (!canCool && canHeat && hasFan) { vals = ["Circ":"Eco/Circulate(Fan)", "Heat":"Heat", "Heat_Circ":"Heat/Circulate(Fan)"] } | |
else if (!canCool && canHeat && !hasFan) { vals = ["Heat":"Heat"] } | |
else if (!canCool && !canHeat && hasFan) { vals = ["Circ":"Eco/Circulate(Fan)"] } | |
else if (canCool && canHeat && !hasFan) { vals = ["Heat_Cool":"Auto", "Heat":"Heat", "Cool":"Cool"] } | |
else { vals = [ "Heat_Cool":"Auto", "Heat":"Heat", "Cool":"Cool", "Circ":"Eco/Circulate(Fan)", "Heat_Cool_Circ":"Auto/Circulate(Fan)", "Heat_Circ":"Heat/Circulate(Fan)", "Cool_Circ":"Cool/Circulate(Fan)" ] } | |
} | |
//log.debug "remSenRuleEnum vals: $vals" | |
return vals | |
} | |
/************************************************************************ | |
| FAN CONTROL AUTOMATION CODE | | |
*************************************************************************/ | |
String fanCtrlPrefix() { return "fanCtrl" } | |
boolean isFanCtrlConfigured() { | |
return ( settings?.schMotOperateFan && (isFanCtrlSwConfigured() || isFanCircConfigured())) ? true : false | |
} | |
boolean isFanCtrlSwConfigured() { | |
return (settings?.schMotOperateFan && settings?.fanCtrlFanSwitches && settings?.fanCtrlFanSwitchTriggerType && settings?.fanCtrlFanSwitchHvacModeFilter) ? true : false | |
} | |
boolean isFanCircConfigured() { | |
return (settings?.schMotOperateFan && (settings?.schMotCirculateTstatFan || settings?.schMotCirculateExtFan) && settings?.schMotFanRuleType) ? true : false | |
} | |
String getFanSwitchDesc(boolean showOpt = true) { | |
String swDesc = "" | |
int swCnt = 0 | |
String pName = fanCtrlPrefix() | |
if(showOpt) { | |
swDesc += (settings?."${pName}FanSwitches" && (settings?."${pName}FanSwitchSpeedCtrl" || settings?."${pName}FanSwitchTriggerType" || settings?."${pName}FanSwitchHvacModeFilter")) ? "Fan Switch Config:" : "" | |
} | |
swDesc += settings?."${pName}FanSwitches" ? "${showOpt ? "\n" : ""}• Fan Switches:" : "" | |
int rmSwCnt = settings?."${pName}FanSwitches"?.size() ?: 0 | |
settings?."${pName}FanSwitches"?.sort { it?.displayName }?.each { sw -> | |
swCnt = swCnt+1 | |
swDesc += "${swCnt >= 1 ? "${swCnt == rmSwCnt ? "\n └" : "\n ├"}" : "\n └"} ${sw?.label}: (${strCapitalize(sw?.currentSwitch)})" | |
swDesc += checkFanSpeedSupport(sw) ? "\n └ Current Spd: (${sw?.currentSpeed?.toString()})" : "" | |
} | |
if(showOpt) { | |
if (settings?."${pName}FanSwitches") { | |
swDesc += (settings?."${pName}FanSwitchSpeedCtrl" || settings?."${pName}FanSwitchTriggerType" || settings?."${pName}FanSwitchHvacModeFilter") ? "\n\nFan Triggers:" : "" | |
swDesc += (settings?."${pName}FanSwitchSpeedCtrl") ? "\n • Fan Speed Support: (Active)" : "" | |
swDesc += (settings?."${pName}FanSwitchTriggerType") ? "\n • Fan Trigger:\n └(${getEnumValue(switchRunEnum(), settings?."${pName}FanSwitchTriggerType")})" : "" | |
swDesc += (settings?."${pName}FanSwitchHvacModeFilter") ? "\n • Hvac Mode Filter:\n └(${getEnumValue(fanModeTrigEnum(), settings?."${pName}FanSwitchHvacModeFilter")})" : "" | |
} | |
} | |
boolean t0 = isFanCircConfigured() | |
swDesc += (t0) ? "\n\nFan Circulation Enabled:" : "" | |
swDesc += (t0) ? "\n • Fan Circulation Rule:\n └(${getEnumValue(remSenRuleEnum("fan"), settings?.schMotFanRuleType)})" : "" | |
swDesc += (t0 && settings?.fanCtrlTempDiffDegrees) ? ("\n • Threshold: (${settings?.fanCtrlTempDiffDegrees}${tUnitStr()})") : "" | |
swDesc += (t0 && settings?.fanCtrlOnTime) ? ("\n • Circulate Time: (${getEnumValue(fanTimeSecEnum(), settings?.fanCtrlOnTime)})") : "" | |
swDesc += (t0 && settings?.fanCtrlTimeBetweenRuns) ? ("\n • Time Between Cycles:\n └ (${getEnumValue(longTimeSecEnum(), settings?.fanCtrlTimeBetweenRuns)})") : "" | |
swDesc += (settings?."${pName}FanSwitches" || t0) ? "\n\nRestrictions Active: (${autoScheduleOk(fanCtrlPrefix()) ? "No" : "Yes"})" : "" | |
return (swDesc == "") ? null : "${swDesc}" | |
} | |
boolean getFanSwitchesSpdChk() { | |
int devCnt = 0 | |
String pName = fanCtrlPrefix() | |
if(settings?."${pName}FanSwitches") { | |
settings?."${pName}FanSwitches"?.each { sw -> | |
if(checkFanSpeedSupport(sw)) { devCnt = devCnt+1 } | |
} | |
} | |
return (devCnt >= 1) ? true : false | |
} | |
boolean fanCtrlScheduleOk() { return autoScheduleOk(fanCtrlPrefix()) } | |
def fanCtrlCheck() { | |
//LogAction("FanControl Event | Fan Switch Check", "info", false) | |
try { | |
def fanCtrlTstat = settings?.schMotTstat | |
if(getIsAutomationDisabled()) { return } | |
if( !isFanCtrlConfigured()) { return } | |
long execTime = now() | |
//state?.autoRunDt = getDtNow() | |
String curMode = settings?.schMotTstat ? settings?.schMotTstat?.currentThermostatMode?.toString() : null | |
boolean modeEco = (curMode in ["eco"]) ? true : false | |
def reqHeatSetPoint | |
def reqCoolSetPoint | |
if(!modeEco) { | |
reqHeatSetPoint = getRemSenHeatSetTemp(curMode) | |
reqCoolSetPoint = getRemSenCoolSetTemp(curMode) | |
} | |
def lastMode = settings?.schMotTstat ? settings?.schMotTstat?.currentpreviousthermostatMode?.toString() : null | |
if(!lastMode && modeEco && isRemSenConfigured()) { | |
if( /* !lastMode && */ state?.extTmpTstatOffRequested && state?.extTmplastMode) { | |
lastMode = state?.extTmplastMode | |
} | |
} | |
if(lastMode) { | |
if(!reqHeatSetpoint) { reqHeatSetPoint = getRemSenHeatSetTemp(lastMode, modeEco, false) } | |
if(!reqCoolSetpoint) { reqCoolSetPoint = getRemSenCoolSetTemp(lastMode, modeEco, false) } | |
if(isRemSenConfigured()) { | |
if(!reqHeatSetPoint) { reqHeatSetPoint = state?.extTmpSavedHTemp } | |
if(!reqCoolSetPoint) { reqCoolSetPoint = state?.extTmpSavedCTemp } | |
} | |
LogAction("fanCtrlCheck: Using lastMode: ${lastMode} | extTmpTstatOffRequested: ${state?.extTmpTstatOffRequested} | curMode: ${curMode}", "info", false) | |
} | |
reqHeatSetPoint = reqHeatSetPoint ?: 0 | |
reqCoolSetPoint = reqCoolSetPoint ?: 0 | |
def curTstatTemp = getRemoteSenTemp().toDouble() | |
def t0 = getReqSetpointTemp(curTstatTemp, reqHeatSetPoint, reqCoolSetPoint).req | |
def curSetPoint = t0 ? t0.toDouble() : 0 | |
def tempDiff = Math.abs(curSetPoint - curTstatTemp) | |
LogAction("fanCtrlCheck: Desired Temps - Heat: ${reqHeatSetPoint} | Cool: ${reqCoolSetPoint}", "info", false) | |
LogAction("fanCtrlCheck: Current Thermostat Sensor Temp: ${curTstatTemp} Temp Difference: (${tempDiff})", "info", false) | |
def circWantsOn = null | |
if(isFanCircConfigured()) { | |
def adjust = (getTemperatureScale() == "C") ? 0.5 : 1.0 | |
def threshold = !settings?.fanCtrlTempDiffDegrees ? adjust : settings?.fanCtrlTempDiffDegrees.toDouble() | |
def hvacMode = curMode | |
/* | |
def curTstatFanMode = settings?.schMotTstat?.currentThermostatFanMode.toString() | |
def fanOn = (curTstatFanMode == "on" || curTstatFanMode == "circulate") ? true : false | |
if(state?.haveRunFan) { | |
if(schMotFanRuleType in ["Circ", "Cool_Circ", "Heat_Circ", "Heat_Cool_Circ"]) { | |
if(fanOn) { | |
LogAction("fantCtrlCheck: Turning OFF '${settings?.schMotTstat?.displayName}' Fan; Modes do not match evaluation", "info", false) | |
storeLastAction("Turned ${settings?.schMotTstat} Fan to (Auto)", getDtNow(), "fanCtrl", settings?.schMotTstat) | |
settings?.schMotTstat?.fanAuto() | |
if(settings?.schMotTstatMir) { settings?.schMotTstatMir*.fanAuto() } | |
} | |
} | |
state.haveRunFan = false | |
} | |
*/ | |
def sTemp = getReqSetpointTemp(curTstatTemp, reqHeatSetPoint, reqCoolSetPoint) | |
def resultMode = sTemp?.type?.toString() | |
def can_Circ = false | |
if( | |
!(hvacMode in ["off"]) && ( | |
( hvacMode in ["cool"] && schMotFanRuleType in ["Cool_Circ"]) || | |
( resultMode in ["cool"] && schMotFanRuleType in ["Cool_Circ", "Heat_Cool_Circ"]) || | |
( hvacMode in ["heat"] && schMotFanRuleType in ["Heat_Circ"]) || | |
( resultMode in ["heat"] && schMotFanRuleType in ["Heat_Circ", "Heat_Cool_Circ"]) || | |
( hvacMode in ["auto"] && schMotFanRuleType in ["Heat_Cool_Circ"]) || | |
( hvacMode in ["eco"] && schMotFanRuleType in ["Circ"]) | |
) | |
) { | |
can_Circ = true | |
} | |
circWantsOn = circulateFanControl(resultMode, curTstatTemp, sTemp?.req?.toDouble(), threshold, can_Circ) | |
} | |
if(isFanCtrlSwConfigured()) { | |
doFanOperation(tempDiff, curTstatTemp, reqHeatSetPoint, reqCoolSetPoint, circWantsOn) | |
} | |
storeExecutionHistory((now()-execTime), "fanCtrlCheck") | |
} catch (ex) { | |
log.error "fanCtrlCheck Exception: ${ex?.message}" | |
//parent?.sendExceptionData(ex, "fanCtrlCheck", true, getAutoType()) | |
} | |
} | |
def getReqSetpointTemp(curTemp, reqHeatSetPoint, reqCoolSetPoint) { | |
//LogAction("getReqSetpointTemp: Current Temp: ${curTemp} Req Heat: ${reqHeatSetPoint} Req Cool: ${reqCoolSetPoint}", "info", false) | |
def tstat = settings?.schMotTstat | |
//def modeEco = (curMode == "eco") ? true : false | |
//def modeAuto = (curMode == "auto") ? true : false | |
def canHeat = state?.schMotTstatCanHeat | |
def canCool = state?.schMotTstatCanCool | |
String hvacMode = tstat ? tstat?.currentThermostatMode.toString() : null | |
String operState = tstat ? tstat?.currentThermostatOperatingState.toString() : null | |
String opType = hvacMode.toString() | |
if(hvacMode == "off") { | |
return ["req":null, "type":"off"] | |
} | |
if((hvacMode == "cool") || (operState == "cooling") || (hvacMode == "eco" && !canHeat && canCool) ) { | |
opType = "cool" | |
} else if((hvacMode == "heat") || (operState == "heating")|| (hvacMode == "eco" && !canCool && canHeat) ) { | |
opType = "heat" | |
} else if(hvacMode == "auto" || hvacMode == "eco") { | |
def coolDiff = Math.abs(curTemp - reqCoolSetPoint) | |
def heatDiff = Math.abs(curTemp - reqHeatSetPoint) | |
opType = coolDiff < heatDiff ? "cool" : "heat" | |
} | |
def temp = (opType == "cool") ? reqCoolSetPoint?.toDouble() : reqHeatSetPoint?.toDouble() | |
return ["req":temp, "type":opType] | |
} | |
def doFanOperation(tempDiff, curTstatTemp, curHeatSetpoint, curCoolSetpoint, circWantsOn) { | |
String pName = fanCtrlPrefix() | |
//LogAction("doFanOperation: Temp Difference: (${tempDiff})", "info", false) | |
try { | |
def tstat = settings?.schMotTstat | |
/* def curTstatTemp = tstat ? getRemoteSenTemp().toDouble() : null | |
def curCoolSetpoint = getRemSenCoolSetTemp() | |
def curHeatSetpoint = getRemSenHeatSetTemp() | |
*/ | |
def hvacMode = tstat ? tstat?.currentThermostatMode.toString() : null | |
def curTstatOperState = tstat?.currentThermostatOperatingState.toString() | |
def curTstatFanMode = tstat?.currentThermostatFanMode.toString() | |
//LogAction("doFanOperation: Thermostat Info - ( Temperature: (${curTstatTemp}) | HeatSetpoint: (${curHeatSetpoint}) | CoolSetpoint: (${curCoolSetpoint}) | HvacMode: (${hvacMode}) | OperatingState: (${curTstatOperState}) | FanMode: (${curTstatFanMode}) )", "info", false) | |
if(state?.haveRunFan == null) { state.haveRunFan = false } | |
def savedHaveRun = state.haveRunFan | |
//def wantFanOn = circWantsOn != null ? circWantsOn ? false | |
def wantFanOn = false | |
// 1:"Heating/Cooling", 2:"With Fan Only", 3:"Heating", 4:"Cooling" | |
def validOperModes = [] | |
switch ( settings?."${pName}FanSwitchTriggerType".toInteger() ) { | |
case 1: | |
validOperModes = ["heating", "cooling"] | |
wantFanOn = (curTstatOperState in validOperModes) ? true : false | |
break | |
case 2: | |
wantFanOn = (curTstatFanMode in ["on", "circulate"]) ? true : false | |
break | |
case 3: | |
validOperModes = ["heating"] | |
wantFanOn = (curTstatOperState in validOperModes) ? true : false | |
break | |
case 4: | |
validOperModes = ["cooling"] | |
wantFanOn = (curTstatOperState in validOperModes) ? true : false | |
break | |
default: | |
break | |
} | |
/* | |
if( settings?."${pName}FanSwitchTriggerType".toInteger() == 1) { | |
def validOperModes = ["heating", "cooling"] | |
wantFanOn = (curTstatOperState in ["heating", "cooling"]) ? true : false | |
} | |
if( settings?."${pName}FanSwitchTriggerType".toInteger() == 2) { | |
wantFanOn = (curTstatFanMode in ["on", "circulate"]) ? true : false | |
} | |
//if(settings?."${pName}FanSwitchHvacModeFilter" != "any" && (settings?."${pName}FanSwitchHvacModeFilter" != hvacMode)) { | |
*/ | |
if( !( ("any" in settings?."${pName}FanSwitchHvacModeFilter") || (hvacMode in settings?."${pName}FanSwitchHvacModeFilter") ) ) { | |
if(savedHaveRun) { | |
LogAction("doFanOperation: Evaluating turn fans off; Thermostat Mode does not Match the required Mode", "info", false) | |
} | |
wantFanOn = false // force off of fans | |
} | |
boolean schedOk = fanCtrlScheduleOk() | |
if(!schedOk) { | |
if(savedHaveRun) { | |
LogAction("doFanOperation: Evaluating turn fans off; Schedule is restricted", "info", false) | |
} | |
wantFanOn = false // force off of fans | |
circWantsOn = false // force off of fans | |
} | |
def allOff = true | |
settings?."${pName}FanSwitches"?.each { sw -> | |
def swOn = (sw?.currentSwitch.toString() == "on") ? true : false | |
if(wantFanOn || circWantsOn) { | |
if(!swOn && !savedHaveRun) { | |
LogAction("doFanOperation: Fan Switch (${sw?.displayName}) is (${swOn ? "ON" : "OFF"}) | Turning '${sw}' Switch (ON)", "info", false) | |
sw.on() | |
swOn = true | |
state.haveRunFan = true | |
storeLastAction("Turned On $sw)", getDtNow(), pName) | |
} else { | |
if(!swOn && savedHaveRun) { | |
LogAction("doFanOperation: savedHaveRun state shows switch ${sw} turned OFF outside of automation requests", "info", false) | |
} | |
} | |
if(swOn && state?.haveRunFan && checkFanSpeedSupport(sw)) { | |
def t0 = sw?.currentSpeed | |
def speed = t0 ?: null | |
if(settings?."${pName}FanSwitchSpeedCtrl" && settings?."${pName}FanSwitchHighSpeed" && settings?."${pName}FanSwitchMedSpeed" && settings?."${pName}FanSwitchLowSpeed") { | |
if(tempDiff < settings?."${pName}FanSwitchMedSpeed".toDouble()) { | |
if(speed != "low") { | |
sw.setSpeed("low") | |
LogAction("doFanOperation: Temp Difference (${tempDiff}${tUnitStr()}) is BELOW the Medium Speed Threshold of (${settings?."${pName}FanSwitchMedSpeed"}) | Turning '${sw}' Fan Switch on (LOW SPEED)", "info", false) | |
storeLastAction("Set Fan $sw to Low Speed", getDtNow(), pName) | |
} | |
} | |
else if(tempDiff >= settings?."${pName}FanSwitchMedSpeed".toDouble() && tempDiff < settings?."${pName}FanSwitchHighSpeed".toDouble()) { | |
if(speed != "medium") { | |
sw.setSpeed("medium") | |
LogAction("doFanOperation: Temp Difference (${tempDiff}${tUnitStr()}) is ABOVE the Medium Speed Threshold of (${settings?."${pName}FanSwitchMedSpeed"}) | Turning '${sw}' Fan Switch on (MEDIUM SPEED)", "info", false) | |
storeLastAction("Set Fan $sw to Medium Speed", getDtNow(), pName) | |
} | |
} | |
else if(tempDiff >= settings?."${pName}FanSwitchHighSpeed".toDouble()) { | |
if(speed != "high") { | |
sw.setSpeed("high") | |
LogAction("doFanOperation: Temp Difference (${tempDiff}${tUnitStr()}) is ABOVE the High Speed Threshold of (${settings?."${pName}FanSwitchHighSpeed"}) | Turning '${sw}' Fan Switch on (HIGH SPEED)", "info", false) | |
storeLastAction("Set Fan $sw to High Speed", getDtNow(), pName) | |
} | |
} | |
} else { | |
if(speed != "high") { | |
sw.setSpeed("high") | |
LogAction("doFanOperation: Fan supports multiple speeds, with speed control disabled | Turning '${sw}' Fan Switch on (HIGH SPEED)", "info", false) | |
storeLastAction("Set Fan $sw to High Speed", getDtNow(), pName) | |
} | |
} | |
} | |
} else { | |
if(swOn && savedHaveRun && !wantfanOn) { | |
LogAction("doFanOperation: Fan Switch (${sw?.displayName}) is (${swOn ? "ON" : "OFF"}) | Turning '${sw}' Switch (OFF)", "info", false) | |
storeLastAction("Turned Off (${sw})", getDtNow(), pName) | |
swOn = false | |
sw.off() | |
state.haveRunFan = false | |
} else { | |
if(swOn && !savedHaveRun) { | |
LogAction("doFanOperation: Saved have run state shows switch ${sw} turned ON outside of automation requests", "info", false) | |
} | |
} | |
} | |
if(swOn) { allOff = false } | |
} | |
if(allOff) { state.haveRunFan = false } | |
} catch (ex) { | |
log.error "doFanOperation Exception: ${ex?.message}" | |
//parent?.sendExceptionData(ex, "doFanOperation", true, getAutoType()) | |
} | |
} | |
def getFanCtrlFanRunDtSec() { return !state?.fanCtrlRunDt ? 100000 : GetTimeDiffSeconds(state?.fanCtrlRunDt, null, "getFanCtrlFanRunDtSec").toInteger() } | |
def getFanCtrlFanOffDtSec() { return !state?.fanCtrlFanOffDt ? 100000 : GetTimeDiffSeconds(state?.fanCtrlFanOffDt, null, "getFanCtrlFanOffDtSec").toInteger() } | |
// CONTROLS THE THERMOSTAT FAN | |
def circulateFanControl(operType, Double curSenTemp, Double reqSetpointTemp, Double threshold, can_Circ) { | |
String pName = fanCtrlPrefix() | |
def tstat = settings?.schMotTstat | |
def tstatsMir = settings?.schMotTstatMir | |
// input (name: "schMotCirculateTstatFan", type: "bool", title: imgTitle(getAppImg("fan_circulation_icon.png"), inputTitleStr("Run HVAC Fan for Circulation?")), description: desc, required: reqinp, defaultValue: false, submitOnChange: true) | |
// input (name: "schMotCirculateExtFan", type: "bool", title: imgTitle(getAppImg("fan_circulation_icon.png"), inputTitleStr("Run External Fan for Circulation?")), description: desc, required: reqinp, defaultValue: false, submitOnChange: true) | |
//ERS TODO Operate external fan | |
def theFanIsOn = false | |
def hvacMode = tstat ? tstat?.currentThermostatMode.toString() : null | |
def curTstatFanMode = tstat?.currentThermostatFanMode.toString() | |
def fanOn = (curTstatFanMode == "on" || curTstatFanMode == "circulate") ? true : false | |
def returnToAuto = can_Circ ? false : true | |
if(hvacMode in ["off"]) { returnToAuto = true } | |
// Track approximate fan on / off times | |
if( !fanOn && state?.fanCtrlRunDt > state?.fanCtrlFanOffDt ) { | |
state?.fanCtrlFanOffDt = getDtNow() | |
returnToAuto = true | |
} | |
if( fanOn && state?.fanCtrlRunDt < state?.fanCtrlFanOffDt ) { | |
state?.lastfanCtrlFanRunDt = getDtNow() | |
} | |
boolean schedOk = fanCtrlScheduleOk() | |
if(!schedOk) { | |
returnToAuto = true | |
} | |
def curOperState = tstat?.currentnestThermostatOperatingState.toString() | |
def tstatOperStateOk = (curOperState == "idle") ? true : false | |
// if ac or heat is on, we should put fan back to auto | |
if(!tstatOperStateOk) { | |
if( state?.fanCtrlFanOffDt > state?.fanCtrlRunDt) { return false } | |
LogAction("Circulate Fan Run: The Thermostat OperatingState is Currently (${strCapitalize(curOperState)}) Skipping", "info", false) | |
state?.fanCtrlFanOffDt = getDtNow() | |
returnToAuto = true | |
} | |
def fanTempOk = getCirculateFanTempOk(curSenTemp, reqSetpointTemp, threshold, fanOn, operType) | |
if(hvacMode in ["heat", "auto", "cool", "eco"] && fanTempOk && !returnToAuto) { | |
if(!fanOn) { | |
def waitTimeVal = fanCtrlTimeBetweenRuns?.toInteger() ?: 1200 | |
def timeSinceLastOffOk = (getFanCtrlFanOffDtSec() > waitTimeVal) ? true : false | |
if(!timeSinceLastOffOk) { | |
def remaining = waitTimeVal - getFanCtrlFanOffDtSec() | |
LogAction("Circulate Fan: Want to RUN Fan | Delaying for wait period ${waitTimeVal}, remaining ${remaining} seconds", "info", false) | |
def val = Math.min( Math.max(remaining,defaultAutomationTime()), 60) | |
scheduleAutomationEval(val) | |
return false // leave off | |
} | |
LogAction("Circulate Fan: Activating '${tstat?.displayName}'' Fan for ${strCapitalize(operType)}ING Circulation", "info", false) | |
tstat?.fanOn() | |
storeLastAction("Turned ${tstat} Fan 'On'", getDtNow(), pName, tstat) | |
state?.fanCtrlRunDt = getDtNow() | |
if(tstatsMir) { | |
tstatsMir?.each { mt -> | |
LogAction("Circulate Fan: Mirroring Primary Thermostat: Activating '${mt?.displayName}' Fan", "info", false) | |
mt?.fanOn() | |
storeLastAction("Turned ${mt.displayName} Fan 'On'", getDtNow(), pName, mt) | |
} | |
} | |
} | |
theFanIsOn = true | |
} else { | |
if(returnToAuto || !fanTempOk) { | |
if(fanOn && !returnToAuto) { | |
def fanOnTimeVal = fanCtrlOnTime?.toInteger() ?: 240 | |
def timeSinceLastRunOk = (getFanCtrlFanRunDtSec() > fanOnTimeVal) ? true : false // fan left on for minimum | |
if(!timeSinceLastRunOk) { | |
def remaining = fanOnTimeVal - getFanCtrlFanRunDtSec() | |
LogAction("Circulate Fan Run: Want to STOP Fan | Delaying for run period ${fanOnTimeVal}, remaining ${remaining} seconds", "info", false) | |
def val = Math.min( Math.max(remaining,defaultAutomationTime()), 60) | |
scheduleAutomationEval(val) | |
return true // leave on | |
} | |
} | |
if(fanOn) { | |
LogAction("Circulate Fan: Turning OFF '${tstat?.displayName}' Fan that was used for ${strCapitalize(operType)}ING Circulation", "info", false) | |
tstat?.fanAuto() | |
storeLastAction("Turned ${tstat} Fan to 'Auto'", getDtNow(), pName, tstat) | |
state?.fanCtrlFanOffDt = getDtNow() | |
if(tstatsMir) { | |
tstatsMir?.each { mt -> | |
LogAction("Circulate Fan: Mirroring Primary Thermostat: Turning OFF '${mt?.displayName}' Fan", "info", false) | |
mt?.fanAuto() | |
storeLastAction("Turned ${mt.displayName} Fan 'Off'", getDtNow(), pName, mt) | |
} | |
} | |
} | |
} | |
theFanIsOn = false | |
} | |
if(theFanIsOn) { | |
scheduleAutomationEval(120) | |
} | |
return theFanIsOn | |
} | |
def getCirculateFanTempOk(Double senTemp, Double reqsetTemp, Double threshold, Boolean fanOn, operType) { | |
def turnOn = false | |
def tempScaleStr = "${tUnitStr()}" | |
/* | |
def adjust = (getTemperatureScale() == "C") ? 0.5 : 1.0 | |
if(threshold > (adjust * 2.0)) { | |
adjust = adjust * 2.0 | |
} | |
if(adjust >= threshold) { | |
LogAction("getCirculateFanTempOk: Bad threshold setting ${threshold} <= ${adjust}", "warn", true) | |
return false | |
} | |
LogAction(" ├ adjust: ${adjust}}${tUnitStr()}", "info", false) | |
*/ | |
//LogAction(" ├ operType: (${strCapitalize(operType)}) | Temp Threshold: ${threshold}${tempScaleStr} | FanAlreadyOn: (${strCapitalize(fanOn)})", "info", false) | |
//LogAction(" ├ Sensor Temp: ${senTemp}${tempScaleStr} | Requested Setpoint Temp: ${reqsetTemp}${tempScaleStr}", "info", false) | |
if(!reqsetTemp) { | |
//LogAction("getCirculateFanTempOk: Bad reqsetTemp ${reqsetTemp}", "warn", true) | |
//LogAction("getCirculateFanTempOk:", "info", false) | |
return false | |
} | |
// def ontemp | |
def offtemp | |
if(operType == "cool") { | |
// ontemp = reqsetTemp + threshold | |
offtemp = reqsetTemp | |
if(senTemp >= (offtemp + threshold)) { turnOn = true } | |
// if((senTemp > offtemp) && (senTemp <= (ontemp - adjust))) { turnOn = true } | |
} | |
if(operType == "heat") { | |
// ontemp = reqsetTemp - threshold | |
offtemp = reqsetTemp | |
if(senTemp <= (offtemp - threshold)) { turnOn = true } | |
// if((senTemp < offtemp) && (senTemp >= (ontemp + adjust))) { turnOn = true } | |
} | |
// LogAction(" ├ onTemp: ${ontemp} | offTemp: ${offtemp}}${tempScaleStr}", "info", false) | |
//LogAction(" ├ offTemp: ${offtemp}${tempScaleStr} | Temp Threshold: ${threshold}${tempScaleStr}", "info", false) | |
//LogAction(" ┌ Final Result: (${strCapitalize(turnOn)})", "info", false) | |
// LogAction("getCirculateFanTempOk: ", "info", false) | |
def resultStr = "getCirculateFanTempOk: The Temperature Difference is " | |
if(turnOn) { | |
resultStr += " within " | |
} else { | |
resultStr += " Outside " | |
} | |
def disp = false | |
resultStr += "of Threshold Limits | " | |
if(!turnOn && fanOn) { | |
resultStr += "Turning Thermostat Fan OFF" | |
disp = true | |
} else if(turnOn && !fanOn) { | |
resultStr += "Turning Thermostat Fan ON" | |
disp = true | |
} else if(turnOn && fanOn) { | |
resultStr += "Fan is ON" | |
} else if(!turnOn && !fanOn) { | |
resultStr += "Fan is OFF" | |
} | |
LogAction("${resultStr}", "info", disp) | |
return turnOn | |
} | |
/******************************************************************************** | |
| HUMIDITY CONTROL AUTOMATION CODE | | |
*********************************************************************************/ | |
String humCtrlPrefix() { return "humCtrl" } | |
boolean isHumCtrlConfigured() { | |
return (settings?.schMotHumidityControl && (settings?.humCtrlUseWeather || settings?.humCtrlTempSensor) && settings?.humCtrlHumidity && settings?.humCtrlSwitches) ? true : false | |
} | |
String humCtrlSwitchDesc(showOpt = true) { | |
if(settings?.humCtrlSwitches) { | |
int cCnt = settings?.humCtrlSwitches?.size() ?: 0 | |
String str = "" | |
int cnt = 0 | |
str += "Switch Status:" | |
settings?.humCtrlSwitches?.sort { it?.displayName }?.each { dev -> | |
cnt = cnt+1 | |
def val = strCapitalize(dev?.currentSwitch) ?: "Not Set" | |
str += "${(cnt >= 1) ? "${(cnt == cCnt) ? "\n└" : "\n├"}" : "\n└"} ${dev?.label}: (${val})" | |
} | |
if(showOpt) { | |
str += (settings?.humCtrlSwitchTriggerType || settings?.humCtrlSwitchHvacModeFilter) ? "\n\nSwitch Triggers:" : "" | |
str += (settings?.humCtrlSwitchTriggerType) ? "\n • Switch Trigger: (${getEnumValue(switchRunEnum(true), settings?.humCtrlSwitchTriggerType)})" : "" | |
str += (settings?.humCtrlSwitchHvacModeFilter) ? "\n • Hvac Mode Filter: (${getEnumValue(fanModeTrigEnum(), settings?.humCtrlSwitchHvacModeFilter).toString().replaceAll("\\[|\\]", "")})" : "" | |
} | |
return str | |
} | |
return null | |
} | |
String humCtrlHumidityDesc() { | |
if(settings?.humCtrlHumidity) { | |
int cCnt = settings?.humCtrlHumidity?.size() ?: 0 | |
String str = "" | |
int cnt = 0 | |
str += "Sensor Humidity (average): (${getDeviceVarAvg(settings.humCtrlHumidity, "currentHumidity")}%)" | |
settings?.humCtrlHumidity?.sort { it?.displayName }?.each { dev -> | |
cnt = cnt+1 | |
String t0 = strCapitalize(dev?.currentHumidity) | |
String val = t0 ?: "Not Set" | |
str += "${(cnt >= 1) ? "${(cnt == cCnt) ? "\n└" : "\n├"}" : "\n└"} ${dev?.label}: ${(dev?.label?.toString()?.length() > 10) ? "\n${(cCnt == 1 || cnt == cCnt) ? " " : "│"}└ " : ""}(${val}%)" | |
} | |
return str | |
} | |
return null | |
} | |
def getHumCtrlTemperature() { | |
def extTemp = 0.0 | |
if(!settings?.humCtrlUseWeather && settings?.humCtrlTempSensor) { | |
extTemp = getDeviceTemp(settings?.humCtrlTempSensor) | |
} else { | |
if(settings?.humCtrlUseWeather && (state?.curWeaTemp_f || state?.curWeaTemp_c)) { | |
if(getTemperatureScale() == "C") { extTemp = state?.curWeaTemp_c.toDouble() } | |
else { extTemp = state?.curWeaTemp_f.toDouble() } | |
} | |
} | |
return extTemp | |
} | |
int getMaxHumidity(curExtTemp) { | |
int maxhum = 15 | |
if(curExtTemp != null) { | |
if(curExtTemp >= adj_temp(40)) { | |
maxhum = 45 | |
} else if(curExtTemp >= adj_temp(32)) { | |
maxhum = 45 - ( (adj_temp(40) - curExtTemp)/(adj_temp(40)-adj_temp(32)) ) * 5 | |
//maxhum = 40 | |
} else if(curExtTemp >= adj_temp(20)) { | |
maxhum = 40 - ( (adj_temp(32) - curExtTemp)/(adj_temp(32)-adj_temp(20)) ) * 5 | |
//maxhum = 35 | |
} else if(curExtTemp >= adj_temp(10)) { | |
maxhum = 35 - ( (adj_temp(20) - curExtTemp)/(adj_temp(20)-adj_temp(10)) ) * 5 | |
//maxhum = 30 | |
} else if(curExtTemp >= adj_temp(0)) { | |
maxhum = 30 - ( (adj_temp(10) - curExtTemp)/(adj_temp(10)-adj_temp(0)) ) * 5 | |
//maxhum = 25 | |
} else if(curExtTemp >= adj_temp(-10)) { | |
maxhum = 25 - Math.abs( (adj_temp(0) - curExtTemp) / (adj_temp(0)-adj_temp(-10)) ) * 5 | |
//maxhum = 20 | |
} else if(curExtTemp >= adj_temp(-20)) { | |
maxhum = 15 | |
} | |
} | |
return maxhum | |
} | |
boolean humCtrlScheduleOk() { return autoScheduleOk(humCtrlPrefix()) } | |
def humCtrlCheck() { | |
//LogAction("humCtrlCheck", "info", false) | |
String pName = humCtrlPrefix() | |
if(getIsAutomationDisabled()) { return } | |
try { | |
long execTime = now() | |
def tstat = settings?.schMotTstat | |
String hvacMode = tstat ? tstat?.currentThermostatMode.toString() : null | |
String curTstatOperState = tstat?.currentThermostatOperatingState.toString() | |
String curTstatFanMode = tstat?.currentThermostatFanMode.toString() | |
//def curHum = humCtrlHumidity?.currentHumidity | |
def curHum = getDeviceVarAvg(settings.humCtrlHumidity, "currentHumidity") | |
def curExtTemp = getHumCtrlTemperature() | |
def maxHum = getMaxHumidity(curExtTemp) | |
boolean schedOk = humCtrlScheduleOk() | |
LogAction("humCtrlCheck: ( Humidity: (${curHum}) | External Temp: (${curExtTemp}) | Max Humidity: (${maxHum}) | HvacMode: (${hvacMode}) | OperatingState: (${curTstatOperState}) )", "info", false) | |
if(state?.haveRunHumidifier == null) { state.haveRunHumidifier = false } | |
def savedHaveRun = state?.haveRunHumidifier | |
boolean humOn = false | |
if(curHum < maxHum) { | |
humOn = true | |
} | |
// 1:"Heating/Cooling", 2:"With Fan Only", 3:"Heating", 4:"Cooling" 5:"All Operating Modes" | |
def validOperModes = [] | |
boolean validOperating = true | |
switch ( settings?.humCtrlSwitchTriggerType?.toInteger() ) { | |
case 1: | |
validOperModes = ["heating", "cooling"] | |
validOperating = (curTstatOperState in validOperModes) ? true : false | |
break | |
case 2: | |
validOperating = (curTstatFanMode in ["on", "circulate"]) ? true : false | |
break | |
case 3: | |
validOperModes = ["heating"] | |
validOperating = (curTstatOperState in validOperModes) ? true : false | |
break | |
case 4: | |
validOperModes = ["cooling"] | |
validOperating = (curTstatOperState in validOperModes) ? true : false | |
break | |
case 5: | |
break | |
default: | |
break | |
} | |
boolean validHvac = true | |
if( !( ("any" in settings?.humCtrlSwitchHvacModeFilter) || (hvacMode in settings?.humCtrlSwitchHvacModeFilter) ) ){ | |
//LogAction("humCtrlCheck: Evaluating turn humidifier off; Thermostat Mode does not Match the required Mode", "info", false) | |
validHvac = false // force off | |
} | |
boolean turnOn = (humOn && validOperating && validHvac && schedOk) ?: false | |
//LogAction("humCtrlCheck: turnOn: ${turnOn} | humOn: ${humOn} | validOperating: ${validOperating} | validHvac: ${validHvac} | schedOk: ${schedOk} | savedHaveRun: ${savedHaveRun}", "info", false) | |
settings?.humCtrlSwitches?.each { sw -> | |
boolean swOn = (sw?.currentSwitch.toString() == "on") ? true : false | |
if(turnOn) { | |
//if(!swOn && !savedHaveRun) { | |
if(!swOn) { | |
LogAction("humCtrlCheck: Fan Switch (${sw?.displayName}) is (${swOn ? "ON" : "OFF"}) | Turning '${sw}' Switch (ON)", "info", false) | |
sw.on() | |
swOn = true | |
state.haveRunHumidifier = true | |
storeLastAction("Turned On $sw)", getDtNow(), pName) | |
} else { | |
if(!swOn && savedHaveRun) { | |
LogAction("humCtrlCheck: savedHaveRun state shows switch ${sw} turned OFF outside of automation requests", "info", false) | |
} | |
} | |
} else { | |
//if(swOn && savedHaveRun) { | |
if(swOn) { | |
LogAction("humCtrlCheck: Fan Switch (${sw?.displayName}) is (${swOn ? "ON" : "OFF"}) | Turning '${sw}' Switch (OFF)", "info", false) | |
storeLastAction("Turned Off (${sw})", getDtNow(), pName) | |
sw.off() | |
state.haveRunHumidifier = false | |
} else { | |
if(swOn && !savedHaveRun) { | |
LogAction("humCtrlCheck: Saved have run state shows switch ${sw} turned ON outside of automation requests", "info", false) | |
} | |
state.haveRunHumidifier = false | |
} | |
} | |
} | |
storeExecutionHistory((now()-execTime), "humCtrlCheck") | |
} catch (ex) { | |
log.error "humCtrlCheck Exception: ${ex?.message}" | |
//parent?.sendExceptionData(ex, "humCtrlCheck", true, getAutoType()) | |
} | |
} | |
/******************************************************************************** | |
| EXTERNAL TEMP AUTOMATION CODE | | |
*********************************************************************************/ | |
String extTmpPrefix() { return "extTmp" } | |
boolean isExtTmpConfigured() { | |
return (settings?.schMotExternalTempOff && (settings?.extTmpUseWeather || settings?.extTmpTempSensor) && settings?.extTmpDiffVal) ? true : false | |
} | |
def getWeathUpdSec() { return !state?.weatherUpdDt ? 100000 : GetTimeDiffSeconds(state?.weatherUpdDt, null, "getWeathUpdSec").toInteger() } | |
def getExtConditions( doEvent = false ) { | |
LogTrace("getExtConditions") | |
long execTime = now() | |
def t0 | |
if(state?.wDevInst == null) { | |
state?.wDevInst = false | |
t0 = parent.getSettingVal("weatherDevice") | |
state?.wDevInst = t0 ? true : false | |
} | |
if(state?.wDevInst) { | |
def weather = parent.getSettingVal("weatherDevice") | |
if(weather) { | |
def temp0 | |
def hum0 | |
if(state?.needWeathUpd || getWeathUpdSec() > 3600) { | |
stateRemove("needWeathUpd") | |
state?.weatherUpdDt = getDtNow() | |
try { | |
weather.refresh() | |
} catch (ex) { | |
log.error "getExtConditions Exception: ${ex?.message}" | |
//parent?.sendExceptionData(ex, "getExtConditions", true, getAutoType()) | |
} | |
} | |
temp0 = getDeviceTempAvg(weather) | |
hum0 = getDeviceVarAvg(weather, "currentHumidity") | |
if(temp0 || hum0) { state.curWeather = true } | |
else { state.curWeather = null; return } | |
//Logger("temp0: ${temp0} hum0: ${hum0} loc: ${state?.curWeatherLoc}") | |
state?.curWeatherLoc = "${weather?.currentCity} ${weather?.currentCountry}" | |
state?.curWeatherHum = hum0 | |
Double c_temp = 0.0 | |
long f_temp = 0 | |
if(getTemperatureScale() == "C") { | |
c_temp = temp0 | |
f_temp = ((c_temp * (9 / 5)) + 32) | |
} else { | |
f_temp = temp0 | |
c_temp = ((f_temp - 32) * (5 / 9)) | |
} | |
state?.curWeaTemp_f = Math.round(f_temp) as Integer | |
state?.curWeaTemp_c = Math.round(c_temp.round(1) * 2) / 2.0f | |
c_temp = estimateDewPoint(hum0, c_temp) | |
if(state.curWeaTemp_c < c_temp) { c_temp = state.curWeaTemp_c } | |
f_temp = c_temp * 9.0/5.0 + 32.0 | |
state?.curWeatherDewpointTemp_c = Math.round(c_temp.round(1) * 2) / 2.0f | |
state?.curWeatherDewpointTemp_f = Math.round(f_temp) as Integer | |
} | |
} | |
storeExecutionHistory((now()-execTime), "getExtConditions") | |
} | |
private estimateDewPoint(double rh,double t) { | |
double L = Math.log(rh/100) | |
double M = 17.27 * t | |
double N = 237.3 + t | |
double B = (L + (M/N)) / 17.27 | |
double dp = (237.3 * B) / (1 - B) | |
double dp1 = 243.04 * ( Math.log(rh / 100) + ( (17.625 * t) / (243.04 + t) ) ) / (17.625 - Math.log(rh / 100) - ( (17.625 * t) / (243.04 + t) ) ) | |
double ave = (dp + dp1)/2 | |
//LogAction("dp: ${dp.round(1)} dp1: ${dp1.round(1)} ave: ${ave.round(1)}") | |
ave = dp1 | |
return ave.round(1) | |
} | |
def getExtTmpTemperature() { | |
def extTemp = 0.0 | |
if(!settings?.extTmpUseWeather && settings?.extTmpTempSensor) { | |
extTemp = getDeviceTemp(settings?.extTmpTempSensor) | |
} else { | |
if(settings?.extTmpUseWeather && (state?.curWeaTemp_f || state?.curWeaTemp_c)) { | |
if(getTemperatureScale() == "C") { extTemp = state?.curWeaTemp_c.toDouble() } | |
else { extTemp = state?.curWeaTemp_f.toDouble() } | |
} | |
} | |
return extTemp | |
} | |
def getExtTmpDewPoint() { | |
def extDp = 0.0 | |
if(settings?.extTmpUseWeather && (state?.curWeatherDewpointTemp_f || state?.curWeatherDewpointTemp_c)) { | |
if(getTemperatureScale() == "C") { extDp = roundTemp(state?.curWeatherDewpointTemp_c.toDouble()) } | |
else { extDp = roundTemp(state?.curWeatherDewpointTemp_f.toDouble()) } | |
} | |
//TODO if an external sensor, if it has temp and humidity, we can calculate DP | |
return extDp | |
} | |
def getDesiredTemp() { | |
def extTmpTstat = settings?.schMotTstat | |
String curMode = extTmpTstat ? extTmpTstat?.currentThermostatMode?.toString() : null | |
boolean modeOff = (curMode in ["off"]) ? true : false | |
boolean modeEco = (curMode in ["eco"]) ? true : false | |
boolean modeCool = (curMode == "cool") ? true : false | |
boolean modeHeat = (curMode == "heat") ? true : false | |
boolean modeAuto = (curMode == "auto") ? true : false | |
def desiredHeatTemp = getRemSenHeatSetTemp(curMode) | |
def desiredCoolTemp = getRemSenCoolSetTemp(curMode) | |
String lastMode = extTmpTstat?.currentpreviousthermostatMode?.toString() | |
if(modeEco) { | |
if( !lastMode && state?.extTmpTstatOffRequested && state?.extTmplastMode) { | |
lastMode = state?.extTmplastMode | |
//state?.extTmpSavedTemp | |
} | |
if(lastMode) { | |
desiredHeatTemp = getRemSenHeatSetTemp(lastMode, modeEco, false) | |
desiredCoolTemp = getRemSenCoolSetTemp(lastMode, modeEco, false) | |
if(!desiredHeatTemp) { desiredHeatTemp = state?.extTmpSavedHTemp } | |
if(!desiredCoolTemp) { desiredCoolTemp = state?.extTmpSavedCTemp } | |
//LogAction("getDesiredTemp: Using lastMode: ${lastMode} | extTmpTstatOffRequested: ${state?.extTmpTstatOffRequested} | curMode: ${curMode}", "info", false) | |
modeOff = (lastMode in ["off"]) ? true : false | |
modeCool = (lastMode == "cool") ? true : false | |
modeHeat = (lastMode == "heat") ? true : false | |
modeAuto = (lastMode == "auto") ? true : false | |
} | |
} | |
def desiredTemp = 0 | |
if(!modeOff) { | |
if(desiredHeatTemp && modeHeat) { desiredTemp = desiredHeatTemp } | |
else if(desiredCoolTemp && modeCool) { desiredTemp = desiredCoolTemp } | |
else if(desiredHeatTemp && desiredCoolTemp && (desiredHeatTemp < desiredCoolTemp) && modeAuto ) { | |
desiredTemp = (desiredCoolTemp + desiredHeatTemp) / 2.0 | |
} | |
//else if(desiredHeatTemp && modeEco) { desiredTemp = desiredHeatTemp } | |
//else if(desiredCoolTemp && modeEco) { desiredTemp = desiredCoolTemp } | |
else if(!desiredTemp && state?.extTmpSavedTemp) { desiredTemp = state?.extTmpSavedTemp } | |
//LogAction("getDesiredTemp: curMode: ${curMode} | lastMode: ${lastMode} | Desired Temp: ${desiredTemp} | Desired Heat Temp: ${desiredHeatTemp} | Desired Cool Temp: ${desiredCoolTemp} extTmpSavedTemp: ${state?.extTmpSavedTemp}", "info", false) | |
} | |
return desiredTemp | |
} | |
boolean extTmpTempOk(disp=false, last=false) { | |
//LogTrace("extTmpTempOk") | |
String pName = extTmpPrefix() | |
try { | |
long execTime = now() | |
def extTmpTstat = settings?.schMotTstat | |
def extTmpTstatMir = settings?.schMotTstatMir | |
def intTemp = extTmpTstat ? getRemoteSenTemp().toDouble() : null | |