Lights Usage Table

Lights Usage Table not working now. Platform 2.3.3.128, C-7, Chrome, Win10

Launches for me normally on .128. C7, Chrome latest, Win11.

2 Likes

Neat.

If this could be adjusted to add a few more trackers, it might actually tempt me to make use of this instead of webcore for something.

I.e. Can total time on be upped to include "total time on today.... This week...... This calendar month.... This year..."

Interesting.

1 Like

Yes, if using the tracker posted above. Just don't reset or use Auto reset and it will continuously accumulate Active time for the selected capability.

2 Likes

That's just a total total.

I'm curious to the the breakdown for Jan, Feb, week 1...week 43....

1 Like

@bravenel I am trying to replicate the functionality you have within Rule Machine to "Create New Action" where clicking the green + takes you to another page within the app:
image

I know that the green + launches appButtonHandler but my question is how does appButtonHandler then redirect you to /doActPage? Within my version of appButtonHandler, I have tried calling the function that defines my dynamic page as well as defining a URL via string and returning it but its not working.

Or this for href:

String hrefLink(String pageName, String linkText, Map params = null) {
	Integer pageNum = 55  // arbitrary number
	"<input type='hidden' name='params_for_action_href_name|${pageName}|${pageNum}'><button type='button' name='_action_href_name|${pageName}|${pageNum}' style='cursor:pointer;color:#1A77C9;background-color: unset;border: none;'>${linkText}</button>"
}
1 Like

Thanks Bruce. With your example realized it was a simple addition of href around the returned text from the call to buttonLink.
image

2 Likes

Hi Bruce @bravenel and folks. I've started using HE dashboards so decided to give this table a whirl. It's pretty cool - works like a champ. However, I also wanted the table to show a count of how many times the light turned on, and when it was last turned on. I also wanted a second table showing similar stats for doors/windows/contacts. I studied Bruce's clever little code and decided to take a whack at updating it myself. Seems to work although I confess I didn't spend a ton of time pressure testing it.

New tables look as follows:

And here's the code.
[EDIT]Found and squashed a few silly bugs - mostly in contacts table
and the variable was updated to include the count

 *  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]
					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]
					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 Activation 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"].start ? new Date(state.lights["$dev.id"].start).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 Activation for $dev'>$startstr</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 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"].start ? new Date(state.contacts["$dev.id"].start).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 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].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
        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].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
        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)
}
4 Likes

@kewashi Thanks for the modified version of this app. Very useful.

1 Like

@kewashi,
Thank you for the modified app.
In addition to the last turned on time, would it be possible to have last turned off time as both will be very useful?
Thanks

Sure — it’s just code. I don’t want the extra column so I won’t make it but you should go for it. The groovy code is fairly straightforward to adapt. If you’re not a coder let me know and I will put it in my queue to help you out.

1 Like

Sure, no problem.
I am not a coder, so any guidance will be much appreciated.
No rush, let me know when you have some spare time. Thanks again

okay my friend - It's an easy hack even for an old school coder like myself. I'll give it a whirl over the coming weekend. Gotta pay the bills during the week...

3 Likes

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)
}
5 Likes

Wow, you guys are amazing, thank you so much!

1 Like

@FriedCheese2006,
The last activation & deactivation time is working great.
Unfortunately, it seems that every time the refresh button is pressed then the total time for active devices are calculated again by xx number of hours?
Could you please check, thanks again.

Total time for my plug sockets show more than 24 hours and changes when refreshed

I'm not sure what you mean is the issue. The "total on time" should update every time the refresh button is pressed.

OOOOHHHHH....weird...it didn't come up in limited testing, but, yeah, I see now that the time seems to exponentially update.

1 Like