ST Presence Fob v4

My v4 tags have been some of the most reliable things I've bought for this integration. I just replace the battery every month and don't think about it after that.

The only issue I had so far is:
It sits as present and suddenly middle of night it goes to not present and 1m later changes back to present. So can't really use it.

yeah, like I've already mentioned they go away, then return during the morning back up.
Not much can be done about this ATM.

All my presence related rules have constraints that prevent those events from effecting anything important.

1 Like

Thanks

It's only a success if you actually wanted your wife to come home. :stuck_out_tongue_winking_eye:

That said, I have been experiencing this issue all along...

I assure everyone I was in bed when this happened..

presence present Steve's Fob has arrived DEVICE 2019-03-06 12:54:06.334 AM EST
presence not present Steve's Fob has departed DEVICE 2019-03-06 12:47:45.202 AM EST

I spun up a test fob whilst troubleshooting the Zigbee issues. It sits in a closet in an adjacent room.. Notice the time correlation for todays event?

presence present Test Fob has arrived DEVICE 2019-03-06 12:52:04.719 AM EST
presence not present Test Fob has departed DEVICE 2019-03-06 12:47:45.421 AM EST

This fob never leaves the closet, yet it appears that it has quite the social life...

presence present Test Fob has arrived DEVICE 2019-02-10 09:43:16.067 PM EST
presence not present Test Fob has departed DEVICE 2019-02-10 07:23:06.574 PM EST
presence present Test Fob has arrived DEVICE 2019-02-10 05:34:14.080 PM EST
presence not present Test Fob has departed DEVICE 2019-02-10 04:13:49.915 PM EST
presence present Test Fob has arrived DEVICE 2019-02-10 02:34:32.242 PM EST

I have another fob that lives in my carry bag with my iPads and laptop...

presence present Steve's Work Bag Fob has arrived DEVICE 2019-03-06 12:52:05.170 AM EST
presence not present Steve's Work Bag Fob has departed DEVICE 2019-03-06 12:47:44.671 AM EST

Again with the same correlation as the other events. Its almost as if the hub stopped processing Zigbee events, only from fobs though. This one however, aside from todays event, has been rock solid.

These are my only misbehaving Zigbee devices.

Presence Central has the facility to β€˜delay’ leaving or arriving if you want to have a look.

I had to add this because I have an app that screams if the presence sensors in the cars leave after midnight.
WAF is not high when the house screams at 3am :slight_smile:

Andy

@Somel
what do you have your arrival sensor Presence timeout set at?

@mike.maxwell
could you add longer or ability for a custom Presence timeout to the DH?

mine is set to the max 5 minutes and I rarely (but looking through the logs occasionally) have the random depart and arrive.

Default value?! 2min....

By the way what does this feature really do?! What happens on no selection?!

10 minute and 15 minute options will be in 2.0.7, also added the Tone capability (beep)

3 Likes

Can I create a dashboard tile with the attribute?!

Beep is the command published by the tone capability, Tone has no attributes, and I do not know if Tone is published in the dashboard.

This is the amount of time from last sensor check in to the departed event. If the device comes back online before the timeout expires, no events are generated.

LMAO :innocent:

2 Likes

@mike.maxwell
just curious
so if "no selection" is selected on the presence timeout does it just use the default timeout?

Yes, default is 2 minutes.

I hope the changes doesn't screw up the presence, from day 1 (I started with HE in July 2018) I never had any issues with HE and presence, I only saw that behavior of going not present randomly when I was with ST combined with one of 2 other events, the internet dropped or the sensor was routed through a bulb. I never seen my presence changed when the hub is doing backup or any other events like the one I mentioned for ST. My presence has been so good that I wanted to have 1 minute timeout.

adding beep, verifying battery reports work and adding a few more timeout options isn't going to effect the base functionality, that section in the driver wasn't touched.

4 Likes

This is awesome, thanks Mike!

There's a driver I found in the ST forum that might be of help to some folks. The driver allows you to disable reading the presence from the fob and set it manually by a command of Disable. I've set it up so the fob is "disabled" when I'm not in away mode and when in away mode, it "enables" 5 minutes later (via RM). It also supports the beep function.

import groovy.json.JsonOutput

/**
 *
 * Arrival Sensor HA with Disable
 *
 *  Copyright 2018 Warren Poschman
 *
 *  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.1"
}

/*
* Change Log:
* 2018-7-29  - (1.1) Improved tracking and added user options to force mode upon enable / disable
* 2018-7-26  - (1.0) Initial release
* 2018-7-24  - (0.1) Debug release
*/


metadata {
    definition (name: "Arrival Sensor HA with Disable", namespace: "LLWarrenP", author: "LLWarrenP") {
        capability "Tone"
        capability "Actuator"
        capability "Presence Sensor"
        capability "Sensor"
        capability "Battery"
        capability "Configuration"
        capability "Health Check"

		attribute "enabled", "string"
        command "enable"
        command "disable"
        command "toggle"

        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
        }
    }
}

def updated() {
    startTimer()
}

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 configure() {
    def cmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.batteryConfig(20, 20, 0x01)
    log.debug "configure -- cmds: ${cmds}"
    return cmds
}

def beep() {
    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
            ]
            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) {
        log.debug "Sensor is present"
        startTimer()
    } else if (!present) {
        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
    	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)
	   	log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value} with status ${device.currentValue("enabled")}"
    	sendEvent(eventMap)
    }
}

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

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

def checkPresenceCallback() {
    def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
    def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
    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
    log.debug "Setting sensor presence to ${settings.enabledMode}"
	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
	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
    log.debug "Setting sensor presence to ${settings.disabledMode}"
	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
	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)
}

I didn't even have to make any modifications to this one...it worked directly from ST. :slight_smile:

1 Like

Thanks Ryan,
This is a great find and would be nice if @mike.maxwell could incorporate such feature on the HE Driver just for a reason of support.

This could help the "ghosts leaves" to disappear when at home.

Question though if it is disabled how does it know that it has left the house?
I'm guessing that you use other rules besides the fob to know if you have left the house correct?

1 Like