/************************************************************************************************ | Application Name: NST Graphs | | Copyright (C) 2018, 2019 | | Authors: Anthony S. (@tonesto7), Eric S. (@nh.schottfam) | | | | Updated 6/10/2020 | | License Info: https://github.com/tonesto7/nest-manager/blob/master/app_license.txt | |************************************************************************************************/ import groovy.json.* import java.text.SimpleDateFormat import groovy.time.* import groovy.transform.Field definition( name: "NST Graphs", namespace: "tonesto7", author: "Anthony S.", parent: "tonesto7:NST Manager", description: "This App is used to display device graphs for NST Manager", category: "Convenience", iconUrl: "", iconX2Url: "", iconX3Url: "", importUrl: "https://raw.githubusercontent.com/tonesto7/nst-manager-he/master/apps/nstGraphs.groovy", singleInstance: true, oauth: true ) static String appVersion() { "2.0.5" } preferences { page(name: "startPage") page(name: "mainAutoPage") page(name: "notAllowedPage") } mappings { path("/deviceTiles") {action: [GET: "renderDeviceTiles"]} path("/tstatTiles") {action: [GET: "getTstatTiles"]} path("/protTiles") {action: [GET: "getProtTiles"]} path("/weatherTile") {action: [GET: "getWeatherTile"]} path("/getTile/:dni") {action: [GET: "getTile"]} } def startPage() { //log.info "startPage" if(!state.autoTyp) { Logger("nothing is set startPage") } if(parent) { Boolean t0 = parent.getStateVal("ok2InstallAutoFlag") if( /* !state.isInstalled && */ t0 != true) { //Logger("Not installed ${t0}") notAllowedPage() } else { state.isParent = false if(!state.access_token) { getAccessToken() } if(!state.access_token) { enableOauth(); getAccessToken() } mainAutoPage() } } 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 } } } def mainAutoPage() { String t0 = getTemperatureScale() state.tempUnit = (t0 != null) ? t0 : state.tempUnit if(!state.autoDisabled) { state.autoDisabled = false } 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()) { t1 = "" def tstats = parent.getSettingVal("thermostats") if(tstats) { def vtstats = parent.getStateVal("vThermostats") def foundvTstats if(vtstats) { foundvTstats = vtstats?.each { dni -> def mydni = parent.getNestvStatDni(dni).toString() tstats << mydni } } t1 += "\n • With Thermostats (${tstats.size()})" } def prots = parent.getSettingVal("protects") if(prots) { t1 += "\n • With Protects (${prots.size()})" } def weather = parent.getSettingVal("weatherDevice") if(weather) { t1 += "\n • With Weather Device" } if(t1 != "") { t1 = "Charts On:" + t1 paragraph sectionTitleStr(t1) if(tstats.size() > 1 || (tstats.size() > 0 && weather.size() > 0)) { String myUrl = getAppEndpointUrl("deviceTiles") String myLUrl = getLocalEndpointUrl("deviceTiles") String myStr = """ All Devices (local) """ paragraph imgTitle(getAppImg("graph_icon.png"), paraTitleStr(myStr)) } } if(tstats) { if(tstats.size() > 1) { String myUrl = getAppEndpointUrl("tstatTiles") String myLUrl = getLocalEndpointUrl("tstatTiles") String myStr = """ All Thermostats (local) """ paragraph imgTitle(getAppImg("graph_icon.png"), paraTitleStr(myStr)) } //def sUrl = "${fullApiServerUrl("")}" def foundTstats = tstats?.collect { String dni -> def d1 = parent.getDevice(dni) String myUrl = getAppEndpointUrl("getTile/${dni}") String myLUrl = getLocalEndpointUrl("getTile/${dni}") //def myUrl = "${sUrl}" + "/getTile/${dni}" + "?access_token=${state.access_token}" //Logger("mainAuto sUrl: ${sUrl} myUrl: ${myUrl}") String myStr = """ ${d1.label ?: d1.name} (local) """ paragraph imgTitle(getAppImg("graph_icon.png"), paraTitleStr(myStr)) } } if(prots) { if(prots.size() > 1) { String myUrl = getAppEndpointUrl("protTiles") String myLUrl = getLocalEndpointUrl("protTiles") String myStr = """ All Protects (local) """ paragraph imgTitle(getAppImg("graph_icon.png"), paraTitleStr(myStr)) } //def sUrl = "${fullApiServerUrl("")}" def foundTstats = prots?.collect { String dni -> def d1 = parent.getDevice(dni) String myUrl = getAppEndpointUrl("getTile/${dni}") String myLUrl = getLocalEndpointUrl("getTile/${dni}") //def myUrl = "${sUrl}" + "/getTile/${dni}" + "?access_token=${state.access_token}" //Logger("mainAuto sUrl: ${sUrl} myUrl: ${myUrl}") String myStr = """ ${d1.label ?: d1.name} (local) """ paragraph imgTitle(getAppImg("graph_icon.png"), paraTitleStr(myStr)) } } if(weather) { String myUrl = getAppEndpointUrl("weatherTile") String myLUrl = getLocalEndpointUrl("weatherTile") String myStr = """ ${weather?.label ?: weather.name} (local) """ paragraph imgTitle(getAppImg("graph_icon.png"), paraTitleStr(myStr)) } } } section(sectionTitleStr("Automation Options:")) { 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("advAppDebug", "bool", title: imgTitle(getAppImg("list_icon.png"), inputTitleStr("Show Verbose Logs?")), required: false, defaultValue: false, submitOnChange: true) } else { settingUpdate("advAppDebug", "false", "bool") } } section(sectionTitleStr("Application Security")) { paragraph title:"What does resetting do?", "If you share a url with someone and want to remove their access you can reset your token and this will invalidate any URL you shared and create a new one for you. This will require any use in dashboards to be updated to the new URL." input (name: "resetAppAccessToken", type: "bool", title: "Reset Access Token?", required: false, defaultValue: false, submitOnChange: true, image: getAppImg("reset_icon.png")) resetAppAccessToken(settings.resetAppAccessToken == true) } section(sectionTitleStr("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." } } } } 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() } void updated() { log.debug "${app.getLabel()} Updated...with settings: ${settings}" state.isInstalled = true String appLbl = getCurAppLbl() if(appLbl?.contains("Graphs")) { if(!state.autoTyp) { state.autoTyp = "chart" } } initialize() state.lastUpdatedDt = getDtNow() } void uninstalled() { log.debug "uninstalled" uninstAutomationApp() } void initialize() { log.debug "${app.label} Initialize..." // Must be log.debug state.autoTyp = "chart" resetVars() 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]) } void resetVars() { stateRemove("evalSched") stateRemove("haveWeather") stateRemove("obs") stateRemove("detailEventHistory") stateRemove("detailExecutionHistory") } void initAutoApp() { if(settings["chartFlag"]) { state.autoTyp = "chart" } //initHistoryStore([id:200]) unschedule() unsubscribe() setAutomationStatus() subscribeToEvents() scheduler() app.updateLabel(getAutoTypeLabel()) LogAction("Automation Label: ${getAutoTypeLabel()}", "info", true) //ERS settingUpdate("showDebug", "true", "bool") settingUpdate("advAppDebug", "true", "bool") stateRemove("enRemDiagLogging") // cause recheck scheduleAutomationEval(30) if(settings.showDebug || settings.advAppDebug) { runIn(1800, logsOff) } checkCleanups() //ERS //revokeAccessToken() /* String devTilesUrl = getAppEndpointUrl("deviceTiles") String tstatTilesUrl = getAppEndpointUrl("tstatTiles") String weatherTilesUrl = getAppEndpointUrl("weatherTile") Logger("initAutoApp: devTile: ${devTilesUrl}") Logger("initAutoApp: tstatTile: ${tstatTilesUrl}") Logger("initAutoApp: weatherTile: ${weatherTilesUrl}") */ } void subscribeToEvents() { def weather = parent.getSettingVal("weatherDevice") if(weather) { subscribe(weather, "temperature", automationGenericEvt) subscribe(weather, "humidity", automationGenericEvt) } else { LogAction("No weather device found", "error", true) } def tstats = parent.getSettingVal("thermostats") def foundTstats if(tstats) { foundTstats = tstats?.collect { String dni -> def d1 = parent.getDevice(dni) if(d1) { //LogAction("Found: ${d1?.displayName} with (Id: ${dni?.key})", "debug", false) tSubscribe(d1) } return d1 } } def vtstats = parent.getStateVal("vThermostats") def foundvTstats if(vtstats) { foundvTstats = vtstats?.each { String dni -> String mydni = parent.getNestvStatDni(dni).toString() def d1 = parent.getDevice(mydni) if(d1) { //LogAction("Found: ${d1?.displayName} with (Id: ${mydni?.key})", "debug", false) tSubscribe(d1) } else { LogAction(" vstat NOT found $dni", "error", true) } } } } void tSubscribe(d1) { subscribe(d1, "temperature", automationGenericEvt) subscribe(d1, "humidity", automationGenericEvt) subscribe(d1, "thermostatOperatingState", automationGenericEvt) subscribe(d1, "thermostatFanMode", automationGenericEvt) subscribe(location, "mode", automationGenericEvt) if(getCanCool(d1)) { subscribe(d1, "coolingSetpoint", automationGenericEvt) } if(getCanHeat(d1)) { subscribe(d1, "heatingSetpoint", automationGenericEvt) } } void scheduler() { // "runEvery${state.poll}Minutes"(poll) runEvery15Minutes(resetVars) } void uninstAutomationApp() { } static String strCapitalize(String str) { return str ? str.capitalize() : null } void automationGenericEvt(evt) { Long startTime = now() Long 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) doTheEvent(evt) } void doTheEvent(evt) { if(getIsAutomationDisabled()) { return } else { scheduleAutomationEval() storeLastEventData(evt) } } void storeLastEventData(evt) { if(evt) { Map newVal = ["name":evt.name, "displayName":evt.displayName, "value":evt.value, "date":formatDt(evt.date), "unit":evt.unit] state.lastEventData = newVal //log.debug "LastEvent: ${state.lastEventData}" List list = state.detailEventHistory ?: [] Integer listSize = 15 if(list?.size() < listSize) { Boolean a=list.push(newVal) } else if(list?.size() > listSize) { Integer nSz = (list?.size()-listSize) + 1 List nList = list.drop(nSz) Boolean a=nList.push(newVal) list = nList } else if(list?.size() == listSize) { List nList = list.drop(1) Boolean a=nList?.push(newVal) list = nList } if(list) { state.detailEventHistory = list } } } void storeExecutionHistory(val, String method = null) { //log.debug "storeExecutionHistory($val, $method)" //try { if(method) { LogTrace("${method} Execution Time: (${val} milliseconds)") } //if(method in ["watchDogCheck", "checkNestMode", "schMotCheck"]) { state.autoExecMS = val ?: null List list = state.evalExecutionHistory ?: [] Integer listSize = 20 list = addToList(val, list, listSize) if(list) { state.evalExecutionHistory = list } //} /* if(!(method in ["watchDogCheck", "checkNestMode"])) { List list = state.detailExecutionHistory ?: [] Integer listSize = 30 list = addToList([val, method, getDtNow()], list, listSize) if(list) { state.detailExecutionHistory = list } } */ /* } catch (ex) { log.error "storeExecutionHistory Exception:", ex //parent?.sendExceptionData(ex, "storeExecutionHistory", true, getAutoType()) } */ } static List addToList(val, List list, Integer listSize) { if(list?.size() < listSize) { list.push(val) } else if(list?.size() > listSize) { Integer nSz = (list?.size()-listSize) + 1 List nList = list?.drop(nSz) Boolean a=nList.push(val) list = nList } else if(list?.size() == listSize) { List nList = list.drop(1) Boolean a=nList?.push(val) list = nList } return list } void setAutomationStatus(Boolean upd=false) { Boolean myDis = (settings.autoDisabledreq == true) Boolean settingsReset = (parent.getSettingVal("disableAllAutomations") == true) Boolean storAutoType = getAutoType() == "storage" 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() } } static Integer defaultAutomationTime() { return 5 } void scheduleAutomationEval(Integer schedtime = defaultAutomationTime()) { Integer theTime = schedtime if(theTime < defaultAutomationTime()) { theTime = defaultAutomationTime() } String autoType = getAutoType() def random = new Random() Integer random_int = random.nextInt(6) // this randomizes a bunch of automations firing at same time off same event Boolean waitOverride = false switch(autoType) { case "chart": if(theTime == defaultAutomationTime()) { theTime += random_int } Integer schWaitVal = settings.schMotWaitVal?.toInteger() ?: 60 if(schWaitVal > 120) { schWaitVal = 120 } Integer t0 = getAutoRunSec() if((schWaitVal - t0) >= theTime ) { theTime = (schWaitVal - t0) waitOverride = true } //theTime = Math.min( Math.max(theTime,defaultAutomationTime()), 120) break } if(!state.evalSched) { runIn(theTime, "runAutomationEval", [overwrite: true]) state.autoRunInSchedDt = getDtNow() state.evalSched = true state.evalSchedLastTime = theTime } else { String str = "scheduleAutomationEval: " Integer t0 = state.evalSchedLastTime if(t0 == null) { t0 = 0 } Integer 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}") } } } Integer getAutoRunSec() { return !state.autoRunDt ? 100000 : GetTimeDiffSeconds(state.autoRunDt, null, "getAutoRunSec").toInteger() } Integer getAutoRunInSec() { return !state.autoRunInSchedDt ? 100000 : GetTimeDiffSeconds(state.autoRunInSchedDt, null, "getAutoRunInSec").toInteger() } void runAutomationEval() { LogTrace("runAutomationEval") Long execTime = now() String autoType = getAutoType() state.evalSched = false state.evalSchedLastTime = null switch(autoType) { case "chart": def weather = parent.getSettingVal("weatherDevice") if(weather) { getSomeWData(weather) } def tstats = parent.getSettingVal("thermostats") def foundTstats if(tstats) { foundTstats = tstats?.collect { String dni -> def d1 = parent.getDevice(dni) if(d1) { //LogAction("Found: ${d1?.displayName} with (Id: ${dni})", "debug", false) getSomeData(d1) } return d1 } } def vtstats = parent.getStateVal("vThermostats") def foundvTstats if(vtstats) { foundvTstats = vtstats?.collect { String dni -> String mydni = parent.getNestvStatDni(dni).toString() def d1 = parent.getDevice(mydni) if(d1) { //LogAction("Found: ${d1?.displayName} with (Id: ${mydni})", "debug", false) getSomeData(d1) } return d1 } } break default: LogAction("runAutomationEval: Invalid Option Received ${autoType}", "warn", true) break } storeExecutionHistory((now()-execTime), "runAutomationEval") } String getCurAppLbl() { return app.label } static String appLabel() { return "NST Graphs" } static String appName() { return appLabel() } String getAutoTypeLabel() { //LogTrace("getAutoTypeLabel()") String type = state.autoTyp String appLbl = getCurAppLbl() String newName = appName() == appLabel() ? "NST Graphs" : appName() String typeLabel = "" String newLbl String dis = getIsAutomationDisabled() ? "\n(Disabled)" : "" typeLabel = "Nest Location ${location.name} Graphs" //Logger("getAutoTypeLabel: ${type} ${appLbl} ${appName()} ${appLabel()} ${typeLabel}") if(appLbl != "" && appLbl && appLbl != "Nst Graphs" && appLbl != appLabel()) { if(appLbl?.contains("\n(Disabled)")) { newLbl = appLbl?.replaceAll('\\\n\\(Disabled\\)', '') } else { newLbl = appLbl } } else { newLbl = typeLabel } return newLbl+dis } //ERS void checkCleanups() { def inuse = [] def weather = parent.getSettingVal("weatherDevice") if(weather) { inuse += weather.id } def tstats = parent.getSettingVal("thermostats") def foundTstats if(tstats) { foundTstats = tstats?.collect { String dni -> def d1 = parent.getDevice(dni) if(d1) { inuse += d1.id //LogAction("Found: ${d1?.displayName} with (Id: ${dni?.key})", "debug", false) } } } def vtstats = parent.getStateVal("vThermostats") def foundvTstats if(vtstats) { foundvTstats = vtstats?.collect { String dni -> String mydni = parent.getNestvStatDni(dni).toString() def d1 = parent.getDevice(mydni) if(d1) { inuse += d1.id //LogAction("Found: ${d1?.displayName} with (Id: ${dni?.key})", "debug", false) } } } def data = [] def regex1 = /Wtoday/ ["Wtoday"]?.each { oi-> state.each { if(it?.key?.toString()?.startsWith(oi)) { data?.push(it.key.replaceAll(regex1, "")) } } } def regex2 = /thermStor/ ["thermStor"]?.each { oi-> state.each { if(it?.key?.toString()?.startsWith(oi)) { data?.push(it.key.replaceAll(regex2, "")) } } } //Logger("data is ${data}") def toDelete = data.findAll { !inuse.contains(it) } //Logger("toDelete is ${toDelete}") toDelete?.each { item -> cleanState(item.toString()) } } void cleanState(id) { LogTrace("cleanState: ${id}") stateRemove("Wtoday${id}") stateRemove("WhumTblYest${id}") stateRemove("WdewTblYest${id}") stateRemove("WtempTblYest${id}") stateRemove("WhumTbl${id}") stateRemove("WdewTbl${id}") stateRemove("WtempTbl${id}") stateRemove("today${id}") stateRemove("thermStor${id}") stateRemove("tempTblYest${id}") stateRemove("tempTbl${id}") stateRemove("oprStTblYest${id}") stateRemove("oprStTbl${id}") stateRemove("humTblYest${id}") stateRemove("humTbl${id}") stateRemove("hspTblYest${id}") stateRemove("hspTbl${id}") stateRemove("cspTblYest${id}") stateRemove("cspTbl${id}") stateRemove("fanTblYest${id}") stateRemove("fanTbl${id}") } static String sectionTitleStr(title) { return "

$title

" } static String inputTitleStr(title) { return "$title" } static String pageTitleStr(title) { return "

$title

" } static String paraTitleStr(title) { return "$title" } String imgTitle(imgSrc, titleStr, color=null, imgWidth=30, imgHeight=null) { String imgStyle = "" imgStyle += imgWidth ? "width: ${imgWidth}px !important;" : "" imgStyle += imgHeight ? "${imgWidth ? " " : ""}height: ${imgHeight}px !important;" : "" if(color) { return """
${titleStr}
""" } else { return """ ${titleStr}""" } } static String icons(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".toString() else return "https://raw.githubusercontent.com/${gitPath()}/Images/$napp/${name}".toString() } static String gitRepo() { return "tonesto7/nest-manager"} static String gitBranch() { return "master" } static String gitPath() { return gitRepo()+'/'+gitBranch() } String getAppImg(String imgName, Boolean on = null) { //return (!disAppIcons || on) ? "https://raw.githubusercontent.com/${gitPath()}/Images/App/$imgName" : "" return (!disAppIcons || on) ? icons(imgName) : "" } String getDevImg(String imgName, Boolean on = null) { //return (!disAppIcons || on) ? "https://raw.githubusercontent.com/${gitPath()}/Images/Devices/$imgName" : "" return (!disAppIcons || on) ? icons(imgName, "Devices") : "" } void logsOff() { Logger("debug logging disabled...") settingUpdate("showDebug", "false", "bool") settingUpdate("advAppDebug", "false", "bool") } def getSettingsData() { def sets = [] settings.sort().each { st -> sets << st } return sets } def getSettingVal(String var) { if(var == (String)null) { return settings } return settings[var] ?: null } def getStateVal(String var) { return state[var] ?: null } void settingUpdate(String name, value, String type=(String)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 } } def stateRemove(String key) { state.remove(key?.toString()) return true } String getAutomationType() { return (String)state.autoTyp ?: (String)null } String getAutoType() { return !parent ? "" : (String)state.autoTyp } Boolean getIsAutomationDisabled() { Boolean dis = state.autoDisabled return (dis != null && dis) } def getTstatTiles() { //log.debug "${params} ${request.requestSource}" return renderDeviceTiles("Nest Thermostat") } def getTile() { LogTrace ("getTile()") //log.debug "${params} ${request.requestSource}" String responseMsg = "" String dni = params?.dni?.toString() if (dni) { def device = parent.getDevice(dni) if (device) { return renderDeviceTiles((String)null, device) } else { responseMsg = "Device '${dni}' Not Found" } } else { responseMsg = "Invalid Parameters" } render contentType: "text/html", data: responseMsg } def getWeatherTile() { //log.debug "${params} ${request.requestSource}" def weather = parent.getSettingVal("weatherDevice") if( weather?.typeName in ["ApiXU Weather Driver Min", "DarkSky.net Weather Driver", "OpenWeatherMap-NWS Alerts Weather Driver" ]){ return renderDeviceTiles((String)weather.typeName) } LogAction("getWeatherTile: Invalid Option Received ${weather.typeName}", "warn", true) return null } def getProtTiles() { //log.debug "${params} ${request.requestSource}" return renderDeviceTiles("Nest Protect") } def renderDeviceTiles(String type=null, theDev=null) { //log.debug "${params} ${request.requestSource}" // try { String devHtml = "" String navHtml = "" String scrStr = "" def allDevices = [] if(theDev) { allDevices << theDev } else { allDevices = parent.getDevices() // app.getChildDevices(true) def weather = parent.getSettingVal("weatherDevice") if(weather) { allDevices << weather } } def devices = allDevices Integer devNum = 1 String myType = type ?: "All Devices" devices?.sort {it?.getLabel()}.each { dev -> def navMap = [:] Boolean hasHtml = true // (dev?.hasHtml() == true) //Logger("renderDeviceTiles: ${dev.id} ${dev.name} ${theDev?.name} 1${dev.typeName}1") // if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") // if( ( (hasHtml && !type) || (hasHtml && type && dev?.typeName == type)) ) Logger("found new in") if( (dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver", "OpenWeatherMap-NWS Alerts Weather Driver"]) && ( (hasHtml && !type) || (hasHtml && type && dev?.typeName == type)) ) { //LogTrace("renderDeviceTiles: ${dev.id} ${dev.name} ${theDev?.name} ${dev.typeName}") String myTile switch (dev.typeName) { case "Nest Thermostat": myTile = getTDeviceTile(devNum, dev) break case "OpenWeatherMap-NWS Alerts Weather Driver": case "DarkSky.net Weather Driver": myTile = getWDDeviceTile(devNum, dev) break case "ApiXU Weather Driver Min": myTile = getWDeviceTile(devNum, dev) break case "Nest Protect": myTile = getProtDeviceTile(devNum, dev) break default: LogAction("renderDeviceTiles Invalid Option Received ${dev.typeName}", "warn", true) break } String lbl= dev.getLabel() ?: dev.name navMap = ["key":lbl, "items":[]] Map navItems = navHtmlBuilder(navMap, devNum) if(navItems?.html) { navHtml += navItems?.html } if(navItems?.js) { scrStr += navItems?.js } devHtml += """

${lbl}

${myTile}
""" devNum = devNum+1 } } String myTitle = "All Devices" myTitle = type ? "${type}s" : myTitle myTitle = theDev ? "${theDev.typeName}" : myTitle String html = """ ${getWebHeaderHtml(myType, true, true, true, true)}
${devHtml}
""" /* """ */ render contentType: "text/html", data: html // } catch (ex) { log.error "renderDeviceData Exception:", ex } } Map navHtmlBuilder(navMap, idNum) { Map res = [:] String htmlStr = "" String jsStr = "" if(navMap?.key) { htmlStr += """ """ res["html"] = htmlStr res["js"] = jsStr return res } String navJsBuilder(String btnId, String divId) { String res = """ \$("#${btnId}").click(function() { \$("html, body").animate({scrollTop: \$("#${divId}").offset().top - hdrHeight - 20},500); closeNavMenu(); toggleMenuBtn(); }); """ return '\n'+res } Map getTodaysUsage(dev) { Map hm = getHistoryStore(dev) Map timeMap = [:] timeMap << ["cooling":["tData":secToTimeMap(hm?."OpSt_Day${hm.cDy}_c"), "tSec":hm?."OpSt_Day${hm.cDy}_c"]] timeMap << ["heating":["tData":secToTimeMap(hm?."OpSt_Day${hm.cDy}_h"), "tSec":hm?."OpSt_Day${hm.cDy}_h"]] timeMap << ["idle":["tData":secToTimeMap(hm?."OpSt_Day${hm.cDy}_i"), "tSec":hm?."OpSt_Day${hm.cDy}_i"]] timeMap << ["fanonly":["tData":secToTimeMap(hm?."OpSt_Day${hm.cDy}_fo"), "tSec":hm?."OpSt_Day${hm.cDy}_fo"]] timeMap << ["fanOn":["tData":secToTimeMap(hm?."FanM_Day${hm.cDy}_On"), "tSec":hm?."FanM_Day${hm.cDy}_On"]] timeMap << ["fanAuto":["tData":secToTimeMap(hm?."FanM_Day${hm.cDy}_auto"), "tSec":hm?."FanM_Day${hm.cDy}_auto"]] return timeMap } Map getWeeksUsage(dev) { Map hm = getHistoryStore(dev) Map timeMap = [:] Long coolVal = 0L Long heatVal = 0L Long idleVal = 0L Long fanonlyVal = 0L Long fanOnVal = 0L Long fanAutoVal = 0L for(Integer i = 1; i <= 7; i++) { coolVal = coolVal + hm?."OpSt_Day${i}_c"?.toInteger() heatVal = heatVal + hm?."OpSt_Day${i}_h"?.toInteger() idleVal = idleVal + hm?."OpSt_Day${i}_i"?.toInteger() fanonlyVal = fanonlyVal + hm?."OpSt_Day${i}_fo"?.toInteger() fanOnVal = fanOnVal + hm?."FanM_Day${i}_On"?.toInteger() fanAutoVal = fanAutoVal + hm?."FanM_Day${i}_auto"?.toInteger() } timeMap << ["cooling":["tData":secToTimeMap(coolVal), "tSec":coolVal]] timeMap << ["heating":["tData":secToTimeMap(heatVal), "tSec":heatVal]] timeMap << ["idle":["tData":secToTimeMap(idleVal), "tSec":idleVal]] timeMap << ["fanonly":["tData":secToTimeMap(fanonlyVal), "tSec":fanonlyVal]] timeMap << ["fanOn":["tData":secToTimeMap(fanOnVal), "tSec":fanOnVal]] timeMap << ["fanAuto":["tData":secToTimeMap(fanAutoVal), "tSec":fanAutoVal]] //log.debug "weeksUsage: ${timeMap}" return timeMap } Map getMonthsUsage(monNum,dev) { LogTrace("getMonthsUsage ${monNum}") Map hm = getHistoryStore(dev) Map timeMap = [:] Integer mVal = (monNum >= 1 && monNum <= 12) ? monNum : hm?.curMon timeMap << ["cooling":["tData":secToTimeMap(hm?."OpSt_M${mVal}_c"), "tSec":hm?."OpSt_M${mVal}_c"]] timeMap << ["heating":["tData":secToTimeMap(hm?."OpSt_M${mVal}_h"), "tSec":hm?."OpSt_M${mVal}_h"]] timeMap << ["idle":["tData":secToTimeMap(hm?."OpSt_M${mVal}_i"), "tSec":hm?."OpSt_M${mVal}_i"]] timeMap << ["fanonly":["tData":secToTimeMap(hm?."OpSt_M${mVal}_fo"), "tSec":hm?."OpSt_M${mVal}_fo"]] timeMap << ["fanOn":["tData":secToTimeMap(hm?."FanM_M${mVal}_On"), "tSec":hm?."FanM_M${mVal}_On"]] timeMap << ["fanAuto":["tData":secToTimeMap(hm?."FanM_M${mVal}_auto"), "tSec":hm?."FanM_M${mVal}_auto"]] //log.debug "monthsUsage: $mVal ${timeMap}" return timeMap } Map getLast3MonthsUsageMap(dev) { Map hm = getHistoryStore(dev) Map timeMap = [:] Integer cnt = 1 Integer mVal = (Integer) hm?.curMon if(mVal) { for(Integer i=1; i<=3; i++) { Map newMap = [:] String mName = getMonthNumToStr(mVal) //log.debug "$mName Usage - Idle: (${hm?."OpSt_M${mVal}_i"}) | Heat: (${hm?."OpSt_M${mVal}_h"}) | Cool: (${hm?."OpSt_M${mVal}_c"})" newMap << ["cooling":["tSec":(hm?."OpSt_M${mVal}_c" ?: 0L), "iNum":cnt, "mName":mName]] newMap << ["heating":["tSec":(hm?."OpSt_M${mVal}_h" ?: 0L), "iNum":cnt, "mName":mName]] newMap << ["idle":["tSec":(hm?."OpSt_M${mVal}_i" ?: 0L), "iNum":cnt, "mName":mName]] newMap << ["fanonly":["tSec":(hm?."OpSt_M${mVal}_fo" ?: 0L), "iNum":cnt, "mName":mName]] newMap << ["fanOn":["tSec":(hm?."FanM_M${mVal}_On" ?: 0L), "iNum":cnt, "mName":mName]] newMap << ["fanAuto":["tSec":(hm?."FanM_M${mVal}_auto" ?: 0L), "iNum":cnt, "mName":mName]] timeMap << [(mVal):newMap] mVal = ((mVal==1) ? 12 : mVal-1) cnt = cnt+1 } } return timeMap } String getMonthNumToStr(val) { Map mons = [1:"Jan", 2:"Feb", 3:"Mar", 4:"Apr", 5:"May", 6:"June", 7:"July", 8:"Aug", 9:"Sept", 10:"Oct", 11:"Nov", 12:"Dec"] def res = mons?.find { key, value -> key.toInteger() == val?.toInteger() } return res ? (String)res.value : "unknown" } /* def getYearsUsage(dev) { Map hm = getHistoryStore(dev) def timeMap = [:] def coolVal = 0L def heatVal = 0L def idleVal = 0L def fanonlyVal = 0L def fanOnVal = 0L def fanAutoVal = 0L for(Integer i = 1; i <= 12; i++) { coolVal = coolVal + hm?."OpSt_M${i}_c"?.toInteger() heatVal = heatVal + hm?."OpSt_M${i}_h"?.toInteger() idleVal = idleVal + hm?."OpSt_M${i}_i"?.toInteger() fanonlyVal = fanonlyVal + hm?."OpSt_M${i}_fo"?.toInteger() fanOnVal = fanOnVal + hm?."FanM_M${i}_On"?.toInteger() fanAutoVal = fanAutoVal + hm?."FanM_M${i}_auto"?.toInteger() } timeMap << ["cooling":["tData":secToTimeMap(coolVal), "tSec":coolVal]] timeMap << ["heating":["tData":secToTimeMap(heatVal), "tSec":heatVal]] timeMap << ["idle":["tData":secToTimeMap(idleVal), "tSec":idleVal]] timeMap << ["fanonly":["tData":secToTimeMap(fanonlyVal), "tSec":fanonlyVal]] timeMap << ["fanOn":["tData":secToTimeMap(fanOnVal), "tSec":fanOnVal]] timeMap << ["fanAuto":["tData":secToTimeMap(fanAutoVal), "tSec":fanAutoVal]] //log.debug "yearsUsage: ${timeMap}" return timeMap } def doSomething() { getNestMgrReport() //getTodaysUsage() //getWeeksUsage() //getMonthsUsage() //getYearsUsage() } */ Map getHistoryStore(dev) { LogTrace("getHistoryStore(${dev.id})...") Map thm = state."thermStor${dev.id}" if(thm == null || thm == [:]) { log.error "thm is null" return } //Logger("getHistoryStore: thm: ${thm}") Map hm = thm.clone() Long Op_cusage = getSumUsage(state."oprStTbl${dev.id}", "cooling") Long Op_husage = getSumUsage(state."oprStTbl${dev.id}", "heating") Long OpIdle = getSumUsage(state."oprStTbl${dev.id}", "idle") Long Op_fo = getSumUsage(state."oprStTbl${dev.id}", "fan only") Long FanOn = getSumUsage(state."fanTbl${dev.id}", "on") Long FanAuto = getSumUsage(state."fanTbl${dev.id}", "auto") //log.info "FanOn ${FanOn} FanAuto: ${FanAuto} OpIdle: ${OpIdle} cool: ${Op_cusage} heat: ${Op_husage}" //log.debug "cDy ${hm.cDy} | curMon ${hm.curMon} | curYr: ${hm.curYr}" hm."OpSt_Day${hm.cDy}_c" = Op_cusage hm."OpSt_Day${hm.cDy}_h" = Op_husage hm."OpSt_Day${hm.cDy}_i" = OpIdle hm."OpSt_Day${hm.cDy}_fo" = Op_fo hm."FanM_Day${hm.cDy}_On" = FanOn hm."FanM_Day${hm.cDy}_auto" = FanAuto Long t1 = hm?."OpSt_M${hm.curMon}_c"?.toInteger() ?: 0L hm."OpSt_M${hm.curMon}_c" = t1 + Op_cusage t1 = hm?."OpSt_M${hm.curMon}_h"?.toInteger() ?: 0L hm."OpSt_M${hm.curMon}_h" = t1 + Op_husage t1 = hm?."OpSt_M${hm.curMon}_i"?.toInteger() ?: 0L hm."OpSt_M${hm.curMon}_i" = t1 + OpIdle t1 = hm?."OpSt_M${hm.curMon}_fo"?.toInteger() ?: 0L hm."OpSt_M${hm.curMon}_fo" = t1 + Op_fo t1 = hm?."FanM_M${hm.curMon}_On"?.toInteger() ?: 0L hm."FanM_M${hm.curMon}_On" = t1 + FanOn t1 = hm?."FanM_M${hm.curMon}_auto"?.toInteger() ?: 0L hm."FanM_M${hm.curMon}_auto" = t1 + FanAuto t1 = hm?.OpSt_thisY_c?.toInteger() ?: 0L hm.OpSt_thisY_c = t1 + Op_cusage t1 = hm?.OpSt_thisY_h?.toInteger() ?: 0L hm.OpSt_thisY_h = t1 + Op_husage t1 = hm?.OpSt_thisY_i?.toInteger() ?: 0L hm.OpSt_thisY_i = t1 + OpIdle t1 = hm?.OpSt_thisY_fo?.toInteger() ?: 0L hm.OpSt_thisY_fo = t1 + Op_fo t1 = hm?.FanM_thisY_On?.toInteger() ?: 0L hm.FanM_thisY_On = t1 + FanOn t1 = hm?.FanM_thisY_auto?.toInteger() ?: 0L hm.FanM_thisY_auto = t1 + FanAuto return hm } Integer getIntListAvg(itemList) { //log.debug "itemList: ${itemList}" def avgRes = 0 Integer iCnt = itemList?.size() if(iCnt >= 1) { if(iCnt > 1) { avgRes = (itemList?.sum().toDouble() / iCnt.toDouble()).round(0) } else { itemList?.each { avgRes = avgRes + it.toInteger() } } } //log.debug "[getIntListAvg] avgRes: $avgRes" return avgRes.toInteger() } Map secToTimeMap(Long seconds) { Long sec = (seconds % 60) ?: 0L Long minutes = ((seconds % 3600) / 60) ?: 0L Long hours = ((seconds % 86400) / 3600) ?: 0L Long days = (seconds / 86400) ?: 0L Long years = (days / 365) ?: 0L Map res = ["m":minutes, "h":hours, "d":days, "y":years] return res } Boolean extWeatTempAvail() { //def weather = parent.getSettingVal("weatherDevice") if(state.haveWeather == null) { state.haveWeather = parent.getSettingVal("weatherDevice") ? true : false } return (Boolean)state.haveWeather } String getTDeviceTile(Integer devNum, dev) { LogTrace("getTDeviceTile ${dev?.label} ${dev.id}") // try { String tempStr = getTempUnitStr() //LogAction("State Size: ${getStateSize()} (${getStateSizePerc()}%)") //Logger("T1") Boolean canHeat = getCanHeat(dev) Boolean canCool = getCanCool(dev) Boolean hasFan = getHasFan(dev) String leafImg = getHasLeaf(dev) ? getDevImg("nest_leaf_on.gif") : getDevImg("nest_leaf_off.gif") //Logger("T2") def timeToTarget = dev.currentState("timeToTarget").value //Logger("3") String sunCorrectStr = dev.currentState("sunlightCorrectionEnabled").value.toBoolean() ? "Enabled (${dev.currentState("sunlightCorrectionActive").value.toBoolean() == true ? "Active" : "Inactive"})" : "Disabled" String refreshBtnHtml = true /* parent.getStateVal("mobileClientType") == "ios" */ ? """
""" : "" //Logger("4") String chartHtml = ( // state.showGraphs && state."tempTbl${dev.id}"?.size() > 0 && state."oprStTbl${dev.id}"?.size() > 0 && state."tempTblYest${dev.id}"?.size() > 0 && state."humTbl${dev.id}"?.size() > 0 && state."cspTbl${dev.id}"?.size() > 0 && state."hspTbl${dev.id}"?.size() > 0) ? showChartHtml(devNum, dev) : (true /* state.showGraphs */ ? hideChartHtml() : "") Map schedData = state.curAutoSchedData String schedHtml = "" //Logger("5") if(schedData) { schedHtml = """

Automation Schedule

Active Schedule
#${schedData?.scdNum} - ${schedData?.schedName}

Zone Status

Temp Source: Zone Temp:
${schedData?.tempSrcDesc} ${schedData?.curZoneTemp}°${state.tempUnit}
Desired Heat Temp Desired Cool Temp
${schedData?.reqSenHeatSetPoint ? "${schedData?.reqSenHeatSetPoint}°${state.tempUnit}": "Not Available"} ${schedData?.reqSenCoolSetPoint ? "${schedData?.reqSenCoolSetPoint}°${state.tempUnit}": "Not Available"}

""" } String chgDescHtml = "" //Logger("6") def onlineStatus = dev.currentState("onlineStatus").value def apiStatus = dev.currentState("apiStatus").value String html = """
${schedHtml == "" ? "" : "${schedHtml}"}

Device Info

Time to Target Sun Correction
${timeToTarget} ${sunCorrectStr}
${onlineStatus.toString().capitalize()} ${apiStatus}
Network Status Leaf API Status
Firmware Version Nest Checked-In
${dev.currentState("softwareVer").value?.toString()} ${dev.currentState("lastConnection").value?.toString()}
${schedHtml == "" ? """
${chgDescHtml}""" : ""}
${schedHtml == "" ? "" : """${chgDescHtml}"""} ${chartHtml}

Swipe/Drag to Change Slide

""" /* """ */ // render contentType: "text/html", data: html, status: 200 /* } catch (ex) { log.error "getTDeviceTile Exception:", ex //exceptionDataHandler(ex?.message, "getTDeviceTile") } */ } String getWebHeaderHtml(String title, Boolean clipboard=true, Boolean vex=false, Boolean swiper=false, Boolean charts=false) { String html = """ NST Graphs ('${parent.getStateVal("structureName")}') - ${title} """ html += clipboard ? """""" : "" html += vex ? """""" : "" html += swiper ? """""" : "" html += vex ? """""" : "" html += vex ? """""" : "" html += swiper ? """""" : "" html += charts ? """""" : "" html += vex ? """""" : "" return html } static String hideChartHtml() { String data = """

Event History


Waiting for more data to be collected...

This may take a few hours

""" return data } String getDataTString(Integer seriesIndex,dev) { //LogAction("getDataTString ${seriesIndex}", "trace") def dataTable = [] switch (seriesIndex) { case 1: dataTable = state."tempTblYest${dev.id}" break case 2: dataTable = state."tempTbl${dev.id}" break case 3: dataTable = state."oprStTbl${dev.id}" break case 4: dataTable = state."humTbl${dev.id}" break case 5: dataTable = state."cspTbl${dev.id}" break case 6: dataTable = state."hspTbl${dev.id}" break case 7: dataTable = state."eTempTbl" break case 8: dataTable = state."fanTbl${dev.id}" break } Integer lastVal = 200 //LogAction("getDataTString ${seriesIndex} ${dataTable}") //LogAction("getDataTString ${seriesIndex}") Boolean lastAdded = false def dataArray def myval Integer myindex def lastdataArray = null String dataString = "" if(seriesIndex == 5) { // state.can_cool } if(seriesIndex == 6) { // state.can_heat } if(seriesIndex == 8) { //state.has_fan } Boolean myhas_fan = getHasFan(dev) && false ? true : false // false because not graphing fan operation now Boolean has_weather = extWeatTempAvail() //if( !(state.curWeatData == null || state.curWeatData == [:])) { has_weather = true } Boolean canHeat = getCanHeat(dev) Boolean canCool = getCanCool(dev) Integer datacolumns myindex = seriesIndex //ERSERS datacolumns = 8 //if(state.can_heat && state.can_cool && myhas_fan && has_weather) { datacolumns = 8 } if(!myhas_fan) { datacolumns -= 1 } if(!has_weather) { datacolumns -= 1 if(myindex == 8) { myindex = 7 } } if((!canHeat && canCool) || (canHeat && !canCool)) { datacolumns -= 1 if(myindex >= 6) { myindex -= 1 } } switch (datacolumns) { case 8: dataArray = [[0,0,0],null,null,null,null,null,null,null,null] break case 7: dataArray = [[0,0,0],null,null,null,null,null,null,null] break case 6: dataArray = [[0,0,0],null,null,null,null,null,null] break case 5: dataArray = [[0,0,0],null,null,null,null,null] break default: LogAction("getDataTString: bad column result", "error") } dataTable.any { it -> myval = it[2] //convert idle / non-idle to numeric value if(myindex == 3) { switch(myval) { case "idle": myval = 0 break case "cooling": myval = 8 break case "heating": myval = 16 break case "fan only": myval = 4 break default: myval = 0 break } } /* if(myhas_fan && seriesIndex == 8) { switch(myval) { case "auto": myval = 0 break case "on": myval = 8 break case "circulate": myval = 8 break default: myval = 0 break } } */ if(seriesIndex == 5) { if(myval == 0) { return false } // state.can_cool } if(seriesIndex == 6) { if(myval == 0) { return false } // state.can_heat } dataArray[myindex] = myval dataArray[0] = [it[0],it[1],0] dataString += dataArray?.toString() + "," return false } if(dataString == "") { dataArray[0] = [0,0,0] //dataArray[myindex] = 0 dataString += dataArray?.toString() + "," } //LogAction("getDataTString ${seriesIndex} datacolumns: ${datacolumns} myindex: ${myindex} datastring: ${dataString}") return dataString } /* variable attribute for history getRoutine variable is present temperature "temperature" getTemp true # coolSetpoint "coolingSetpoint" getCoolTemp state.can_cool # heatSetpoint "heatingSetpoint" getHeatTemp state.can_heat # operatingState "thermostatOperatingState" getHvacState true idle cooling heating operatingMode "thermostatMode" getHvacMode true heat cool off auto presence "presence" getPresence true present not present */ void getSomeData(dev, Boolean devpoll = false) { //LogTrace("getSomeData ${app} ${dev?.label} ${dev.id}") //ERS Date today = new Date() String todayDay = today.format("dd",location.timeZone) //Logger("getSomeData: ${today} ${todayDay} ${dev.id}") if(state."tempTbl${dev.id}" == null) { state."tempTbl${dev.id}" = [] state."oprStTbl${dev.id}" = [] state."humTbl${dev.id}" = [] state."cspTbl${dev.id}" = [] state."hspTbl${dev.id}" = [] state."eTempTbl" = [] state."fanTbl${dev.id}" = [] addNewData(dev) } def tempTbl = state."tempTbl${dev.id}" def oprStTbl = state."oprStTbl${dev.id}" def humTbl = state."humTbl${dev.id}" def cspTbl = state."cspTbl${dev.id}" def hspTbl = state."hspTbl${dev.id}" def eTempTbl = state."eTempTbl" def fanTbl = state."fanTbl${dev.id}" /* if(fanTbl == null) { // upgrade cleanup ERSTODO state.fanTbl = []; fanTbl = state.fanTbl; state.fanTblYest = fanTbl } if(eTempTbl == null) { // upgrade cleanup ERSTODO state.eTempTbl = []; eTempTbl = state.eTempTbl; state.eTempTblYest = eTempTbl } */ Map hm = state."thermStor${dev.id}" if(hm == null) { initHistoryStore(dev) } if(state."tempTblYest${dev.id}"?.size() == 0) { state."tempTblYest${dev.id}" = tempTbl state."oprStTblYest${dev.id}" = oprStTbl state."humTblYest${dev.id}" = humTbl state."cspTblYest${dev.id}" = cspTbl state."hspTblYest${dev.id}" = hspTbl state."eTempTblYest" = eTempTbl state."fanTblYest${dev.id}" = fanTbl } // DAY CHANGE if(!state."today${dev.id}" || state."today${dev.id}" != todayDay) { state."today${dev.id}" = todayDay state."tempTblYest${dev.id}" = tempTbl state."oprStTblYest${dev.id}" = oprStTbl state."humTblYest${dev.id}" = humTbl state."cspTblYest${dev.id}" = cspTbl state."hspTblYest${dev.id}" = hspTbl state."eTempTblYest" = eTempTbl state."fanTblYest${dev.id}" = fanTbl state."tempTbl${dev.id}" = [] state."oprStTbl${dev.id}" = [] state."humTbl${dev.id}" = [] state."cspTbl${dev.id}" = [] state."hspTbl${dev.id}" = [] state."eTempTbl" = [] state."fanTbl${dev.id}" = [] updateOperatingHistory(today, dev) } //initHistoryStore(dev) // ERSTODO DEBUGGING //updateOperatingHistory(today, dev) // ERSTODO DEBUGGING addNewData(dev) //def bb = getHistoryStore(dev) // ERSTODO DEBUGGING } Boolean getCanHeat(dev) { def t0 = dev.currentState("canHeat")?.value return t0?.toString() == "false" ? false : true } Boolean getCanCool(dev) { def t0 = dev.currentState("canCool")?.value return t0?.toString() == "false" ? false : true } Boolean getHasFan(dev) { def t0 = dev.currentState("hasFan")?.value return t0?.toString() == "false" ? false : true } String getHasLeaf(dev) { def t0 = dev.currentState("hasLeaf")?.value return !t0 ? "unknown" : t0?.toString() } private cast(value, dataType) { switch(dataType) { case "number": if (value == null) return (Integer) 0 if (value instanceof String) { if (value.isInteger()) return value.toInteger() if (value.isFloat()) return (Integer) Math.floor(value.toFloat()) if (value in trueStrings) return (Integer) 1 } Integer result = (Integer) 0 try { result = (Integer) value } catch(all) { result = (Integer) 0 } return result ? result : (Integer) 0 case "decimal": if (value == null) return (Float) 0 if (value instanceof String) { if (value.isFloat()) return (Float) value.toFloat() if (value.isInteger()) return (Float) value.toInteger() if (value in trueStrings) return (Float) 1 } Float result = (Float) 0 try { result = (Float) value } catch(all) { } return result ? result : (Float) 0 } } def getApiXUData(dev) { def obs = [:] if(state.obs) { obs = state.obs String t0 = "${obs.current.last_updated}" String t1 = formatDt(Date.parse("yyyy-MM-dd HH:mm", t0)) // def start = Date.parse("E MMM dd HH:mm:ss z yyyy", strtDate).getTime() // def localMidNight = Date.parse("yyyy-MM-dd HH:mm", t0).getTime() // def localMidNightf = Date.parse("yyyy-MM-dd hh:mm a", t0) Integer s = GetTimeDiffSeconds(t1, null, "getApiXUData").toInteger() if(s > (60*60*4)) { // we are doing this primarily for forecasts stateRemove("obs") } else return obs } def t0 = dev.currentState("apiXUquery")?.value if(t0) { def myUri = t0 def params = [ uri: myUri ] try { httpGet(params) { resp -> if (resp?.data) { obs << resp.data; state.obs = obs return obs } else log.error "http call for ApiXU weather api did not return data: $resp"; } } catch (e) { log.error "http call failed for ApiXU weather api: $e" } // log.debug "$obs" } return obs } def getCoolTemp(dev) { def t0 = dev.currentState("coolingSetpoint")?.value return t0 ? t0 : 0 } def getHeatTemp(dev) { def t0 = dev.currentState("heatingSetpoint")?.value return t0 ? t0 : 0 } String getHvacState(dev) { def t0 = dev.currentState("thermostatOperatingState")?.value return !t0 ? "unknown" : t0?.toString() } def getHumidity(dev) { def t0 = dev.currentState("humidity")?.value return t0 ? t0 : 0 } String getFanMode(dev) { def t0 = dev.currentState("thermostatFanMode")?.value return !t0 ? "unknown" : t0?.toString() } String getTZ(dev) { def t0 = dev.currentState("tz_id")?.value return !t0 ? (String)null : t0?.toString() } /* 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") } */ void addNewData(dev) { def currentTemperature = getTemp(dev) def currentcoolSetPoint = getCoolTemp(dev) def currentheatSetPoint = getHeatTemp(dev) def currentoperatingState = getHvacState(dev) def currenthumidity = getHumidity(dev) def currentfanMode = getFanMode(dev) def temp0 def weather = parent.getSettingVal("weatherDevice") if(weather) { temp0 = getTemp(weather) } def currentExternal = temp0 def tempTbl = state."tempTbl${dev.id}" def oprStTbl = state."oprStTbl${dev.id}" def humTbl = state."humTbl${dev.id}" def cspTbl = state."cspTbl${dev.id}" def hspTbl = state."hspTbl${dev.id}" def eTempTbl = state."eTempTbl" def fanTbl = state."fanTbl${dev.id}" // add latest coolSetpoint & temperature readings for the graph Date newDate = new Date() Integer hr = newDate.format("H", location.timeZone) as Integer Integer mins = newDate.format("m", location.timeZone) as Integer //Logger("addNewData currentTemp: ${currentTemperature} tempTbl: ${tempTbl}", "trace") //Logger("addNewData ${dev.id} hr: ${hr} mins: ${mins}", "trace") state."tempTbl${dev.id}" = addValue(tempTbl, hr, mins, currentTemperature) state."oprStTbl${dev.id}" = addValue(oprStTbl, hr, mins, currentoperatingState) state."humTbl${dev.id}" = addValue(humTbl, hr, mins, currenthumidity) state."cspTbl${dev.id}" = addValue(cspTbl, hr, mins, currentcoolSetPoint) state."hspTbl${dev.id}" = addValue(hspTbl, hr, mins, currentheatSetPoint) state."eTempTbl" = addValue(eTempTbl, hr, mins, currentExternal) state."fanTbl${dev.id}" = addValue(fanTbl, hr, mins, currentfanMode) } def updateOperatingHistory(today, dev) { LogTrace("updateOperatingHistory(${today}, ${dev.id})...", "trace") Boolean dayChange = false Boolean monthChange = false Boolean yearChange = false Map hm = state."thermStor${dev.id}" if(hm == null) { log.error "hm is null" return } Integer dayNum = today.format("u", location.timeZone).toInteger() // 1 = Monday,... 7 = Sunday Integer monthNum = today.format("MM", location.timeZone).toInteger() def yearNum = today[Calendar.YEAR] //def yearNum = today.format("YYYY", location.timeZone).toInteger() DOES NOT WORK if(hm.cDy == null || hm.cDy < 1 || hm.cDy > 7) { Logger("hm.cDy is invalid (${hm.cDy})", "error") return } if(dayNum == null || dayNum < 1 || dayNum > 7) { Logger("dayNum is invalid (${dayNum})", "error") return } if(monthNum == null || monthNum < 1 || monthNum > 12) { Logger("monthNum is invalid (${monthNum})", "error") return } LogTrace("updateOperatingHistory: dayNum: ${dayNum} cDy ${hm.cDy} | monthNum: ${monthNum} curMon ${hm.curMon} | yearNum: ${yearNum} curYr: ${hm.curYr}") if(dayNum != hm.cDy) { dayChange = true } if(monthNum != hm.curMon) { monthChange = true } if(yearNum != hm.curYr) { yearChange = true } if(dayChange) { stateRemove("obs") // get new forecasts // try { Long Op_cusage = getSumUsage(state."oprStTblYest${dev.id}", "cooling") Long Op_husage = getSumUsage(state."oprStTblYest${dev.id}", "heating") Long OpIdle = getSumUsage(state."oprStTblYest${dev.id}", "idle") Long Op_fo = getSumUsage(state."oprStTblYest${dev.id}", "fan only") Long FanOn = getSumUsage(state."fanTblYest${dev.id}", "on") Long FanAuto = getSumUsage(state."fanTblYest${dev.id}", "auto") Logger("FanOn ${FanOn} FanAuto: ${FanAuto} OpIdle: ${OpIdle} cool: ${Op_cusage} heat: ${Op_husage} fanonly: ${Op_fo}") hm."OpSt_Day${hm.cDy}_c" = Op_cusage hm."OpSt_Day${hm.cDy}_h" = Op_husage hm."OpSt_Day${hm.cDy}_i" = OpIdle hm."OpSt_Day${hm.cDy}_fo" = Op_fo hm."FanM_Day${hm.cDy}_On" = FanOn hm."FanM_Day${hm.cDy}_auto" = FanAuto hm.cDy = dayNum hm.OpSt_DWago_c = hm."OpSt_Day${hm.cDy}_c" hm.OpSt_DWago_h = hm."OpSt_Day${hm.cDy}_h" hm.OpSt_DWago_i = hm."OpSt_Day${hm.cDy}_i" hm.OpSt_DWago_fo = hm."OpSt_Day${hm.cDy}_fo" hm.FanM_DWago_On = hm."FanM_Day${hm.cDy}_On" hm.FanM_DWago_auto = hm."FanM_Day${hm.cDy}_auto" hm."OpSt_Day${hm.cDy}_c" = 0L hm."OpSt_Day${hm.cDy}_h" = 0L hm."OpSt_Day${hm.cDy}_i" = 0L hm."OpSt_Day${hm.cDy}_fo" = 0L hm."FanM_Day${hm.cDy}_On" = 0L hm."FanM_Day${hm.cDy}_auto" = 0L Long t1 = hm?."OpSt_M${hm.curMon}_c"?.toInteger() ?: 0L hm."OpSt_M${hm.curMon}_c" = t1 + Op_cusage t1 = hm?."OpSt_M${hm.curMon}_h"?.toInteger() ?: 0L hm."OpSt_M${hm.curMon}_h" = t1 + Op_husage t1 = hm?."OpSt_M${hm.curMon}_i"?.toInteger() ?: 0L hm."OpSt_M${hm.curMon}_i" = t1 + OpIdle t1 = hm?."OpSt_M${hm.curMon}_fo"?.toInteger() ?: 0L hm."OpSt_M${hm.curMon}_fo" = t1 + Op_fo t1 = hm?."FanM_M${hm.curMon}_On"?.toInteger() ?: 0L hm."FanM_M${hm.curMon}_On" = t1 + FanOn t1 = hm?."FanM_M${hm.curMon}_auto"?.toInteger() ?: 0L hm."FanM_M${hm.curMon}_auto" = t1 + FanAuto if(monthChange) { hm.curMon = monthNum hm.OpSt_MYago_c = hm."OpSt_M${hm.curMon}_c" hm.OpSt_MYago_h = hm."OpSt_M${hm.curMon}_h" hm.OpSt_MYago_i = hm."OpSt_M${hm.curMon}_i" hm.OpSt_MYago_fo = hm."OpSt_M${hm.curMon}_fo" hm.FanM_MYago_On = hm."FanM_M${hm.curMon}_On" hm.FanM_MYago_auto = hm."FanM_M${hm.curMon}_auto" hm."OpSt_M${hm.curMon}_c" = 0L hm."OpSt_M${hm.curMon}_h" = 0L hm."OpSt_M${hm.curMon}_i" = 0L hm."FanM_M${hm.curMon}_On" = 0L hm."FanM_M${hm.curMon}_auto" = 0L } t1 = hm?.OpSt_thisY_c?.toInteger() ?: 0L hm.OpSt_thisY_c = t1 + Op_cusage t1 = hm?.OpSt_thisY_h?.toInteger() ?: 0L hm.OpSt_thisY_h = t1 + Op_husage t1 = hm?.OpSt_thisY_i?.toInteger() ?: 0L hm.OpSt_thisY_i = t1 + OpIdle t1 = hm?.OpSt_thisY_fo?.toInteger() ?: 0L hm.OpSt_thisY_fo = t1 + Op_fo t1 = hm?.FanM_thisY_On?.toInteger() ?: 0L hm.FanM_thisY_On = t1 + FanOn t1 = hm?.FanM_thisY_auto?.toInteger() ?: 0L hm.FanM_thisY_auto = t1 + FanAuto if(yearChange) { hm.curYr = yearNum hm.OpSt_lastY_c = hm.OpSt_thisY_c hm.OpSt_lastY_h = hm.OpSt_thisY_h hm.OpSt_lastY_i = hm.OpSt_thisY_i hm.OpSt_lastY_fo = hm.OpSt_thisY_fo hm.FanM_lastY_On = hm.FanM_thisY_On hm.FanM_lastY_auto = hm.FanM_thisY_auto hm.OpSt_thisY_c = 0L hm.OpSt_thisY_h = 0L hm.OpSt_thisY_i = 0L hm.OpSt_thisY_fo = 0L hm.FanM_thisY_On = 0L hm.FanM_thisY_auto = 0L } state."thermStor${dev.id}" = hm /* } catch (ex) { //state.eric = 0 // force clear of stats //state.remove("thermStor${dev.id}") log.error "updateOperatingHistory Exception:", ex } */ } } def getSumUsage(table, String strtyp) { //log.trace "getSumUsage...$strtyp Table size: ${table?.size()}" Long totseconds = 0L Long newseconds = 0L Integer hr Integer mins String myval Integer lasthr = 0 Integer lastmins = 0 Boolean counting = false Boolean firsttime = true Integer strthr Integer strtmin table.sort { a, b -> a[0] as Integer <=> b[0] as Integer ?: a[1] as Integer <=> b[1] as Integer ?: a[2] <=> b[2] } //log.trace "$table" table.each() { hr = it[0].toInteger() mins = it[1].toInteger() myval = it[2].toString() //log.debug "${it[0]} ${it[1]} ${it[2]}" if(myval == strtyp) { if(!counting) { strthr = firstime ? lasthr : hr strtmin = firsttime ? lastmins : mins counting = true } } else if(counting) { newseconds = ((hr * 60 + mins) - (strthr * 60 + strtmin)) * 60 totseconds += newseconds counting = false //log.debug "found $strtyp starthr: $strthr startmin: $strtmin newseconds: $newseconds totalseconds: $totseconds" } firsttime = false } if(counting) { Date newDate = new Date() lasthr = newDate.format("H", location.timeZone).toInteger() lastmins = newDate.format("m", location.timeZone).toInteger() if( (hr*60+mins > lasthr*60+lastmins) ) { lasthr = 24 lastmins = 0 } newseconds = ((lasthr * 60 + lastmins) - (strthr * 60 + strtmin)) * 60 totseconds += newseconds //log.debug "still counting found $strtyp lasthr: $lasthr lastmins: $lastmins starthr: $strthr startmin: $strtmin newseconds: $newseconds totalseconds: $totseconds" } //log.info "$strtyp totseconds: $totseconds" return totseconds } void initHistoryStore(dev) { LogTrace("initHistoryStore($dev.id)...", "trace") def mytimeZone = location.timeZone Date today = new Date() Integer dayNum = today.format("u", mytimeZone) as Integer // 1 = Monday,... 7 = Sunday Integer monthNum = today.format("MM", mytimeZone) as Integer Integer yearNum = today[Calendar.YEAR] //def yearNum = today.format("YYYY", mytimeZone) as Integer DOES NOT WORK LogTrace("initHIstoryStore: dayNum: ${dayNum} | monthNum: ${monthNum} | yearNum: ${yearNum} ") //dayNum = 6 // ERSTODO DEBUGGING Map thermStor = [ "cDy": dayNum, "curMon": monthNum, "curYr": yearNum, //"tz": mytimeZone, OpSt_DWago_c: 0L, OpSt_DWago_h: 0L, OpSt_DWago_i: 0L, OpSt_DWago_fo: 0L, OpSt_MYago_c: 0L, OpSt_MYago_h: 0L, OpSt_MYago_i: 0L, OpSt_MYago_fo: 0L, OpSt_thisY_c: 0L, OpSt_thisY_h: 0L, OpSt_thisY_i: 0L, OpSt_thisY_fo: 0L, OpSt_lastY_c: 0L, OpSt_lastY_h: 0L, OpSt_lastY_i: 0L, OpSt_lastY_fo: 0L, FanM_DWago_On: 0L, FanM_DWago_auto: 0L, FanM_MYago_On: 0L, FanM_MYago_auto: 0L, FanM_thisY_On: 0L, FanM_thisY_auto: 0L, FanM_lastY_On: 0L, FanM_lastY_auto: 0L ] for(Integer i = 1; i <= 7; i++) { thermStor << ["OpSt_Day${i}_c": 0L, "OpSt_Day${i}_h": 0L, "OpSt_Day${i}_i": 0L, "OpSt_Day${i}_fo": 0L] thermStor << ["FanM_Day${i}_On": 0L, "FanM_Day${i}_auto": 0L] } for(Integer i = 1; i <= 12; i++) { thermStor << ["OpSt_M${i}_c": 0L, "OpSt_M${i}_h": 0L, "OpSt_M${i}_i": 0L, "OpSt_M${i}_fo": 0L] thermStor << ["FanM_M${i}_On": 0L, "FanM_M${i}_auto": 0L] } //log.debug "initHistoryStore thermStor${dev.id}: $thermStor" state."thermStor${dev.id}" = thermStor } String showChartHtml(Integer devNum, dev) { String tempStr = getTempUnitStr() Boolean canHeat = getCanHeat(dev) //Logger("showChart 0") Boolean canCool = getCanCool(dev) Boolean hasFan = getHasFan(dev) Boolean has_weather = extWeatTempAvail() String commastr = has_weather ? "," : "" //Logger("showChart 1") String coolstr1 String coolstr2 String coolstr3 if(canCool) { coolstr1 = "data.addColumn('number', 'CoolSP');" coolstr2 = getDataTString(5,dev) coolstr3 = "4: {targetAxisIndex: 1, type: 'line', color: '#85AAFF', lineWidth: 1}," } String heatstr1 String heatstr2 String heatstr3 //Logger("showChart 2") if(canHeat) { heatstr1 = "data.addColumn('number', 'HeatSP');" heatstr2 = getDataTString(6,dev) heatstr3 = "5: {targetAxisIndex: 1, type: 'line', color: '#FF4900', lineWidth: 1}${commastr}" } String weathstr1 = "data.addColumn('number', 'ExtTmp');" String weathstr2 = getDataTString(7,dev) String weathstr3 = "6: {targetAxisIndex: 1, type: 'line', color: '#000000', lineWidth: 1}" if(state.has_weather) { weathstr1 = "data.addColumn('number', 'ExtTmp');" weathstr2 = getDataTString(7,dev) weathstr3 = "6: {targetAxisIndex: 1, type: 'line', color: '#000000', lineWidth: 1}" } if(canCool && !canHeat) { coolstr3 = "4: {targetAxisIndex: 1, type: 'line', color: '#85AAFF', lineWidth: 1}${commastr}" } if(!canCool && canHeat) { heatstr3 = "4: {targetAxisIndex: 1, type: 'line', color: '#FF4900', lineWidth: 1}${commastr}" } if(!canCool) { coolstr1 = "" coolstr2 = "" coolstr3 = "" weathstr3 = "5: {targetAxisIndex: 1, type: 'line', color: '#000000', lineWidth: 1}" } if(!canHeat) { heatstr1 = "" heatstr2 = "" heatstr3 = "" weathstr3 = "5: {targetAxisIndex: 1, type: 'line', color: '#000000', lineWidth: 1}" } if(!has_weather) { weathstr1 = "" weathstr2 = "" weathstr3 = "" } //Logger("showChart 3") def minval minval = has_weather ? getMinTemp("tempTblYest${dev.id}", "tempTbl${dev.id}", "eTempTbl") : getMinTemp("tempTblYest${dev.id}", "tempTbl${dev.id}") //def minval = getTMinTemp() def minstr = "minValue: ${minval}," //def minval = has_weather ? getMinTemp("tempTblYest", "tempTbl", "eTempTbl") : getMinTemp("tempTblYest", "tempTbl") def maxval maxval = has_weather ? getMaxTemp("tempTblYest${dev.id}", "tempTbl${dev.id}", "eTempTbl") : getMaxTemp("tempTblYest${dev.id}", "tempTbl${dev.id}") //def maxval = getTMaxTemp() def maxstr = "maxValue: ${maxval}," def differ = maxval - minval minstr = "minValue: ${(minval - (wantMetric() ? 2:5))}," maxstr = "maxValue: ${(maxval + (wantMetric() ? 2:5))}," //Logger("showChart 4") def uData = getTodaysUsage(dev) def thData = (uData?.heating?.tSec.toLong()/3600).toDouble().round(0) def tcData = (uData?.cooling?.tSec.toLong()/3600).toDouble().round(0) def tiData = (uData?.idle?.tSec.toLong()/3600).toDouble().round(0) def tfData = (uData?.fanonly?.tSec.toLong()/3600).toDouble().round(0) def tfoData = (uData?.fanOn?.tSec.toLong()/3600).toDouble().round(0) def tfaData = (uData?.fanAuto?.tSec.toLong()/3600).toDouble().round(0) //Logger("showChart 5") //Month Chart Section uData = getMonthsUsage(null, dev) def mhData = (uData?.heating?.tSec.toLong()/3600).toDouble().round(0) def mcData = (uData?.cooling?.tSec.toLong()/3600).toDouble().round(0) def miData = (uData?.idle?.tSec.toLong()/3600).toDouble().round(0) def mfData = (uData?.fanonly?.tSec.toLong()/3600).toDouble().round(0) def mfoData = (uData?.fanOn?.tSec.toLong()/3600).toDouble().round(0) def mfaData = (uData?.fanAuto?.tSec.toLong()/3600).toDouble().round(0) //Logger("showChart 5a") Integer useTabListSize = 0 if(canHeat) { useTabListSize = useTabListSize+1 } if(canCool) { useTabListSize = useTabListSize+1 } if(hasFan) { useTabListSize = useTabListSize+1 } String lStr = "" //Last 3 Months and Today Section def grpUseData = getLast3MonthsUsageMap(dev) def m1Data = [] def m2Data = [] def m3Data = [] //ERSTODO fix for fanonly //Logger("showChart 6") grpUseData?.each { mon -> def data = mon?.value def heat = data?.heating ? (data?.heating?.tSec.toLong()/3600).toDouble().round(0) : 0 def cool = data?.cooling ? (data?.cooling?.tSec.toLong()/3600).toDouble().round(0) : 0 def idle = data?.idle ? (data?.idle?.tSec.toLong()/3600).toDouble().round(0) : 0 def fanonly = data?.fanonly ? (data?.fanonly?.tSec.toLong()/3600).toDouble().round(0) : 0 def fanOn = data?.fanOn ? (data?.fanOn?.tSec.toLong()/3600).toDouble().round(0) : 0 def fanAuto = data?.fanAuto ? (data?.fanAuto?.tSec.toLong()/3600).toDouble().round(0) : 0 def mName = getMonthNumToStr(mon?.key) lStr += "\n$mName Usage - Idle: ($idle) | Heat: ($heat) | Cool: ($cool) | Fanonly: (${fanonly}) FanOn: ($fanOn) | FanAuto: ($fanAuto)" Integer iNum = 1 if(data?.idle?.iNum) { iNum = data?.idle?.iNum.toInteger() } else if(data?.heating?.iNum) {iNum = data?.heating?.iNum.toInteger() } else if(data?.cooling?.iNum == 1) { iNum = data?.cooling?.iNum.toInteger() } else if(data?.fanonly?.iNum == 1) { iNum = data?.fanonly?.iNum.toInteger() } else if(data?.fanOn?.iNum == 1) { iNum = data?.fanOn?.iNum.toInteger() } if(iNum == 1) { m1Data.push("'$mName'") if(canHeat) { m1Data.push("${heat}") } if(canCool) { m1Data.push("${cool}") } if(hasFan) { m1Data.push("${fanOn}") } } if(iNum == 2) { m2Data.push("'$mName'") if(canHeat) { m2Data.push("${heat}") } if(canCool) { m2Data.push("${cool}") } if(hasFan) { m2Data.push("${fanOn}") } } if(iNum == 3) { m3Data.push("'$mName'") if(canHeat) { m3Data.push("${heat}") } if(canCool) { m3Data.push("${cool}") } if(hasFan) { m3Data.push("${fanOn}") } } } //Logger("showChart 7") lStr += "\nToday's Usage - Idle: ($tiData) | Heat: ($thData) | Cool: ($tcData) | FanOn: ($tfoData) | FanAuto: ($tfaData)" def mUseHeadStr = ["'Month'"] if(canHeat) { mUseHeadStr.push("'Heat'") } if(canCool) { mUseHeadStr.push("'Cool'") } if(hasFan) { mUseHeadStr.push("'FanOn'") } def tdData = ["'Today'"] if(canHeat) { tdData.push("${thData}") } if(canCool) { tdData.push("${tcData}") } if(hasFan) { tdData.push("${tfoData}") } lStr += "\nToday Data List: $tdData\n\n" //Logger("showChart 8") LogTrace("showChart: " + lStr) String data = """

Event History

Usage History

""" return data /* """ */ } //ERS String getAppEndpointUrl(subPath) { return "${getFullApiServerUrl()}${subPath ? "/${subPath}" : ""}?access_token=${state.access_token}" } String getLocalEndpointUrl(subPath) { return "${getFullLocalApiServerUrl()}${subPath ? "/${subPath}" : ""}?access_token=${state.access_token}" } Boolean getAccessToken() { try { if(!state.access_token) { state.access_token = createAccessToken() } else { return true } } catch (ex) { def msg = "Error: OAuth is not Enabled for ${app?.name}!." // sendPush(msg) log.error "getAccessToken Exception ${ex?.message}" LogAction("getAccessToken Exception | $msg", "warn", true) return false } } void enableOauth() { def params = [ uri: "http://localhost:8080/app/edit/update?_action_update=Update&oauthEnabled=true&id=${app.appTypeId}", headers: ['Content-Type':'text/html;charset=utf-8'] ] try { httpPost(params) { resp -> //LogTrace("response data: ${resp.data}") } } catch (e) { log.debug "enableOauth something went wrong: ${e}" } } void resetAppAccessToken(reset) { if(reset != true) { return } LogAction("Resetting Access Token....", "info", true) //revokeAccessToken() state.access_token = null state.accessToken = null if(getAccessToken()) { LogAction("Reset Access Token... Successful", "info", true) settingUpdate("resetAppAccessToken", "false", "bool") } } def getSomeWData(dev, devpoll = false) { //LogTrace("getSomeWData ${app} ${dev?.label} ${dev.id}") //def todayDay = new Date().format("dd",getTimeZone()) def mytz = getTZ(dev) def tZ = myTz ? TimeZone.getTimeZone(mytz) : location.timeZone def todayDay = new Date().format("dd",tZ) //Logger("getSomeWData: todayDay: ${todayDay} ${dev.id} ${tZ}") if (state."WtempTbl${dev.id}" == null) { //getSomeOldData(devpoll) state."WtempTbl${dev.id}" = [] state."WdewTbl${dev.id}" = [] state."WhumTbl${dev.id}" = [] addNewWData(dev, tZ) } def tempTbl = state."WtempTbl${dev.id}" def dewTbl = state."WdewTbl${dev.id}" def humTbl = state."WhumTbl${dev.id}" if (state."WtempTblYest${dev.id}"?.size() == 0) { state."WtempTblYest${dev.id}" = tempTbl state."WdewTblYest${dev.id}" = dewTbl state."WhumTblYest${dev.id}" = humTbl } if (!state."Wtoday${dev.id}" || state."Wtoday${dev.id}" != todayDay) { state."Wtoday${dev.id}" = todayDay state."WdewTblYest${dev.id}" = dewTbl state."WtempTblYest${dev.id}" = tempTbl state."WhumTblYest${dev.id}" = humTbl state."WtempTbl${dev.id}" = [] state."WdewTbl${dev.id}" = [] state."WhumTbl${dev.id}" = [] } addNewWData(dev, tZ) } def addNewWData(dev, tZ) { // add latest weather humidity, dewpoint & temperature readings for the graph def currentTemperature = getTemp(dev) def currentDewpoint = getDewpoint(dev) def currentHumidity = getHumidity(dev) def tempTbl = state."WtempTbl${dev.id}" def dewTbl = state."WdewTbl${dev.id}" def humTbl = state."WhumTbl${dev.id}" Date newDate = new Date() if(newDate == null) { Logger("got null for new Date()") } Integer hr = newDate.format("H", location.timeZone) as Integer Integer mins = newDate.format("m", location.timeZone) as Integer //Logger("addNewWData currentTemp: ${currentTemperature} WtempTbl: ${tempTbl}", "trace") //Logger("addNewWData ${dev.id} ${tZ} hr: ${hr} mins: ${mins}", "trace") state."WtempTbl${dev.id}" = addValue(tempTbl, hr, mins, currentTemperature) state."WdewTbl${dev.id}" = addValue(dewTbl, hr, mins, currentDewpoint) state."WhumTbl${dev.id}" = addValue(humTbl, hr, mins, currentHumidity) } List addValue(List table, Integer hr, Integer mins, val) { List newTable = table if(table?.size() > 2) { def last = table.last()[2] def secondtolast = table[-2][2] if(val == last && val == secondtolast) { newTable = table.take(table.size() - 1) } } newTable.add([hr, mins, val]) return newTable } // getStartTime("dewTbl", "dewTblYest")) Integer getStartTime(tbl1, tbl2) { Integer startTime = 24 if (state."${tbl1}"?.size()) { startTime = state."${tbl1}".min{it[0].toInteger()}[0].toInteger() } if (state."${tbl2}"?.size()) { startTime = Math.min(startTime, state."${tbl2}".min{it[0].toInteger()}[0].toInteger()) } return startTime } // getMinTemp("tempTblYest", "tempTbl", "dewTbl", "dewTblYest")) def getMinTemp(tbl1, tbl2, tbl3=null, tbl4=null) { def list = [] if (state."${tbl1}"?.size() > 0) { list.add(state."${tbl1}"?.min { it[2] }[2]) } if (state."${tbl2}"?.size() > 0) { list.add(state."${tbl2}".min { it[2] }[2]) } if (state."${tbl3}"?.size() > 0) { list.add(state."${tbl3}".min { it[2] }[2]) } if (state."${tbl4}"?.size() > 0) { list.add(state."${tbl4}".min { it[2] }[2]) } //LogAction("getMinTemp: ${list.min()} result: ${list}", "trace") return list?.min() } // getMaxTemp("tempTblYest", "tempTbl", "dewTbl", "dewTblYest")) def getMaxTemp(tbl1, tbl2, tbl3=null, tbl4=null) { def list = [] if (state."${tbl1}"?.size() > 0) { list.add(state."${tbl1}".max { it[2] }[2]) } if (state."${tbl2}"?.size() > 0) { list.add(state."${tbl2}".max { it[2] }[2]) } if (state."${tbl3}"?.size() > 0) { list.add(state."${tbl3}".max { it[2] }[2]) } if (state."${tbl4}"?.size() > 0) { list.add(state."${tbl4}".max { it[2] }[2]) } //LogAnction("getMaxTemp: ${list.max()} result: ${list}", "trace") return list?.max() } String getWDDeviceTile(Integer devNum, dev) { //log.warn "in getWDDeviceTile" def obs //= getApiXUData(dev) // try { /* if(!obs) { //state.curWeather || !state.curForecast) { return hideWeatherHtml() } */ //Logger("W1") String updateAvail = !state.updateAvailable ? "" : """
Device Update Available!
""" String clientBl = state.clientBl ? """
Your Manager client has been blacklisted!\nPlease contact the Nest Manager developer to get the issue resolved!!!
""" : "" String obsrvTime = "Last Updated:\n${dev.getDataValue("fotime")}" //Logger("W2") //log.warn "obs $obsrvTime" String mainHtml = """

