I took the liberty. Also found an issue where every time the refresh button was hit, it was updating the "start" time for the devices to the current time.
Code
/**
* Usage and Count Table
*
* Copyright 2022 Hubitat, Inc. All Rights Reserved.
*
*/
definition(
name: "Usage and Count Table",
namespace: "hubitat",
author: "Bruce Ravenel",
description: "Show Time and Frequency Usage of Lights and Contacts",
category: "Convenience",
iconUrl: "",
iconX2Url: ""
)
preferences {
page(name: "mainPage")
}
def mainPage() {
if(state.lights == null) state.lights = [:]
if(state.lightsList == null) state.lightsList = []
if(state.contacts == null) state.contacts = [:]
if(state.contactsList == null) state.contactsList = []
dynamicPage(name: "mainPage", title: "Usage and Count Table", uninstall: true, install: true) {
section {
input "lights", "capability.switch", title: "Select Lights to Measure Usage", multiple: true, submitOnChange: true, width: 4
lights.each {dev ->
if(!state.lights["$dev.id"]) {
state.lights["$dev.id"] = [start: dev.currentSwitch == "on" ? now() : 0, total: 0, var: "", time: "", count: 0,lastOn: dev.currentSwitch == "on" ? now() : 0,lastOff: dev.currentSwitch == "off" ? now() : 0]
state.lightsList += dev.id
}
}
input "resetVar", "enum", title: "Select Boolean Variable to Reset Light Timers and Counters", submitOnChange: true, width: 4, style: 'margin-left:10px',
options: getAllGlobalVars().findAll{it.value.type == "boolean"}.keySet().collect().sort{it.capitalize()}
if(lights) {
if(lights.id.sort() != state.lightsList.sort()) { //something was removed
state.lightsList = lights.id
Map newState = [:]
lights.each{d -> newState["$d.id"] = state.lights["$d.id"]}
state.lights = newState
}
updated()
paragraph displayLightsTable()
if(state.newVar) {
List vars = getAllGlobalVars().findAll{it.value.type == "string"}.keySet().collect().sort{it.capitalize()}
input "newVar", "enum", title: "Select Variable", submitOnChange: true, width: 4, options: vars, newLineAfter: true
if(newVar) {
state.lights[state.newVar].var = newVar
state.remove("newVar")
app.removeSetting("newVar")
paragraph "<script>{changeSubmit(this)}</script>"
}
} else if(state.remVar) {
state.lights[state.remVar].var = ""
state.remove("remVar")
paragraph "<script>{changeSubmit(this)}</script>"
}
input "refresh", "button", title: "Refresh Table", width: 2
input "reset", "button", title: "Reset Table", width: 2
}
}
section {
input "contacts", "capability.contactSensor", title: "Select Contacts to Measure Openings", multiple: true, submitOnChange: true, width: 4
contacts.each {dev ->
if(!state.contacts["$dev.id"]) {
state.contacts["$dev.id"] = [start: dev.currentContact == "open" ? now() : 0, total: 0, var: "", time: "", count: 0,lastOpen: dev.currentContact == "open" ? now() : 0,lastClosed: dev.currentContact == "closed" ? now() : 0]
state.contactsList += dev.id
}
}
input "resetContactVar", "enum", title: "Select Boolean Variable to Reset Contact Timers and Counters", submitOnChange: true, width: 4, style: 'margin-left:10px',
options: getAllGlobalVars().findAll{it.value.type == "boolean"}.keySet().collect().sort{it.capitalize()}
if(contacts) {
if(contacts.id.sort() != state.contactsList.sort()) { //something was removed
state.contactsList = contacts.id
Map newState = [:]
contacts.each{d -> newState["$d.id"] = state.contacts["$d.id"]}
state.contacts = newState
}
updated()
paragraph displayContactsTable()
if(state.newContactVar) {
List vars = getAllGlobalVars().findAll{it.value.type == "string"}.keySet().collect().sort{it.capitalize()}
input "newContactVar", "enum", title: "Select Variable", submitOnChange: true, width: 4, options: vars, newLineAfter: true
if(newContactVar) {
state.contacts[state.newContactVar].var = newContactVar
state.remove("newContactVar")
app.removeSetting("newContactVar")
paragraph "<script>{changeSubmit(this)}</script>"
}
} else if(state.remContactVar) {
state.contacts[state.remContactVar].var = ""
state.remove("remContactVar")
paragraph "<script>{changeSubmit(this)}</script>"
}
input "refreshc", "button", title: "Refresh Table", width: 2
input "resetc", "button", title: "Reset Table", width: 2
}
}
}
}
String displayLightsTable() {
if(state.reset) {
def dev = lights.find{"$it.id" == state.reset}
state.lights[state.reset].start = dev.currentSwitch == "on" ? now() : 0
state.lights[state.reset].time = new Date().format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}")
state.lights[state.reset].total = 0
state.lights[state.reset].count = dev.currentSwitch == "on" ? 1 : 0
state.remove("reset")
}
String str = "<script src='https://code.iconify.design/iconify-icon/1.0.0/iconify-icon.min.js'></script>"
str += "<style>.mdl-data-table tbody tr:hover{background-color:inherit} .tstat-col td,.tstat-col th { padding:8px 8px;text-align:center;font-size:12px} .tstat-col td {font-size:15px }" +
"</style><div style='overflow-x:auto'><table class='mdl-data-table tstat-col' style=';border:2px solid black'>" +
"<thead><tr style='border-bottom:2px solid black'><th style='border-right:2px solid black'>Light</th>" +
"<th>Total On Time</th>" +
"<th>Total Count</th>" +
"<th>Reset</th>" +
"<th>Last On Time</th>" +
"<th>Last Off Time</th>" +
"<th>Last Reset Time</th>" +
"<th>Variable</th></tr></thead>"
lights.sort{it.displayName.toLowerCase()}.each {dev ->
int total = state.lights["$dev.id"].total / 1000
String thisVar = state.lights["$dev.id"].var
String count = state.lights["$dev.id"].count
String startstr = state.lights["$dev.id"].lastOn ? new Date(state.lights["$dev.id"].lastOn).format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}") : ""
String stopstr = state.lights["$dev.id"].lastOff ? new Date(state.lights["$dev.id"].lastOff).format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}") : ""
int hours = total / 3600
total = total % 3600
int mins = total / 60
int secs = total % 60
String time = "$hours:${mins < 10 ? "0" : ""}$mins:${secs < 10 ? "0" : ""}$secs"
if(thisVar) setGlobalVar(thisVar, "$time $count")
String devLink = "<a href='/device/edit/$dev.id' target='_blank' title='Open Device Page for $dev'>$dev"
String reset = buttonLink("d$dev.id", "<iconify-icon icon='bx:reset'></iconify-icon>", "black", "20px")
String var = thisVar ? buttonLink("r$dev.id", thisVar, "purple") : buttonLink("n$dev.id", "Select", "green")
str += "<tr style='color:black'><td style='border-right:2px solid black'>$devLink</td>" +
"<td title='Switch usage time since last Reset' style='color:${dev.currentSwitch == "on" ? "green" : "red"}'>$time</td>" +
"<td title='Activation count since last Reset' style='color:${dev.currentSwitch == "on" ? "green" : "red"}'>$count</td>" +
"<td title='Reset Total Time and Total Count for $dev' style='padding:0px 0px'>$reset</td>" +
"<td title='Time of last On for $dev'>$startstr</td>" +
"<td title='Time of last Off for $dev'>$stopstr</td>" +
"<td title='Time of last Reset for $dev'>${state.lights["$dev.id"].time ?: ""}</td>" +
"<td title='${thisVar ? "Deselect $thisVar" : "Select String Hub Variable"}'>$var</td></tr>"
}
str += "</table></div>"
str
}
String displayContactsTable() {
if(state.contactreset) {
def dev = contacts.find{"$it.id" == state.contactreset}
state.contacts[state.contactreset].start = dev.currentContact == "open" ? now() : 0
state.contacts[state.contactreset].time = new Date().format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}")
state.contacts[state.contactreset].total = 0
state.contacts[state.contactreset].count = dev.currentContact == "open" ? 1 : 0
state.remove("contactreset")
}
String str = "<script src='https://code.iconify.design/iconify-icon/1.0.0/iconify-icon.min.js'></script>"
str += "<style>.mdl-data-table tbody tr:hover{background-color:inherit} .tstat-col td,.tstat-col th { padding:8px 8px;text-align:center;font-size:12px} .tstat-col td {font-size:15px }" +
"</style><div style='overflow-x:auto'><table class='mdl-data-table tstat-col' style=';border:2px solid black'>" +
"<thead><tr style='border-bottom:2px solid black'><th style='border-right:2px solid black'>Contact</th>" +
"<th>Total Open Time</th>" +
"<th>Total Count</th>" +
"<th>Reset</th>" +
"<th>Last Open Time</th>" +
"<th>Last Close Time</th>" +
"<th>Last Reset Time</th>" +
"<th>Variable</th></tr></thead>"
contacts.sort{it.displayName.toLowerCase()}.each {dev ->
int total = state.contacts["$dev.id"].total / 1000
String thisVar = state.contacts["$dev.id"].var
String count = state.contacts["$dev.id"].count
String startstr = state.contacts["$dev.id"].lastOpen ? new Date(state.contacts["$dev.id"].lastOpen).format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}") : ""
String stopstr = state.contacts["$dev.id"].lastClosed ? new Date(state.contacts["$dev.id"].lastClosed).format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}") : ""
int hours = total / 3600
total = total % 3600
int mins = total / 60
int secs = total % 60
String time = "$hours:${mins < 10 ? "0" : ""}$mins:${secs < 10 ? "0" : ""}$secs"
if(thisVar) setGlobalVar(thisVar, "$time $count")
String devLink = "<a href='/device/edit/$dev.id' target='_blank' title='Open Device Page for $dev'>$dev"
String reset = buttonLink("e$dev.id", "<iconify-icon icon='bx:reset'></iconify-icon>", "black", "20px")
String var = thisVar ? buttonLink("s$dev.id", thisVar, "purple") : buttonLink("o$dev.id", "Select", "green")
str += "<tr style='color:black'><td style='border-right:2px solid black'>$devLink</td>" +
"<td title='Contact open time since last Reset' style='color:${dev.currentContact == "open" ? "green" : "red"}'>$time</td>" +
"<td title='Open count since last Reset' style='color:${dev.currentContact == "open" ? "green" : "red"}'>$count</td>" +
"<td title='Reset Total Time and Total Count for $dev' style='padding:0px 0px'>$reset</td>" +
"<td title='Time of last Open for $dev'>$startstr</td>" +
"<td title='Time of last Close for $dev'>$stopstr</td>" +
"<td title='Time of last Reset for $dev'>${state.contacts["$dev.id"].time ?: ""}</td>" +
"<td title='${thisVar ? "Deselect $thisVar" : "Select String Hub Variable"}'>$var</td></tr>"
}
str += "</table></div>"
str
}
String buttonLink(String btnName, String linkText, color = "#1A77C9", font = "15px") {
"<div class='form-group'><input type='hidden' name='${btnName}.type' value='button'></div><div><div class='submitOnChange' onclick='buttonClick(this)' style='color:$color;cursor:pointer;font-size:$font'>$linkText</div></div><input type='hidden' name='settings[$btnName]' value=''>"
}
void appButtonHandler(btn) {
if(btn == "reset") resetTimers()
else if(btn == "resetc") resetContactTimers()
else if(btn == "refresh") {
state.lights.each{k, v ->
def dev = lights.find{"$it.id" == k}
if(dev.currentSwitch == "on") {
state.lights[k].total += now() - state.lights[k].start
state.lights[k].start = now()
}
}
} else if(btn =="refreshc") {
state.contacts.each{k, v ->
def dev = contacts.find{"$it.id" == k}
if(dev.currentContact == "open") {
state.contacts[k].total += now() - state.contacts[k].start
state.contacts[k].start = now()
}
}
} else if(btn.startsWith("n")) state.newVar = btn.minus("n")
else if(btn.startsWith("r")) state.remVar = btn.minus("r")
else if(btn.startsWith("o")) state.newContactVar = btn.minus("o")
else if(btn.startsWith("s")) state.remContactVar = btn.minus("s")
else if(btn.startsWith('e')) state.contactreset = btn.minus("e")
else if(btn.startsWith('d')) state.reset = btn.minus("d")
else log.warn "Unrecognized button pressed"
}
def updated() {
unsubscribe()
initialize()
}
def installed() {
}
def uninstalled() {
unsubscribe()
}
void initialize() {
subscribe(lights, "switch.on", "onHandler")
subscribe(lights, "switch.off", "offHandler")
if(resetVar) {
subscribe(location, "variable:${resetVar}.true", resetTimers)
setGlobalVar(resetVar, false)
}
// subscribe(contacts, "contact.open", "openHandler")
// subscribe(contacts, "contact.closed", "closeHandler")
contacts?.each { device ->
subscribe(device, "contact", "contactHandler")
}
if(resetContactVar) {
subscribe(location, "variable:${resetContactVar}.true", resetContactTimers)
setGlobalVar(resetContactVar, false)
}
}
void contactHandler(evt) {
def value = evt.value
if (value == "open") openHandler(evt)
else if (value== "closed") closeHandler(evt)
}
void onHandler(evt) {
if ( state.lights[evt.device.id] ) {
state.lights[evt.device.id].start = now()
state.lights[evt.device.id].lastOn = now()
state.lights[evt.device.id].count++
String startstr = new Date(state.lights[evt.device.id].start).format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}")
log.info "device ${evt.device.displayName} turned on at $startstr"
}
}
void offHandler(evt) {
if ( state.lights[evt.device.id] ) {
state.lights[evt.device.id].total += now() - state.lights[evt.device.id].start
state.lights[evt.device.id].lastOff = now()
String thisVar = state.lights[evt.device.id].var
int total = state.lights[evt.device.id].total / 1000
int hours = total / 3600
total = total % 3600
int mins = total / 60
int secs = total % 60
String thisTime = "$hours:${mins < 10 ? "0" : ""}$mins:${secs < 10 ? "0" : ""}$secs"
log.info "device ${evt.device.displayName} total = $thisTime"
if(thisVar) setGlobalVar(thisVar, thisTime)
}
}
void openHandler(evt) {
if ( state.contacts[evt.device.id] ) {
state.contacts[evt.device.id].start = now()
state.contacts[evt.device.id].lastOpen = now()
state.contacts[evt.device.id].count++
String startstr = new Date(state.contacts[evt.device.id].start).format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}")
log.info "device ${evt.device.displayName} opened at $startstr"
}
}
void closeHandler(evt) {
if ( state.contacts[evt.device.id] ) {
state.contacts[evt.device.id].total += now() - state.contacts[evt.device.id].start
state.contacts[evt.device.id].lastClosed = now()
String thisVar = state.contacts[evt.device.id].var
int total = state.contacts[evt.device.id].total / 1000
int hours = total / 3600
total = total % 3600
int mins = total / 60
int secs = total % 60
String thisTime = "$hours:${mins < 10 ? "0" : ""}$mins:${secs < 10 ? "0" : ""}$secs"
log.info "device ${evt.device.displayName} total = $thisTime"
if(thisVar) setGlobalVar(thisVar, thisTime)
}
}
void resetTimers(evt = null) {
state.lights.each{k, v ->
def dev = lights.find{"$it.id" == k}
if ( dev ) {
state.lights[k].start = dev.currentSwitch == "on" ? now() : 0
state.lights[k].time = new Date().format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}")
state.lights[k].total = 0
state.lights[k].count = dev.currentSwitch == "on" ? 1 : 0
}
}
if(resetVar) setGlobalVar(resetVar, false)
}
void resetContactTimers(evt = null) {
state.contacts.each{k, v ->
def dev = contacts.find{"$it.id" == k}
if ( dev ) {
state.contacts[k].start = dev.currentContact == "open" ? now() : 0
state.contacts[k].time = new Date().format("MM-dd-yyyy ${location.timeFormat == "12" ? "h:mm:ss a" : "HH:mm:ss"}")
state.contacts[k].total = 0
state.contacts[k].count = dev.currentContact == "open" ? 1 : 0
}
}
if(resetContactVar) setGlobalVar(resetContactVar, false)
}