iPhone device tracker on linux

Hello @ all,

sorry windows user, i have no access to a windows machine running home-assistant. So this will not work
for you.

I found a little script in a german forum for openhab: http://openhabforum.de/viewtopic.php?f=16&t=137
Based on this script an the original ping device tracker from home-assistant i wrote a little script.

The idea behind this is to send a message to the iPhone on udp port 5353. The iPhone responds in
some way so that an entry in the arp cache is made even when it is in deep sleep. This entry is detectet
by the script.

Howto setup:

Create a file in your configuration directory:

<config_dir>/custom_components/device_tracker/iphonedetect.py

and save this Code in this file:

"""
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 = timedelta(seconds=len(hosts)) + 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())

Add the following to your configuration.yaml:

device_tracker:
  - platform: iphonedetect
    hosts:
      host_one: your ip adress
      host_two: 192.168.2.25 # <- for example

Only ip adresses will work, no hostnames! So you have to assign a static ip adress to your iPhone,
probably with an entry in your router. Leave the default scan interval at the default value or make it
shorter. The arp cache is cleared by my system every 60 seconds. So udp messages must send in
this timeslot or the iPhone is detected as away.

I hope you have fun. Feel free to ask your questions and provide your feedback.

5 Likes

Hi @return01, im doing nearly the same, not so nice integrated in HA.

You should point out that “iPhone Wlan Sync” has to be enabled in ITunes for this to work.
Greets
Rainer

Hello @VDRainer,

Im not aware of this. I got an old iPhone 5s from my daughter. She resetet the phone and wipes
all data from it. I activatet it and upgraded it to iOs 10.2.1. I have made no other steps with this
phone, but it responds nicely to the script. But anyway when iPhone Wlan Sync is enabled
and it responds eventually better, why not.

Hi, I have tried the code but i got this error when I restart my HomeAssistant

17-03-21 20:43:03 ERROR (MainThread) [homeassistant.components.device_tracker] Error setting up platform iphonedetect
Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.4/site-packages/homeassistant/components/device_tracker/__init__.py", line 161, in async_setup_platform
    raise HomeAssistantError("Invalid device_tracker platform.")
homeassistant.exceptions.HomeAssistantError: Invalid device_tracker platform.

Any help will be appreciated
//Fredrik

Hi @Frippen,

currently i have no idea why this error happens. Please post your configuration and your version
of HA. Im running version 0.40.0.

And please post where you saved the python file. You have to create the above mentioned directorys
manuell.

<config_dir>  <-- is where your configuration.yaml lives!

In this directory you must create this directorys:

custom_components/device_tracker/

And inside the device_tracker directory you must create the above postet iphonedetect.py.

Hey,
I gave your device tracker a try. I’m running NMAP and Owntracks too. Things did not work out the way I wanted so I removed the iphonedetect.py file and also removed the addition in the configuration.yaml.
But when I look at Entity lists of states its still there. How can I remove it?

Never mind. i found them in the knowndevices.yaml file and deleted them there. :slight_smile:

I figured out. The user who is running HA-process didn’t have access to iphonedetect.py. When I fixed that everything works as expected.

been using this for some time now and wanted to let you know it works great!
i use it together with own tracks.

I found it drained my batteries like crazy. How about you?

Just wanted to say this works perfectly well for my family with 8 iOS devices floating around. Thanks!

Hi there, I have found your Iphone device tracker and it works great, nice work. There is the problem of the Home Assistant updates and the 0.92.0 just broke it. Will it be too much trouble for you to update it so we can keep using it? Thank you

Sorry, currently i am have no access anymore to home assistant. So I can not figure out what the problem is. Eventually another python developer can dig into the problem.
But nice to hear that this was useful for other people.

try create an empty file called __init__.py and place it in the custom component folder.

the filename has 2 underscores before and after init in its name

Thank you. That did it, it’s working again.

I’ve just upgraded to 0.94.1 and it seems like there is an issue with this device_tracker. If I am not mistaken, version 0.94 and higher have now changed how device_tracker are handled. Any of you having issues? This used to be one of my best person tracker and very reliable. My folder structure is: /custom_component/iphonedetect/device_tracker.py
I also have placed the init.py, manifest.json, services.yaml and pycache directory.
Anyone have any ideas on how to fix that?

Try this, working for me under 94. You also need an empty __init__.py

Code modified for the new HA device tracker; this will not work on pre 94 releases.

"""
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)
from homeassistant.components.device_tracker.const import (
    CONF_SCAN_INTERVAL, 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 cache 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 = 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())

FANTASTIC !! Thank you so much!
For others this is back in working order with the code posted above.

Just curious, was there a lot of code changes to fix these device_tracker issues? I have a few others that are not working and would love to see if there is an easy fix (find/replace) that I could try.

No, not a lot. They split the device tracker up and changed one constant name. Once that became obvious a 5 minute fix.

This is the best router based tracker, works great all the time, someone needs to get it to be an official component in Home Assistant. I hate to lose it on future updates

1 Like