Ping + Arp custom_component device tracker

Orange pi zero, network by lan and ipv6 disabled.
My router is tp link c2600 and i don’t have any disconnections nor any other issues with it.

Could you please add an aditional device tracker using the same custom component (ping_arp) for your router? Something like this:

device_tracker:

 - platform: ping_arp
   count: 5
   consider_home: 30
   hosts:
     iphone: <iphone ip>
     router: <router ip>

and leave it for, lets say 24 hours, and then compare the history of your iphone and your router and check if the not_home moments of your iphone correspond with those of your router.

great idea, i’m not home for some days but i will tell you if it worked. thanks

Well I tried with 3 iPhones on a similar router (tp link C3150) and all iphones got their status “not_home” at the same moment…
So it looks like a router issue indeed. Any idea of what setting to change ?

try to set consider_home: 180 (3 minutes) and go decreasing this value till you get a consistent status

Hey please see this as it may help improve this component

2 Likes

Very interesting. I´ll give it a try asap !!
Thanks a lot @jwelter and good job !!

Hi,

You may want to mention that you need to install “arp” otherwise you end up with an error “arp directory not found”.

On a Debian (hassbian, ubuntu etc) system it would be something like “sudo apt-get install net-tools nmap”.

I’ve just installed it and will see how the iOS devices perform over night.

Thanks for the share, much appreciated.

Simon

I set “consider_home” to 180 but it has no effect, my phone is still not home for 1 minutes, times to times.

What router do you have and what is your iOS version ?
it still don’t work for me as consider_home has no effect

Update :
I added my router to the component and it is always online, no disconnection. Whereas my iPhone disconnect times to times.
I made tests in 2 different house with 2 routers, 4 iPhones and 2 different HA system. Same issue. So I guess the bluetooth beacon is the only way to track iPhones.

Your custom_component Ping + Arp worked fine until HA 0.91.4.
From the HA 0.92.0b1 that does not work anymore. I get the following error message under “Check Configuration”: “Integration ping_arp not found when trying to verify its device_tracker platform.”
Can you please check that once?

Just need to create an empy file __init__.py and it’ll be back working :slight_smile:

1 Like

is a manifest.json file needed?

I would say that it is not necessary

After upgrading to 0.94.0 I am getting

Platform not found: device_tracker.composite 
Platform not found: device_tracker.ping_arp

when doing a configuration check, device trackers are not updated.

Me too, “Platform not found: device_tracker.ping_arp” after upgrading to 0.94.0.

Got it fixed by modifying device_tracker.py

"""
Tracks devices by sending a ICMP echo request (ping) and query arp table.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ping/
"""
import logging
import subprocess
import sys
import re
from datetime import timedelta

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
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 import util
from homeassistant import const

_LOGGER = logging.getLogger(__name__)

CONF_PING_COUNT = 'count'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(const.CONF_HOSTS): {cv.string: cv.string},
    vol.Optional(CONF_PING_COUNT, default=1): cv.positive_int,
})


class Host:
    """Host object with ping detection."""

    def __init__(self, ip_address, dev_id, hass, config):
        """Initialize the Host pinger."""
        self.hass = hass
        self.ip_address = ip_address
        self.dev_id = dev_id
        self._count = config[CONF_PING_COUNT]
        self._ping_cmd = ['ping', '-n', '-q', '-c1', '-W1', self.ip_address]
        self._parp_cmd = ['arp', '-n', self.ip_address]

    def ping(self):
        """Send an ICMP echo request and return True if success."""
        pinger = subprocess.Popen(self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
        try:
            pinger.communicate()
            return pinger.returncode == 0
        except subprocess.CalledProcessError:
            return False

   
    def parp(self):
        """Get the MAC address for a given IP."""
        arp = subprocess.Popen(self._parp_cmd, stdout=subprocess.PIPE)
        out, _ = arp.communicate() 
        match = re.search(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})', str(out))
        if match:
             return True
        return False

    def update(self, see):
        """Update device state by sending one or more ping messages."""
        failed = 0
        while failed < self._count:  # check more times if host is unreachable

            if self.ping():
                see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER)
                _LOGGER.info("Ping OK from %s", self.ip_address)
                return True
            _LOGGER.info("No response from %s failed=%d", self.ip_address, failed)
           
            if self.parp():  
                 see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER)
                 _LOGGER.info("Arp OK from %s", self.ip_address)
                 return True
            _LOGGER.info("No MAC address found") 
            failed += 1 

