diff --git a/.coveragerc b/.coveragerc index 34531651358..bd0376ee87a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -157,6 +157,7 @@ omit = homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py + homeassistant/components/device_tracker/ping.py homeassistant/components/device_tracker/snmp.py homeassistant/components/device_tracker/swisscom.py homeassistant/components/device_tracker/thomson.py diff --git a/homeassistant/components/device_tracker/ping.py b/homeassistant/components/device_tracker/ping.py new file mode 100644 index 00000000000..de75a09a943 --- /dev/null +++ b/homeassistant/components/device_tracker/ping.py @@ -0,0 +1,92 @@ +""" +Tracks devices by sending a ICMP ping. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.ping/ + +device_tracker: + - platform: ping + count: 2 + hosts: + host_one: pc.local + host_two: 192.168.2.25 +""" +import logging +import subprocess +import sys +from datetime import timedelta + +import voluptuous as vol + +from homeassistant.components.device_tracker import ( + PLATFORM_SCHEMA, DEFAULT_SCAN_INTERVAL) +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__) + +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] + if sys.platform == "win32": + self._ping_cmd = ['ping', '-n 1', '-w 1000', self.ip_address] + else: + self._ping_cmd = ['ping', '-n', '-q', '-c1', '-W1', + self.ip_address] + + def ping(self): + """Send ICMP ping and return True if success.""" + pinger = subprocess.Popen(self._ping_cmd, stdout=subprocess.PIPE) + try: + pinger.communicate() + return pinger.returncode == 0 + except subprocess.CalledProcessError: + 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 in unreachable + if self.ping(): + see(dev_id=self.dev_id) + return True + failed += 1 + + _LOGGER.debug("ping KO on ip=%s failed=%d", self.ip_address, failed) + + +def setup_scanner(hass, config, see): + """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) * 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(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())