No, not iCloud. That is far too heavy. All iOS devices have a secret magic way to poll if they are present as iTunes does this when you turn on the wireless sync with a device. So the tracker uses this same mechanism to see if devices are present. This is a UDP message sent to port 5353 on the iOS device.
The challenge is you need to manually set your iOS devices to have static IP addresses (or DHCP reservations) and also need to turn that devices wireless iTunes sync on in iTunes.
Power hit is about 3% per day once this tracker was added. I tested by leaving phone sit idle with cellular turned off just on WIFI. I measured the % drop over 3 days without the tracker and also with the tracker. The tracker showed a 9% additional drop over 3 days. Not super scientific.
Tracker is below:
"""
Tracks iPhones by sending a udp message to port 5353.
An entry in the arp cache is then made and checked.
device_tracker:
- platform: iphonedetect
hosts:
host_one: 192.168.2.12
host_two: 192.168.2.25
"""
import logging
import subprocess
import sys
from datetime import timedelta
import socket
import voluptuous as vol
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, DEFAULT_SCAN_INTERVAL, SOURCE_TYPE_ROUTER)
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant import util
from homeassistant import const
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = []
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(const.CONF_HOSTS): {cv.string: cv.string},
})
class Host:
"""Host object with arp detection."""
def __init__(self, ip_address, dev_id, hass, config):
"""Initialize the Host."""
self.hass = hass
self.ip_address = ip_address
self.dev_id = dev_id
def detectiphone(self):
"""Send udp message to port 5353
and return True if an arp chache entry is made success.
"""
aSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
aSocket.settimeout(1)
addr = (self.ip_address, 5353)
message = b'Steve Jobs'
aSocket.sendto(message, addr)
try:
output = subprocess.check_output('arp -na', shell=True)
output = output.decode('utf-8').split('\n')
for entry in output:
mac = entry.split(' ')
if mac[0] != '':
rcvd_ip = mac[1]
rcvd_ip = rcvd_ip[:-1] # remove last Klammer
rcvd_ip = rcvd_ip[1:] # remove first Klammer
mac = mac[3]
mac = mac.split(':')
if rcvd_ip == self.ip_address:
if len(mac) == 6:
return True
except subprocess.CalledProcessError:
return False
return False
def update(self, see):
"""Update device state."""
if self.detectiphone():
see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER)
return True
_LOGGER.debug("iPhone on ip=%s not present", self.ip_address)
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the Host objects and return the update function."""
hosts = [Host(ip, dev_id, hass, config) for (dev_id, ip) in
config[const.CONF_HOSTS].items()]
interval = DEFAULT_SCAN_INTERVAL
_LOGGER.info("Started iphonedetect with interval=%s on hosts: %s",
interval, ",".join([host.ip_address for host in hosts]))
def update(now):
"""Update all the hosts on every interval time."""
for host in hosts:
host.update(see)
track_point_in_utc_time(hass, update, now + interval)
return True
return update(util.dt.utcnow())