def setup_scanner(hass, config, see, discovery_info=None):
    """Set up 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 = config.get(CONF_SCAN_INTERVAL, timedelta(seconds=len(hosts) * config[CONF_PING_COUNT]) + DEFAULT_SCAN_INTERVAL)

    _LOGGER.info("Started ping tracker with interval=%s on hosts: %s", interval, ",".join([host.ip_address for host in hosts]))

    def update_interval(now):
        """Update all the hosts on every interval time."""
        try:
            for host in hosts:
                host.update(see)
        finally:
            hass.helpers.event.track_point_in_utc_time(
                update_interval, util.dt.utcnow() + interval)

    update_interval(None)
    return True
1 Like

Doesn’t work; devices do not show up in the integrations page…

Hi Anilet,

I had to change default_scan_interval to just scan_interval on line 87 to get it to start working. Thanks for posting, I would have never figured it out.

FYI…this is what works for me.

"""
Tracks devices by sending a ICMP echo request (ping) and query arp table.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ping/
"""

import logging
import subprocess
import sys
import re
from datetime import timedelta

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
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 import util
from homeassistant import const

_LOGGER = logging.getLogger(__name__)

CONF_PING_COUNT = 'count'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(const.CONF_HOSTS): {cv.string: cv.string},
    vol.Optional(CONF_PING_COUNT, default=1): cv.positive_int,
})


class Host:
    """Host object with ping detection."""

    def __init__(self, ip_address, dev_id, hass, config):
        """Initialize the Host pinger."""
        self.hass = hass
        self.ip_address = ip_address
        self.dev_id = dev_id
        self._count = config[CONF_PING_COUNT]
        self._ping_cmd = ['ping', '-n', '-q', '-c1', '-W1', self.ip_address]
        self._parp_cmd = ['arp', '-n', self.ip_address]

    def ping(self):
        """Send an ICMP echo request and return True if success."""
        pinger = subprocess.Popen(self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
        try:
            pinger.communicate()
            return pinger.returncode == 0
        except subprocess.CalledProcessError:
            return False

   
    def parp(self):
        """Get the MAC address for a given IP."""
        arp = subprocess.Popen(self._parp_cmd, stdout=subprocess.PIPE)
        out, _ = arp.communicate() 
        match = re.search(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})', str(out))
        if match:
             return True
        return False

    def update(self, see):
        """Update device state by sending one or more ping messages."""
        failed = 0
        while failed < self._count:  # check more times if host is unreachable

            if self.ping():
                see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER)
                _LOGGER.info("Ping OK from %s", self.ip_address)
                return True
            _LOGGER.info("No response from %s failed=%d", self.ip_address, failed)
           
            if self.parp():  
                 see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER)
                 _LOGGER.info("Arp OK from %s", self.ip_address)
                 return True
            _LOGGER.info("No MAC address found") 
            failed += 1 

def setup_scanner(hass, config, see, discovery_info=None):
    """Set up 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 = config.get(CONF_SCAN_INTERVAL, timedelta(seconds=len(hosts) * config[CONF_PING_COUNT]) + SCAN_INTERVAL)

    _LOGGER.info("Started ping tracker with interval=%s on hosts: %s", interval, ",".join([host.ip_address for host in hosts]))

    def update_interval(now):
        """Update all the hosts on every interval time."""
        try:
            for host in hosts:
                host.update(see)
        finally:
            hass.helpers.event.track_point_in_utc_time(
                update_interval, util.dt.utcnow() + interval)

    update_interval(None)
    return True
1 Like

@Twit HA restarted without error and I assumed everything working, anyway thanks for figuring it out.