Current Weather Conditions

${dev.getDataValue("city")}

Feels Like: ${getFeelslike(dev)}
Precip: ${getPrecip(dev)}
Humidity: ${getHumidity(dev)}%
Dew Point: ${getDewpoint(dev)}${getTempUnitStr()}
Pressure: ${getPressure(dev)}
UV Index: ${dev.currentState("ultravioletIndex")?.value}
Visibility: ${getVisibility(dev)}
Lux: ${getLux(dev)}
Sunrise: ${getSunrise(dev, obs)}
Sunset: ${getSunset(dev, obs)}
Wind: ${getWind(dev)}
Moon Phase: ${getMoonPhase(0, obs, dev)}

${getTemp(dev)}

${getConditionText(obs,dev)}

${forecastDay(0, obs, dev)}
${forecastDay(1, obs, dev)}
${forecastDay(2, obs, dev)}
${forecastDay(3, obs, dev)}
${forecastDay(4, obs, dev)}
${forecastDay(5, obs, dev)}

Tap Icon to View Forecast

${historyGraphHtml(devNum,dev)}
""" // Logger("getMoonPhase: ${getMoonPhase(0, obs, dev)}") // Logger("getMoonPhase: ${getMoonPhase(1, obs, dev)}") // Logger("getMoonPhase: ${getMoonPhase(2, obs, dev)}") // Logger("getMoonPhase: ${getMoonPhase(3, obs, dev)}") // Logger("getMoonPhase: ${getMoonPhase(4, obs, dev)}") // Logger("getMoonPhase: ${getMoonPhase(5, obs, dev)}") // Logger("getMoonPhase: ${getMoonPhase(6, obs, dev)}") /* """ */ // render contentType: "text/html", data: mainHtml, status: 200 /* } catch (ex) { log.error "getDeviceTile Exception:", ex //exceptionDataHandler(ex?.message, "getDeviceTile") } */ } // weather ERS String getWDeviceTile(Integer devNum, dev) { def obs = getApiXUData(dev) // try { if(!obs) { //state.curWeather || !state.curForecast) { return hideWeatherHtml() } //Logger("W1") String updateAvail = !state.updateAvailable ? "" : """
Device Update Available!
""" String clientBl = state.clientBl ? """
Your Manager client has been blacklisted!\nPlease contact the Nest Manager developer to get the issue resolved!!!
""" : "" String obsrvTime = "Last Updated:\n${dev?.currentState("last_updated").value}" //Logger("W2") String mainHtml = """

