Refresh

It seems necessary to refresh the dashboard to get tiles for presence and mode to update on my Android phone. I see the refresh is set to 2 seconds for local and 5 for cloud, but the tiles don't update automatically. Should they? Any way to fix?

I also noted that background image doesn't seem to be refreshed automatically either, if the image changes on the net.

Another issue I faced was my battery went and my phone died. I then left the property and went somewhere else. My presence is now stuck as present even though I'm not, meaning some rules are not running correctly now. It doesn't seem possible to override presence by clicking on the tile and changing it. I think that could be a useful option.

So a lot to unpack here. You mentioned Android but not the version. What version / os level?

You mention cloud and local links but not clear on what problem is happening when.

Please help me understand each specific problems with specific steps to reproduce.

If we can reproduce the issue, we can typically fix the issue.

You mention a presence device but not the driver name, is this custom or using life 360 or using a virtual presence driver?

Anything you can provide will help. As this is not the normal behavior of dashboard.

Also, is this a problem you can replicate on a different device?

What solution are you using for presence? This is an option I've added to the ST Arrival sensor driver and could easily be added to another driver as well. However, when you say "click on the tile", how are you going to do that if your phone is dead?

I'm using Life360 (on a Samsung Galaxy S8 Edge and a Samsung S6) and an ST presence sensor v2. None of the tiles for these 3 devices updates automatically without a browser refresh.

I meant click on the dashboard tile on another device (eg. wIfe's phone)...

Well, the ST Arrival sensor would have read as away. Are you also using combined presence?

About the not updating tiles and background issue, I've concluded that this is a chrome(webbrowser) chaching "fault".

I have the same issues, I have a chrome weblink on my home screen, using that I often need to open it then close it and open it again to force a reload of the page. It is however not that obvious when no changes are made to the dashboard.

Okay, that's not what I asked. Since you are using two method for presence, how are you combining them to know if you are present or not? Or are you just using them independently and both your phone and ST sensor have to be gone for you to be gone?

Yeah, I'm using Chrome. But I thought I did see some tiles update. Maybe not, maybe the page refreshed. I will need to do some more testing.

ST sensor is for guests. Wife & I use our phones using Life360, pending the new app. All 3 are displayed as dashboard tiles.

Okay...I thought you meant that you were using both for yourself.
So, for the ST presence sensor, there's a driver that has Enable and disable like a normal switch.

import groovy.json.JsonOutput

/**
 *
 * ST Arrival Sensor (Advanced)
 *
 *  Copyright 2019 Ryan Casler
 *  Developed from LLWarrenP's "Arrival Sensor HA with Disable"
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
 
def dthVersion() {
	return "1.0"
}

/*
* Change Log:
* 2019-3-13  - (1.0) Initial release
*/


metadata {
    definition (name: "ST Arrival Sensor (Advanced)", namespace: "ryan780", author: "ryan780") {
        capability "Tone"
        capability "Actuator"
        capability "Presence Sensor"
        capability "Sensor"
        capability "Battery"
        capability "Configuration"
		capability "Switch"

		attribute "enabled", "string"
        command "enable"
        command "disable"
        command "toggle"
		command "disableDelayedEnable", ["minutes"]

        fingerprint inClusters: "0000,0001,0003,000F,0020", outClusters: "0003,0019",
                        manufacturer: "SmartThings", model: "tagv4", deviceJoinName: "Arrival Sensor"
    }

    preferences {
        section {
            image(name: 'educationalcontent', multiple: true, images: [
                "http://cdn.device-gse.smartthings.com/Arrival/Arrival1.png",
                "http://cdn.device-gse.smartthings.com/Arrival/Arrival2.png"
                ])
        }
        section {
            input "checkInterval", "enum", title: "Presence timeout (minutes)", description: "Tap to set",
                    defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
        }
        section {
            input "disabledMode", "enum", title: "When disabling sensor, set presence to:", description: "Tap to set",
                    defaultValue:"auto", options: ["auto", "present", "not present"], displayDuringSetup: false
            input "enabledMode", "enum", title: "When enabling sensor, set presence to:", description: "Tap to set",
                    defaultValue:"auto", options: ["auto", "present", "not present"], displayDuringSetup: false
			input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
        }
    }
}

def updated() {
    startTimer()
	log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
    if (logEnable) runIn(1800, logsOff)
}


def installed() {
    // Arrival sensors only goes OFFLINE when Hub is off
    sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
}

def logsOff() {
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable", [value: "false", type: "bool"])
}

def configure() {
    def cmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.batteryConfig(20, 20, 0x01)
    if (logEnable) log.debug "configure -- cmds: ${cmds}"
    return cmds
}

def beep() {
    if (logEnable) log.debug "Sending Identify command to beep the sensor for 5 seconds"
    return zigbee.command(0x0003, 0x00, "0500")
}

def parse(String description) {
    state.lastCheckin = now()
    handlePresenceEvent(true)

    if (description?.startsWith('read attr -')) {
        handleReportAttributeMessage(description)
    }

    return []
}

private handleReportAttributeMessage(String description) {
    def descMap = zigbee.parseDescriptionAsMap(description)
    if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
        handleBatteryEvent(Integer.parseInt(descMap.value, 16))
    }
}

