Some rough code for a device driver that may work:
Rough Driver Code for Summary Tile
/*
*/
import java.text.SimpleDateFormat
import groovy.json.JsonSlurper
import groovy.transform.Field
@Field static final String okSymFLD = "\u2713"
@Field static final String notOkSymFLD = "<span style='color:red'>\u2715</span>"
@Field static final String sBLANK = ''
@SuppressWarnings('unused')
static String version() {return "0.0.1"}
metadata {
definition (
name: "ES Tile",
namespace: "thebearmay",
author: "Jean P. May, Jr.",
importUrl:"https://raw.githubusercontent.com/thebearmay/hubitat/main/xxxx.groovy"
) {
capability "Actuator"
capability "Refresh"
attribute "cookieRefreshDays", "number"
attribute "serverData","string"
attribute "cookieData","string"
attribute "csrf","string"
attribute "amazonDomain","string"
attribute "tm2NewAtRfrsh", "string"
attribute "tmFromAtRrsh", "string"
attribute "html","string"
attribute "htmlAlt", "string"
//command "processPage"
command "refreshHTML"
}
}
preferences {
input("security", "bool", title: "Hub Security Enabled", defaultValue: false, submitOnChange: true, width:4)
if (security) {
input("username", "string", title: "Hub Security Username", required: false, width:4)
input("password", "password", title: "Hub Security Password", required: false, width:4)
}
input("pollRate","number", title:"Poll rate (in minutes) Disable:0):", defaultValue:720, submitOnChange:true, width:4)
}
@SuppressWarnings('unused')
def installed() {
}
void updateAttr(String aKey, aValue, String aUnit = ""){
sendEvent(name:aKey, value:aValue, unit:aUnit)
}
void refresh(){
processPage()
refreshHTML()
if(pollRate == null)
device.updateSetting("pollRate",[value:720,type:"number"])
if(pollRate > 0) {
runIn(pollRate*60,"refresh")
}
}
def updated(){
if(pollRate == null)
device.updateSetting("pollRate",[value:720,type:"number"])
if(pollRate > 0) {
runIn(pollRate*60,"refresh")
}
}
void processPage(){
app = findPage()
if(app==-1) {
log.error "Echo Speaks not Installed"
return
}
pData=readPage("http://127.0.0.1:8080/installedapp/status/$app")
dWork = pData.substring(pData.indexOf('refreshCookieDays'),pData.indexOf('refreshCookieDays')+500)
dWork.replace('<','')
dWork=dWork.split(' ')
dWork.each{
if(it.isNumber()) updateAttr("cookieRefreshDays",it.toInteger())
}
dWork = pData.substring(pData.indexOf('serverDataMap'),pData.indexOf('serverDataMap')+800)
dWork = dWork.substring(dWork.indexOf('{'),dWork.indexOf('}')+1)
createServerMap(dWork)
if(pData.indexOf("cookieData") >-1){
updateAttr("cookieData",true)
if(pData.indexOf("csrf") > -1)
updateAttr("csrf", true)
}
dWork = pData.substring(pData.indexOf('amazonDomain'),pData.indexOf('amazonDomain')+300)
dWork.replace('<','')
dWork=dWork.split(' ')
dWork.each{
if(it.contains(".")) updateAttr("amazonDomain",it.trim())
}
}
Integer findPage(){
def params = [
uri: "http://127.0.0.1:8080/installedapp/list",
textParser: true
]
def allAppsList = []
def allAppNames = []
try {
httpGet(params) { resp ->
def matcherText = resp.data.text.replace("\n","").replace("\r","")
def matcher = matcherText.findAll(/(<tr class="app-row" data-app-id="[^<>]+">.*?<\/tr>)/).each {
def allFields = it.findAll(/(<td .*?<\/td>)/) // { match,f -> return f }
def id = it.find(/data-app-id="([^"]+)"/) { match,i -> return i.trim() }
def title = allFields[0].find(/data-order="([^"]+)/) { match,t -> return t.trim() }
allAppsList += [id:id,title:title]
allAppNames << title
}
}
} catch (e) {
log.error "Error retrieving installed apps: ${e}"
log.error(getExceptionMessageWithLine(e))
}
for(i=0;i < allAppsList.size();i++) {
if(allAppsList[i].title == 'Echo Speaks') {
//log.debug "Found it"
return allAppsList[i].id.toInteger()
break
}
}
return -1
}
void createServerMap(sData){
sWork = sData.replace("=",'\":\"')
sWork = sWork.replace(', ','","')
sWork = sWork.replace('{','{\"')
sWork = sWork.replace('}','\"}')
updateAttr("serverData", sWork)
}
void refreshHTML(){
Long tNow = new Date().getTime()
def jSlurp = new JsonSlurper()
serverData = jSlurp.parseText(device.currentValue("serverData",true))
nextCookieRefreshDur()
wkStr = "<table style='color:mediumblue;font-size:small'><tr><th>Auth Status: "
if(device.currentValue("csrf",true) == "true" && device.currentValue("cookieData",true) == "true")
wkStr+=okSymFLD
else
wkStr+=notOkSymFLD
wkStr+="</th></tr><tr><td> Cookie: "
if(device.currentValue("cookieData",true) == "true")
wkStr+=okSymFLD
else
wkStr+=notOkSymFLD
wkStr+="</td></tr><tr><td> CSRF: "
if(device.currentValue("csrf",true) == "true")
wkStr+=okSymFLD
else
wkStr+=notOkSymFLD
wkStr+="</td></tr><tr><th>Cookie Data</th></tr>"
wkStr2 = wkStr
wkStr+="<tr><td>Last Refresh: ${serverData.lastCookieRrshDt}</td></tr>"
startDate = Date.parse("E MMM dd HH:mm:ss z yyyy", serverData.lastCookieRrshDt).getTime()
nextDate = startDate + (86400000 * device.currentValue("cookieRefreshDays").toInteger())
//log.debug "$tNow $nextDate"
SimpleDateFormat sdf = new SimpleDateFormat("E MMM dd HH:mm:ss z yyyy")
if(nextDate > tNow){
wkStr+="<tr><td>Next Refresh: ${sdf.format(nextDate)}</td></tr>"
} else {
wkStr+="<tr><td style='color:red;font-weight:bold'>Missed Refresh: ${sdf.format(nextDate)}</td></tr>"
}
wkStr+="</td></tr><tr><th>Server Data</th></tr>"
wkStr+="<tr><td>Heroku: "
if(serverData.onHeroku == "true")
wkStr+=okSymFLD
else
wkStr+=notOkSymFLD
wkStr+="</td></tr><tr><td>Local Server: "
if(serverData.isLocal == "true")
wkStr+=okSymFLD
else
wkStr+=notOkSymFLD
wkStr+="</td></tr><tr><td>Server IP: ${serverData.serverHost}</td></tr>"
wkStr+="<tr><td>Domain: ${device.currentValue("amazonDomain",true)}</td></tr>"
wkStr+="</table>"
updateAttr("html",wkStr)
wkStr2+="<tr><td>Last Refresh: ${device.currentValue("tmFromAtRrsh",true)} ago</td></tr>"
if(nextDate > tNow){
wkStr2+="<tr><td>Next Refresh: ${device.currentValue("tm2NewAtRfrsh")}</td></tr>"
} else {
wkStr2+="<tr><td style='color:red;font-weight:bold'>Missed Refresh: ${sdf.format(nextDate)}</td></tr>"
}
wkStr2+="</td></tr><tr><th>Server Data</th></tr>"
wkStr2+="<tr><td>Heroku: "
if(serverData.onHeroku == "true")
wkStr2+=okSymFLD
else
wkStr2+=notOkSymFLD
wkStr2+="</td></tr><tr><td>Local Server: "
if(serverData.isLocal == "true")
wkStr2+=okSymFLD
else
wkStr2+=notOkSymFLD
wkStr2+="</td></tr><tr><td>Server IP: ${serverData.serverHost}</td></tr>"
wkStr2+="<tr><td>Domain: ${device.currentValue("amazonDomain",true)}</td></tr>"
wkStr2+="</table>"
updateAttr("htmlAlt",wkStr2)
}
String nextCookieRefreshDur() {
Long tNow = new Date().getTime()
def jSlurp = new JsonSlurper()
serverData = jSlurp.parseText(device.currentValue("serverData",true))
Integer days = device.currentValue("cookieRefreshDays").toInteger()
String lastCookieRfsh = serverData.lastCookieRrshDt
if(!lastCookieRfsh) { return "Not Sure"}
Date lastDt = Date.parse("E MMM dd HH:mm:ss z yyyy", formatDt(Date.parse("E MMM dd HH:mm:ss z yyyy", lastCookieRfsh)))
String dMinus = seconds2Duration(((tNow-lastDt.getTime())/1000).toInteger(),false,3)
updateAttr("tmFromAtRrsh", dMinus)
Date nextDt = Date.parse("E MMM dd HH:mm:ss z yyyy", formatDt(lastDt + days))
Integer diff = ( (nextDt.getTime() - wnow()) / 1000) as Integer
String dur = seconds2Duration(diff, false, 3)
// log.debug "now: ${now} | lastDt: ${lastDt} | nextDt: ${nextDt} | Days: $days | Wait: $diff | Dur: ${dur}"
updateAttr("tm2NewAtRfrsh", dur)
}
String formatDt(Date dt, Boolean tzChg=true) {
def tf = new SimpleDateFormat("E MMM dd HH:mm:ss z yyyy")
if(tzChg) { if(location.timeZone) { tf.setTimeZone((TimeZone)location?.timeZone) } }
return (String)tf.format(dt)
}
private Long wnow(){ return (Long)now() }
@SuppressWarnings('GroovyAssignabilityCheck')
static String seconds2Duration(Integer itimeSec, Boolean postfix=true, Integer tk=2) {
Integer timeSec=itimeSec
Integer years = Math.floor(timeSec / 31536000); timeSec -= years * 31536000
Integer months = Math.floor(timeSec / 31536000); timeSec -= months * 2592000
Integer days = Math.floor(timeSec / 86400); timeSec -= days * 86400
Integer hours = Math.floor(timeSec / 3600); timeSec -= hours * 3600
Integer minutes = Math.floor(timeSec / 60); timeSec -= minutes * 60
Integer seconds = Integer.parseInt((timeSec % 60) as String, 10)
Map d = [y: years, mn: months, d: days, h: hours, m: minutes, s: seconds]
List l = []
if(d.d > 0) { l.push("${d.d} ${pluralize(d.d, "day")}") }
if(d.h > 0) { l.push("${d.h} ${pluralize(d.h, "hour")}") }
if(d.m > 0) { l.push("${d.m} ${pluralize(d.m, "min")}") }
if(d.s > 0) { l.push("${d.s} ${pluralize(d.s, "sec")}") }
return l.size() ? "${l.take(tk ?: 2)?.join(", ")}${postfix ? " ago" : sBLANK}".toString() : "Not Sure"
}
static String pluralize(Integer itemVal, String str) { return (itemVal > 1) ? str+"s" : str }
String readPage(fName){
if(security) cookie = securityLogin().cookie
def params = [
uri: fName,
contentType: "text/html",
textParser: true,
headers: [
"Cookie": cookie
]
]
try {
httpGet(params) { resp ->
if(resp!= null) {
int i = 0
String delim = ""
i = resp.data.read()
while (i != -1){
char c = (char) i
delim+=c
i = resp.data.read()
}
return delim
}
else {
log.error "Null Response"
}
}
} catch (exception) {
log.error "Read Ext Error: ${exception.message}"
return null;
}
}
HashMap securityLogin(){
def result = false
try{
httpPost(
[
uri: "http://127.0.0.1:8080",
path: "/login",
query:
[
loginRedirect: "/"
],
body:
[
username: username,
password: password,
submit: "Login"
],
textParser: true,
ignoreSSLIssues: true
]
)
{ resp ->
if (resp.data?.text?.contains("The login information you supplied was incorrect."))
result = false
else {
cookie = resp?.headers?.'Set-Cookie'?.split(';')?.getAt(0)
result = true
}
}
}catch (e){
log.error "Error logging in: ${e}"
result = false
cookie = null
}
return [result: result, cookie: cookie]
}
Edit: Updated code 19Dec2022
Edit 2: Code update for Beta 2.6.3 is in Github - 28Aug2023