Current Weather Conditions

${dev?.currentState("location").value}

Feels Like: ${getFeelslike(dev)}
Precip: ${getPrecip(dev)}
Humidity: ${getHumidity(dev)}%
Dew Point: ${getDewpoint(dev)}${getTempUnitStr()}
Pressure: ${getPressure(dev)}
UV Index: ${dev.currentState("ultravioletIndex")?.value}
Visibility: ${getVisibility(dev)}
Lux: ${getLux(dev)}
Sunrise: ${getSunrise(dev, obs)}
Sunset: ${getSunset(dev, obs)}
Wind: ${getWind(dev)}
Moon Phase: ${getMoonPhase(0, obs)}

${getTemp(dev)}

${getConditionText(obs,dev)}

${forecastDay(0, obs)}
${forecastDay(1, obs)}
${forecastDay(2, obs)}
${forecastDay(3, obs)}
${forecastDay(4, obs)}
${forecastDay(5, obs)}

Tap Icon to View Forecast

${historyGraphHtml(devNum,dev)}
""" // Logger("getMoonPhase: ${getMoonPhase(0, obs)}") // Logger("getMoonPhase: ${getMoonPhase(1, obs)}") // Logger("getMoonPhase: ${getMoonPhase(2, obs)}") // Logger("getMoonPhase: ${getMoonPhase(3, obs)}") // Logger("getMoonPhase: ${getMoonPhase(4, obs)}") // Logger("getMoonPhase: ${getMoonPhase(5, obs)}") // Logger("getMoonPhase: ${getMoonPhase(6, obs)}") /* """ */ // render contentType: "text/html", data: mainHtml, status: 200 /* } catch (ex) { log.error "getDeviceTile Exception:", ex //exceptionDataHandler(ex?.message, "getDeviceTile") } */ } String getMoonPhase(Integer day, obs, dev) { if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ return getMoonPhase1(day, obs) }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") return dev.getDataValue("moonPhase") } } String getMoonPhase1(Integer day, obs) { //Logger("getMoon") if(!obs) { return "no data"} def astro = obs?.forecast?.forecastday[day]?.astro LogTrace("day $day astro: ${astro}") String t0 = "${obs?.forecast?.forecastday[day]?.date} ${astro?.sunrise}" Long sunRise = Date.parse("yyyy-MM-dd hh:mm a", t0).getTime() t0 = "${obs.forecast.forecastday[day].date} ${astro?.sunset}" Long sunSet = Date.parse("yyyy-MM-dd hh:mm a", t0).getTime() Long moonRise if(astro?.moonrise == "No moonrise") { t0 = "${obs?.forecast?.forecastday[day]?.date} 00:01 AM" moonRise = Date.parse("yyyy-MM-dd hh:mm a", t0).getTime() - 30*60*1000 // subtract 30 mins } else { t0 = "${obs?.forecast?.forecastday[day]?.date} ${astro.moonrise}" moonRise = Date.parse("yyyy-MM-dd hh:mm a", t0).getTime() } Long moonSet if(astro.moonset == "No moonset") { t0 = "${obs?.forecast?.forecastday[day]?.date} 11:59 PM" moonSet = Date.parse("yyyy-MM-dd hh:mm a", t0).getTime() + 30*60*1000 // add 30 mins } else { t0 = "${obs?.forecast?.forecastday[day]?.date} ${astro?.moonset}" moonSet = Date.parse("yyyy-MM-dd hh:mm a", t0).getTime() } t0 = "${obs?.forecast?.forecastday[day]?.date} 00:00 AM" Long localMidNight = Date.parse("yyyy-MM-dd hh:mm a", t0).getTime() // def localMidNightf = formatDt(Date.parse("yyyy-MM-dd hh:mm a", t0)) // Logger("localMidNight: ${localMidNightf}") t0 = "${obs?.forecast?.forecastday[day]?.date} 12:00 PM" def localNoon = Date.parse("yyyy-MM-dd hh:mm a", t0).getTime() t0 = 24/8*3600*1000 def t1 = 24/8*3600*1000 * 0.333 def compare = [ Math.abs(sunRise + t1 - moonRise)/(1000), // New Moon Math.abs(sunRise + t0 + t1 - moonRise)/(1000), // Waxing crescent Math.abs(localNoon + t1 - moonRise)/(1000), // First Quarter Math.abs(localNoon + t0 + t1 - moonRise)/(1000), // Waxing gibbous Math.abs(sunSet + t1 - moonRise)/(1000), // Full Math.abs(sunSet + t0 + t1 - moonRise)/(1000), // Waning gibbous Math.abs(localMidNight + t1 - moonRise)/(1000), // 3rd Quarter Math.abs(localMidNight + t0 + t1 - moonRise)/(1000) // Waning cresent ] def nearest = compare.min() Integer indx = compare.indexOf(nearest) String tstr Integer brightness switch (indx) { case 0: tstr = "New Moon" brightness = 0 break case 1: tstr = "Waxing cresent" brightness = 20 break case 2: tstr = "1st Quarter" brightness = 40 break case 3: tstr = "Waxing gibbous" brightness = 70 break case 4: tstr = "Full" brightness = 100 break case 5: tstr = "Waning gibbous" brightness = 70 break case 6: tstr = "3rd Quarter" brightness = 40 break case 7: tstr = "Waning cresent" brightness = 20 break } LogTrace("moon: $day ${compare[indx]} $indx $tstr $compare") return tstr } String getWeatherImg(Integer cond) { def aa = getWUIconName(cond,1) //def newCond = getWeatCondFromUrl(cond) //def url = "https://cdn.rawgit.com/tonesto7/nest-manager/master/Images/Weather/icons/black/${getWeatCondFromUrl(cond) ?: "unknown"}.svg" // https://console.bluemix.net/docs/api/content/services/Weather/images/30.png String url = "https://cdn.rawgit.com/tonesto7/nest-manager/master/Images/Weather/icons/black/${aa ? "${aa}" : "unknown"}.svg".toString() return url } String forecastDay(day, obs, dev=null) { //Logger("forecastDay $day") if(!obs) { return "no data"} String dayName = "${obs.forecast.forecastday[day].date}
" String foreImgB64 = getWeatherImg(obs.forecast.forecastday[day].day.condition.code) String forecastImageLink = """
""" String forecastTxt = "

${obs.forecast.forecastday[day].day.condition.text}" def t0 = (!wantMetric() ? obs.forecast.forecastday[day].day.mintemp_f : obs.forecast.forecastday[day].day.mintemp_c) def t1 = (!wantMetric() ? obs.forecast.forecastday[day].day.maxtemp_f : obs.forecast.forecastday[day].day.maxtemp_c) forecastTxt += "
Temp low: ${t0} Temp high: ${t1}" t0 = (!wantMetric() ? obs.forecast.forecastday[day].day.maxwind_mph : obs.forecast.forecastday[day].day.maxwind_kph) forecastTxt += "
Wind: ${t0}" t0 = (!wantMetric() ? obs.forecast.forecastday[day].day.totalprecip_in : obs.forecast.forecastday[day].day.totalprecip_mm) forecastTxt += "
Precipitation: ${t0}" t0 = "
Moon Phase: ${getMoonPhase(day, obs, dev)}" forecastTxt += t0 String modalHead = "" return dayName + forecastImageLink + modalHead + modalTitle + forecastImage + forecastTxt + modalClose } String getWUIconName(Integer condition_code, Integer is_day) { Integer cC = condition_code String wuIcon = (conditionFactor[cC] ? conditionFactor[cC][2] : '') if (is_day != 1 && wuIcon) wuIcon = 'nt_' + wuIcon; return wuIcon } @Field final Map conditionFactor = [ 1000: ['Sunny', 1, 'sunny'], 1003: ['Partly cloudy', 0.8, 'partlycloudy'], 1006: ['Cloudy', 0.6, 'cloudy'], 1009: ['Overcast', 0.5, 'cloudy'], 1030: ['Mist', 0.5, 'fog'], 1063: ['Patchy rain possible', 0.8, 'chancerain'], 1066: ['Patchy snow possible', 0.6, 'chancesnow'], 1069: ['Patchy sleet possible', 0.6, 'chancesleet'], 1072: ['Patchy freezing drizzle possible', 0.4, 'chancesleet'], 1087: ['Thundery outbreaks possible', 0.2, 'chancetstorms'], 1114: ['Blowing snow', 0.3, 'snow'], 1117: ['Blizzard', 0.1, 'snow'], 1135: ['Fog', 0.2, 'fog'], 1147: ['Freezing fog', 0.1, 'fog'], 1150: ['Patchy light drizzle', 0.8, 'rain'], 1153: ['Light drizzle', 0.7, 'rain'], 1168: ['Freezing drizzle', 0.5, 'sleet'], 1171: ['Heavy freezing drizzle', 0.2, 'sleet'], 1180: ['Patchy light rain', 0.8, 'rain'], 1183: ['Light rain', 0.7, 'rain'], 1186: ['Moderate rain at times', 0.5, 'rain'], 1189: ['Moderate rain', 0.4, 'rain'], 1192: ['Heavy rain at times', 0.3, 'rain'], 1195: ['Heavy rain', 0.2, 'rain'], 1198: ['Light freezing rain', 0.7, 'sleet'], 1201: ['Moderate or heavy freezing rain', 0.3, 'sleet'], 1204: ['Light sleet', 0.5, 'sleet'], 1207: ['Moderate or heavy sleet', 0.3, 'sleet'], 1210: ['Patchy light snow', 0.8, 'flurries'], 1213: ['Light snow', 0.7, 'snow'], 1216: ['Patchy moderate snow', 0.6, 'snow'], 1219: ['Moderate snow', 0.5, 'snow'], 1222: ['Patchy heavy snow', 0.4, 'snow'], 1225: ['Heavy snow', 0.3, 'snow'], 1237: ['Ice pellets', 0.5, 'sleet'], 1240: ['Light rain shower', 0.8, 'rain'], 1243: ['Moderate or heavy rain shower', 0.3, 'rain'], 1246: ['Torrential rain shower', 0.1, 'rain'], 1249: ['Light sleet showers', 0.7, 'sleet'], 1252: ['Moderate or heavy sleet showers', 0.5, 'sleet'], 1255: ['Light snow showers', 0.7, 'snow'], 1258: ['Moderate or heavy snow showers', 0.5, 'snow'], 1261: ['Light showers of ice pellets', 0.7, 'sleet'], 1264: ['Moderate or heavy showers of ice pellets',0.3, 'sleet'], 1273: ['Patchy light rain with thunder', 0.5, 'tstorms'], 1276: ['Moderate or heavy rain with thunder', 0.3, 'tstorms'], 1279: ['Patchy light snow with thunder', 0.5, 'tstorms'], 1282: ['Moderate or heavy snow with thunder', 0.3, 'tstorms'] ] Boolean wantMetric() { return (getTemperatureScale() == "C") } String getTempUnitStr() { String tempStr = "\u00b0F" if ( wantMetric() ) { tempStr = "\u00b0C" } return tempStr } Float getTemp(dev) { def t0 = dev.currentState("temperature")?.value Float t1 = cast(t0, "decimal") return t1 } Integer getDewpoint(dev) { def t0 = dev.currentState("dewpoint")?.value Integer t1 = cast(t0, "number") return t1 } String getFeelslike(dev) { if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ return "${dev.currentState("feelsLike")?.value}${getTempUnitStr()}" }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") return "${dev.getDataValue("feelsLike")}${getTempUnitStr()}" } } String getPrecip(dev) { if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ String tstr = dev.currentState("precip_today")?.value.toString() if(wantMetric()) { return "${tstr} mm" } else { return "${tstr} in" } }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") tstr = dev.getDataValue("percentPrecip") return tstr+'%' } } String getPressure(dev) { String tstr = "" // " " + device.currentState("pressure_trend")?.value.toString() String tstr1 = dev.currentState("pressure")?.value.toString() if(wantMetric()) { return "${tstr1} mb ${tstr}" } else { return "${tstr1} in ${tstr}" } } String getVisibility(dev) { String tstr if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ tstr = dev.currentState("visibility")?.value.toString() }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") tstr = dev.getDataValue("vis") } if(wantMetric()) { return "${tstr} km" } else { return "${tstr} Miles" } } String getLux(dev) { String cur = dev.currentState("illuminance")?.value.toString() return cur } String getSunrise(dev, obs){ String cur if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ return obs ? "${obs?.forecast?.forecastday[0]?.astro?.sunrise}" : "" }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") return dev.getDataValue("riseTime") } } String getSunset(dev, obs){ String cur if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ return obs ? "${obs?.forecast?.forecastday[0]?.astro?.sunset}" : "" }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") return dev.getDataValue("setTime") } } String getWind(dev) { String cur = dev.currentState("wind")?.value.toString() String cur1 if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ cur1 = dev.currentState("wind_dir")?.value.toString() }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") cur1 = dev.getDataValue("wind_cardinal") } return "${cur1} at ${cur} ${wantMetric() ? "Kph" : "Mph"}" } String getConditionUrl(obs, dev=null){ if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ return getWeatherImg(obs?.current?.condition?.code) }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") return dev.getDataValue("condition_icon_url") } } String getConditionText(obs, dev){ if( dev?.typeName in ["ApiXU Weather Driver Min"] ){ return objs ? obs?.current?.condition?.text : "" }else{ //if( dev?.typeName in ["Nest Thermostat", "Nest Protect", "ApiXU Weather Driver Min", "DarkSky.net Weather Driver"]) Logger("found in") return dev.getDataValue("condition_text") } } String getDataString(Integer seriesIndex, dev) { String dataString = "" def dataTable = [] switch (seriesIndex) { case 1: // dataTable = state."WtempTblYest${dev.id}" dataTable = state."WtempTbl${dev.id}" break case 2: // dataTable = state."WdewTblYest${dev.id}" dataTable = state."WdewTbl${dev.id}" break case 3: // dataTable = state."WtempTbl${dev.id}" dataTable = state."WhumTbl${dev.id}" break case 4: // dataTable = state."WdewTbl${dev.id}" dataTable = state."WtempTblYest${dev.id}" break case 5: // dataTable = state."WhumTblYest${dev.id}" dataTable = state."WdewTblYest${dev.id}" break case 6: // dataTable = state."WhumTbl${dev.id}" dataTable = state."WhumTblYest${dev.id}" break } dataTable.each() { def dataArray = [[it[0],it[1],0],null,null,null,null,null,null] dataArray[seriesIndex] = it[2] dataString += dataArray?.toString() + "," } return dataString } String historyGraphHtml(Integer devNum, dev) { //Logger("HistoryG 1") String html = "" if(true) { if (state."WtempTbl${dev.id}"?.size() > 0 && state."WdewTbl${dev.id}"?.size() > 0) { String tempStr = getTempUnitStr() def minval = getMinTemp("WtempTblYest${dev.id}", "WtempTbl${dev.id}", "WdewTbl${dev.id}", "WdewTblYest${dev.id}") String minstr = "minValue: ${minval}," //Logger("HistoryG 1a") def maxval = getMaxTemp("WtempTblYest${dev.id}", "WtempTbl${dev.id}", "WdewTbl${dev.id}", "WdewTblYest${dev.id}") String maxstr = "maxValue: ${maxval}," //Logger("HistoryG 1b") def differ = maxval - minval //LogAction("differ ${differ}", "trace") minstr = "minValue: ${(minval - (wantMetric() ? 2:5))}," maxstr = "maxValue: ${(maxval + (wantMetric() ? 2:5))}," //Logger("HistoryG 2") html = """

