[RELEASE] iPhone WiFi Presence Sensor



Those lines are part of the updated() routine, IIRC. So, when you hit SAVE on the Device page, the updated() function is called. The first runEvery1Minute(refresh) call sets up a recurring schedule that will execute once every 1 minute forever. The second one will execute in 2 seconds after you hit SAVE, but just once.


Thank you. Just starting to learn how everything works under the covers.


Why not just use a RPI that preforms ARP scans on the Mac Addresss instead since iPhones don’t always respond to Ping?


Not sure if you can initiate an ARP request from HE and web sockets. Good idea though.


I am really enjoying this driver and being off any cloud based presence apps/drivers. I would like to narrow the refresh time to 30 seconds. The one minute refresh is leaving me standing at the front door if I happen to arrive just after the last refresh took place.

Right now I have a refresh rule running every 30 seconds with the 'runEvery1Minute(refresh) commented out. I couldn't find any documentation on HE supporting a 'runEvery30Seconds'.

Is this the best way to do this?

Thank you!


Are you seeing drops once every 10-11 minutes? Apparently after iOS 10.1.3 update my phone now sleeps somewhere in that timeframe. Just curious about others.


PSA: 2.05 will likely break this driver as it did for me. Attached is the code that @ogiewon came up with to fix the issue at least for me. Thanks to all!

 *  iPhone WiFi Presence Sensor
 *  Copyright 2019 Joel Wetzel
 *  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.

import groovy.json.*
metadata {
	definition (name: "iPhone WiFi Presence Sensor", namespace: "joelwetzel", author: "Joel Wetzel") {
		capability "Refresh"
		capability "Sensor"
        capability "Presence Sensor"

	preferences {
		section {
			input (
				type: "string",
				name: "ipAddress",
				title: "iPhone IP Address",
				required: true				
			input (
				type: "number",
				name: "timeoutMinutes",
				title: "Timeout Minutes",
				description: "Approximate number of minutes without a response before deciding the device is away/offline.",
				required: true,
				defaultValue: 3
			input (
				type: "bool",
				name: "enableDebugLogging",
				title: "Enable Debug Logging?",
				required: true,
				defaultValue: true

def log(msg) {
	if (enableDebugLogging) {

def installed () {
	log.info "${device.displayName}.installed()"

def updated () {
	log.info "${device.displayName}.updated()"
    state.tryCount = 0
    runEvery1Minute(refresh)		// Generally test it every minute.
    runIn(2, refresh)				// But test it once, right after we install or update it too.

def refresh() {
	log "${device.displayName}.refresh()"

	state.tryCount = state.tryCount + 1
    if (state.tryCount > (timeoutMinutes < 1 ? 1 : timeoutMinutes) && device.currentValue('presence') != "not present") {
        def descriptionText = "${device.displayName} is OFFLINE";
        log descriptionText
        sendEvent(name: "presence", value: "not present", linkText: deviceName, descriptionText: descriptionText)
	if (ipAddress == null || ipAddress.size() == 0) {
	asynchttpGet("httpGetCallback", [
		uri: "http://${ipAddress}/"	

def httpGetCallback(response, data) {
	//log.debug "${device.displayName}: httpGetCallback(${groovy.json.JsonOutput.toJson(response)}, data)"
	//if (response != null && response instanceof Map && response.status == 408 && response.errorMessage.contains("Connection refused")) {
	if (response != null && response.status == 408 && response.errorMessage.contains("Connection refused")) {
		state.tryCount = 0
		if (device.currentValue('presence') != "present") {
			def descriptionText = "${device.displayName} is ONLINE";
			log descriptionText
			sendEvent(name: "presence", value: "present", linkText: deviceName, descriptionText: descriptionText)


I was just about to post that revision! Thanks for saving me the trouble! :slight_smile:

@jwetzel1492 - The response is no longer returned as a map, so I removed that criteria.

	//if (response != null && response instanceof Map && response.status == 408 && response.errorMessage.contains("Connection refused")) {
	if (response != null && response.status == 408 && response.errorMessage.contains("Connection refused")) {

Not sure if there will be an negative consequences of this, but it seems to be working properly with Hubitat firmware v2.0.5.112.


Nice catch man and thanks @ogiewon for the quick patch.


I had to disable iPhone WiFi Presence Sensor from my logic. I tried to use Combined Presence and Presence Central to combine both locative and iPhone WiFi Presence together. For some odd reason(s) my main virtual presence plus sensor would get tripped to not present every 10-12 minutes due to iPhones going into power saving mode and then 1 minute late be present. The issue was discovered using the Welcome Home app. Since the presence would trip the app would place announcements for everyone in its list when the contact sensor would get tripped even though no-one left.

So, what is everyone’s logic to increase presence control while ensuring ghost incidents are minimal if not completely removed?


That implies that locative thought you were away from home? Did you happen to notice locative’s state at the time?


Thanks @halfrican.ak and @ogiewon! I have updated the code in github. Anyone who has installed this driver should update.


I have three Presence devices for each person.

  • Person
  • Person-Locative
  • Person-iPhone

Both apps used locative and iPhone to determine Person Presence status.

Locative stays present entire time whereas iPhone flips back and forth which is a documented issue with iPhones in general.

The two apps I tried wouldn’t use the OR statement to establish presence. I can’t seem to get this to work reliably to keep Presence from flipping back and forth.


Combined Presence needs separate input and output presence sensors. Did you have the separate ones created beforehand?



Input Sensor(s): Person-Locative and Person-iPhone
Output Sensor: Person


I am testing @jwetzel1492's Combined Presence App on my Hubitat Development Hub. I am using Locative on my iPhone 7 + @jwetzel1492's iPhone WiFi Presence Sensor. Here is a Grafana chart showing my "Combined" virtual presence sensor working exactly as expected. It is a little hard to see the green "Combined" trace as it so closely mirrors the Locative trace. I also changed the scale for the right-hand axis to make the WiFi trace not conflict with the other two.


Ok, I'll list off the top of my head everything I can think of that could be keeping it from operating the way I imagine it should from your descriptions:

  • Locative is incorrectly showing Not Present
  • Something in the configuration isn't actually how you're describing it. (for example, you have extra virtual presence sensors, and it's set to a different one than you think)
  • The code for Combined Presence has been modified and isn't the same as on GitHub
  • You have both Combined Presence and Presence Central installed and running at the same time (you should only use one)

Can you double-check all this?


Answered the quote above in bold.


Thanks for the answers. One more brainstorm option:

  • The automations are triggering off of the iPhone WiFi Presence sensor, not the combined sensor.


Apps are Triggering off combined presence.

I am now testing a RM rule that only enforces Presence active based on the iPhone WiFi but keeps locative as the response for departed. Having better luck with this but would rather use an app.