/**
 * Create battery event from reported battery voltage.
 *
 * @param volts Battery voltage in .1V increments
 */
private handleBatteryEvent(volts) {
	def descriptionText
    if (volts == 0 || volts == 255) {
        log.debug "Ignoring invalid value for voltage (${volts/10}V)"
    }
    else {
        def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
                          22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
        def minVolts = 15
        def maxVolts = 28

        if (volts < minVolts)
            volts = minVolts
        else if (volts > maxVolts)
            volts = maxVolts
        def value = batteryMap[volts]
        if (value != null) {
            def linkText = getLinkText(device)
            descriptionText = '{{ linkText }} battery was {{ value }}'
            def eventMap = [
                name: 'battery',
                value: value,
                descriptionText: descriptionText,
                translatable: true
            ]
            if (logEnable) log.debug "Creating battery event for voltage=${volts/10}V: ${linkText} ${eventMap.name} is ${eventMap.value}%"
            sendEvent(eventMap)
        }
    }
}

private handlePresenceEvent(present) {
    def wasPresent = device.currentState("presence")?.value == "present"
    if (!wasPresent && present) {
        if (logEnable) log.debug "Sensor is present"
        startTimer()
    } else if (!present) {
        if (logEnable) log.debug "Sensor is not present"
        stopTimer()
    }
    def linkText = getLinkText(device)
    def descriptionText
    def enabledStatus = ""
    if ( present )
    	descriptionText = "{{ linkText }} has arrived"
    else
    	descriptionText = "{{ linkText }} has left"
    if ((device.currentValue("enabled") == "disabled-present") || (device.currentValue("enabled") == "disabled-not present")) {
    	// Device is disabled so we won't generate a presence event but instead just track the status behind the scenes by generating an enabled event
    	if (logEnable) log.debug "${linkText} is ${device.currentValue("enabled")}: not creating presence event"
        enabledStatus = "disabled-"
        enabledStatus = enabledStatus.concat(present ? "present" : "not present")
		if (device.currentValue("enabled") != enabledStatus) sendEvent(name: "enabled", value: enabledStatus, isStateChange: true)
	}
    else {
    	// Device is enabled so we will generate a presence event and an enabled event
	    def eventMap = [
        	name: "presence",
        	value: present ? "present" : "not present",
        	linkText: linkText,
        	descriptionText: descriptionText,
        	translatable: true
    		]
        enabledStatus = "enabled-"
        enabledStatus = enabledStatus.concat(present ? "present" : "not present")
		if (device.currentValue("enabled") != enabledStatus) sendEvent(name: "enabled", value: enabledStatus, isStateChange: true)
	   	if (logEnable)log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value} with status ${device.currentValue("enabled")}"
    	sendEvent(eventMap)
    }
}

private startTimer() {
    if (logEnable)log.debug "Scheduling periodic timer"
    runEvery1Minute("checkPresenceCallback")
}

private stopTimer() {
    if (logEnable)log.debug "Stopping periodic timer"
    unschedule()
}

def checkPresenceCallback() {
    def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
    def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
    if (logEnable)log.debug "Sensor checked in ${timeSinceLastCheckin} seconds ago"
    if (timeSinceLastCheckin >= theCheckInterval) {
        handlePresenceEvent(false)
    }
}

def toggle() {
	// Button pressed, toggle the enabled state (which also tracks the current presence)
	if ((device.currentValue("enabled") == "enabled-present") || (device.currentValue("enabled") == "enabled-not present"))
    	disable()
    else
    	enable()
}