Event History

""" } else { html = """

Event History



Waiting for more data to be collected

This may take at a couple hours

""" } } } String hideWeatherHtml() { String data = """



The Required Weather data is not available yet...



Please refresh this page after a couple minutes...





""" // render contentType: "text/html", data: data, status: 200 } Map getCarbonImg(b64=true, dev) { def carbonVal = dev.currentState("nestCarbonMonoxide")?.value //values in ST are tested, clear, detected //values from nest are ok, warning, emergency String img = "" String caption = "${carbonVal ? carbonVal?.toString().toUpperCase() : ""}".toString() String captionClass = "" switch(carbonVal) { case "warning": img = getImg("co2_warn_status.png") captionClass = "alarmWarnCap" break case "emergency": img = getImg("co2_emergency_status.png") captionClass = "alarmEmerCap" break default: img = getImg("co2_clear_status.png") captionClass = "alarmClearCap" break } return ["img":img, "caption": caption, "captionClass":captionClass] } Map getSmokeImg(b64=true, dev) { def smokeVal = dev.currentState("nestSmoke")?.value //values in ST are tested, clear, detected //values from nest are ok, warning, emergency String img = "" String caption = "${smokeVal ? smokeVal?.toString().toUpperCase() : ""}".toString() String captionClass = "" switch(smokeVal) { case "warning": img = getImg("smoke_warn_status.png") captionClass = "alarmWarnCap" break case "emergency": img = getImg("smoke_emergency_status.png") captionClass = "alarmEmerCap" break default: img = getImg("smoke_clear_status.png") captionClass = "alarmClearCap" break } return ["img":img, "caption": caption, "captionClass":captionClass] } String getImg(String imgName) { if(imgName) { return imgName ? "https://cdn.rawgit.com/tonesto7/nest-manager/master/Images/Devices/$imgName".toString() : "" } else { log.error "getImg Error: Missing imgName value..." } } String getProtDeviceTile(Integer devNum, dev) { // try { String battImg = (dev.currentState("batteryState")?.value == "replace") ? """""" : """""" //def battImg = (state.battVal == "low") ? """""" : """""" def testVal = dev.currentState("isTesting")?.value String testModeHTML = (testVal.toString() == "true") ? "

Test Mode

" : "" String updateAvail = !state.updateAvailable ? "" : """
Device Update Available!
""" String clientBl = state.clientBl ? """
Your Manager client has been blacklisted!\nPlease contact the Nest Manager developer to get the issue resolved!!!
""" : "" Map smokeImg = getSmokeImg(false, dev) Map carbonImg = getCarbonImg(false, dev) String onlineStatus = dev.currentState("onlineStatus")?.value String powerSource = dev.currentState("powerSource")?.value String apiStatus = dev.currentState("apiStatus")?.value String softwareVer = dev.currentState("softwareVer")?.value String lastConnection = dev.currentState("lastConnection")?.value String html = """ ${testModeHTML} ${clientBl} ${updateAvail}

Alarm Status

Smoke Detector Carbon Monoxide
${smokeImg?.caption} ${carbonImg?.caption}

Device Info

${onlineStatus.toString().capitalize()} ${apiStatus}
Network Status Power Type API Status
${powerSource != null ? powerSource.toString().capitalize() : "Not Available Yet"}
Firmware Version Last Check-In
v${softwareVer.toString()} ${lastConnection.toString()}
""" return html /* } catch (ex) { log.error "getDeviceTile Exception:", ex exceptionDataHandler(ex?.message, "getInfoHtml") } */ } def getTimeZone() { def tz = null if(location?.timeZone) { tz = location?.timeZone } if(!tz) { LogAction("getTimeZone: Hub or Nest TimeZone not found", "warn", true) } return tz } String getDtNow() { Date now = new Date() return formatDt(now) } String formatDt(Date dt) { def tf = new SimpleDateFormat("E MMM dd HH:mm:ss z yyyy") if(getTimeZone()) { tf.setTimeZone(getTimeZone()) } else { LogAction("HE TimeZone is not set; Please open your location and Press Save", "warn", true) } return tf.format(dt) } Long GetTimeDiffSeconds(String strtDate, String stpDate=(String)null, String methName=(String)null) { //LogTrace("[GetTimeDiffSeconds] StartDate: $strtDate | StopDate: ${stpDate ?: "Not Sent"} | MethodName: ${methName ?: "Not Sent"})") if((strtDate && !stpDate) || (strtDate && stpDate)) { //if(strtDate?.contains("dtNow")) { return 10000 } Date now = new Date() String stopVal = stpDate ? stpDate.toString() : formatDt(now) Long start = Date.parse("E MMM dd HH:mm:ss z yyyy", strtDate).getTime() Long stop = Date.parse("E MMM dd HH:mm:ss z yyyy", stopVal).getTime() Long diff = (Long) (stop - start) / 1000L LogTrace("[GetTimeDiffSeconds] Results for '$methName': ($diff seconds)") return diff } else { return null } } /************************************************************************************************ | LOGGING AND Diagnostic | *************************************************************************************************/ String lastN(String input, n) { return n > input?.size() ? input : input[-n..-1] } void LogTrace(String msg, String logSrc=(String)null) { Boolean trOn = (settings.showDebug && settings.advAppDebug) if(trOn) { Boolean logOn = (settings.enRemDiagLogging && state.enRemDiagLogging) //def theId = lastN(app?.id.toString(),5) //def theLogSrc = (logSrc == null) ? (parent ? "Automation-${theId}" : "NestManager") : logSrc Logger(msg, "trace", logSrc, logOn) } } void LogAction(String msg, String type="debug", Boolean showAlways=false, String logSrc=(String)null) { Boolean isDbg = settings.showDebug // def theId = lastN(app?.id.toString(),5) // def theLogSrc = (logSrc == null) ? (parent ? "Automation-${theId}" : "NestManager") : logSrc if(showAlways || (isDbg && !showAlways)) { Logger(msg, type, logSrc) } } void Logger(String msg, String type="debug", String logSrc=(String)null, Boolean noLog=false) { if(msg && type) { String labelstr = "" if(state.dbgAppndName == null) { def tval = parent ? parent.getSettingVal("dbgAppndName") : settings.dbgAppndName state.dbgAppndName = (tval || tval == null) ? true : false } String t0 = app.label if(state.dbgAppndName) { labelstr = t0+' | ' } String themsg = labelstr+msg //log.debug "Logger remDiagTest: $msg | $type | $logSrc" if(state.enRemDiagLogging == null) { state.enRemDiagLogging = parent?.getStateVal("enRemDiagLogging") if(state.enRemDiagLogging == null) { state.enRemDiagLogging = false } //log.debug "set enRemDiagLogging to ${state.enRemDiagLogging}" } if(state.enRemDiagLogging) { String theId = lastN(app?.id.toString(),5) String theLogSrc = (logSrc == (String)null) ? (parent ? "Automation-${theId}" : "NestManager") : logSrc parent?.saveLogtoRemDiagStore(themsg, type, theLogSrc) } else { if(!noLog) { switch(type) { case "debug": log.debug themsg break case "info": log.info '| '+themsg break case "trace": log.trace '| '+themsg break case "error": log.error '| '+themsg break case "warn": log.warn '|| '+themsg break default: log.debug themsg break } } } } else { log.error "${labelstr}Logger Error - type: ${type} | msg: ${msg} | logSrc: ${logSrc}" } }