def enable() {
    // Force presence per user settings
    if (logEnable)log.debug "Setting sensor presence to ${settings.enabledMode}"
	sendEvent(name: "switch", value: "on", isStateChange: true)
	if (settings.enabledMode && (settings.enabledMode != "auto")) {
    	stopTimer()
    	sendEvent(name: "presence", value: settings.enabledMode, translatable: true)
        }
    else if (settings.enabledMode && (settings.enabledMode == "auto"))
	    startTimer()
	// Enable the device and update the enabled status to reflect the new status
	if (logEnable)log.debug "Enabling ${getLinkText(device)}"
    if (device.currentValue("presence") == "present")
		sendEvent(name: "enabled", value: "enabled-present", isStateChange: true)
    else if (device.currentValue("presence") == "not present")
		sendEvent(name: "enabled", value: "enabled-not present", isStateChange: true)
}

def disable() {
    // Force presence per user settings
    if (logEnable)log.debug "Setting sensor presence to ${settings.disabledMode}"
	sendEvent(name: "switch", value: "off", isStateChange: true)
	if (settings.disabledMode && (settings.disabledMode != "auto")) {
    	stopTimer()
    	sendEvent(name: "presence", value: settings.disabledMode, translatable: true)
        }
    else if (settings.disabledMode && (settings.disabledMode == "auto"))
	    startTimer()
	// Disable the device and update the enabled status to reflect the new status
	if (logEnable)log.debug "Disabling ${getLinkText(device)}"
    state.updatePresence = false
    if (device.currentValue("presence") == "present")
		sendEvent(name: "enabled", value: "disabled-present", isStateChange: true)
    else if (device.currentValue("presence") == "not present")
		sendEvent(name: "enabled", value: "disabled-not present", isStateChange: true)
}

def disableDelayedEnable(delay) {
    unschedule()
	disable()
	myDelay=(delay.toInteger())*60
	runIn(myDelay, enable)
}

def on(){
	enable()
}

def off(){
	disable()
}

When disabled you can have the presence be whatever you choose in the driver. If you're using life 360 for your other presence, that's going to be a little tougher since that uses a built in app and driver that no one but hubitat staff has access to. You best bet might be to look at Combined Presence. it's a Cobra app. That way, you can control what state the virtual presence sensor you create is in manually. That's the only way I can think to accomplish what you want. Not a clean method to be sure. Might just be easier to get a powerbank to charge your phone up.

I have to say, when I first read your post and read "left my phone at home" i thought at first you were joking. The thought of leaving home without my phone has become almost unthinkable. lol

So I just arrived at the property. The ST sensor updated fine. No refresh needed. Neither of our tiles for Life360 presence updated and the Mode tiles also still hasn't updated since yesterday evening (to night or now to day). A refresh fixes all issues.

Driving there:

Arrived:

Browser refresh:

Sorry if I'm doing something wrong or don't have it set up correctly. I've only started playing with dashboards in the last week or so and have only had the hub less than a month. Still learning...

Yeah, understood. My phone is glued to my hip or hand always. It's just that the battery died and so didn't update location and when I got home I put the phone on charge and used a chromebook. Then noticed the presence hadn't updated (of course because the phone was dead) but then found I couldn't manually set it by click on the tile which was a surprise. But I guess since it takes its data from Life360 and should update automatically it would be stupid for me to ask to overwrite it manually probably anyway.

Are you saying that the dashboard isn't updating or your actual presence isn't updating? You have to be more specific what you are trying to fix. You have to compare what is displayed in the dashboard, which is an app, versus what is displayed in the edit device page. Then we can figure out if your problem is the tile not updating in the dashboard or Life 360 not updating. But you also have to remember, life 360 is FAR from instant.

Again, no update to the tiles until I refresh the browser (eg. status is correct in the device on the hub, dashboard doesn't automatically update either the mode or the presence from Life360). I don't know how to make it any clearer, sorry.

Regarding mode, take a look at the top tile. It still says evening and it's midday. It didn't update until a browser refresh. So this is not just an issue regarding Life360.

Do you have data saving activated in chrome?

Yes! (well its referred to as "Lite Mode" in the menu but refers to data saving). Good idea. Lemme try if that fixes it.

I confirm the problem still exists with Lite Mode switched off on Chrome on my Galaxy S8 Edge. The (Life360) presence tiles and the Mode tile still don't update on my phone (even tho they are shown correctly updated in the Hub device settings).

Android V9 on Galaxy S8 Edge, Chrome v74.

I also confirm the phone power saver mode is off (as required by Life360)

I have a LG G6 android V7. Have the same issue. Tiles on both local and cloud don't update without a refresh. Some of the time the local will update but the cloud link never seems to. For what it's worth, I also have a Sharptools dashboard for comparison. It seems to be fine.

I have some local dashboards on a couple tablets on the wall and they seem to update fine.