From 7c191388a9181c8e98e6dc28fd85b34ce4c984f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Aug 2020 08:50:09 -0500 Subject: [PATCH] Use icmplib for ping when available (#39284) * Use icmplib for ping when available * Update homeassistant/components/ping/binary_sensor.py Co-authored-by: Paulus Schoutsen * Revert "Update homeassistant/components/ping/binary_sensor.py" This reverts commit 618f42512a89834bb8b2ed7830e7e77d79a29f44. * move it up so its easier to see Co-authored-by: Paulus Schoutsen --- .../components/ping/binary_sensor.py | 44 +++++++++++++++++-- homeassistant/components/ping/const.py | 1 + .../components/ping/device_tracker.py | 43 ++++++++++++++++-- homeassistant/components/ping/manifest.json | 1 + requirements_all.txt | 3 ++ 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index 0c568bb7892..535c5bcd6a2 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -6,6 +6,7 @@ import re import sys from typing import Any, Dict +from icmplib import SocketPermissionError, ping as icmp_ping import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity @@ -59,7 +60,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None) -> None: count = config[CONF_PING_COUNT] name = config.get(CONF_NAME, f"{DEFAULT_NAME} {host}") - add_entities([PingBinarySensor(name, PingData(host, count))], True) + try: + # Verify we can create a raw socket, or + # fallback to using a subprocess + icmp_ping("127.0.0.1", count=0, timeout=0) + ping_cls = PingDataICMPLib + except SocketPermissionError: + ping_cls = PingDataSubProcess + + ping_data = ping_cls(hass, host, count) + + add_entities([PingBinarySensor(name, ping_data)], True) class PingBinarySensor(BinarySensorEntity): @@ -102,15 +113,42 @@ class PingBinarySensor(BinarySensorEntity): class PingData: - """The Class for handling the data retrieval.""" + """The base class for handling the data retrieval.""" - def __init__(self, host, count) -> None: + def __init__(self, hass, host, count) -> None: """Initialize the data object.""" + self.hass = hass self._ip_address = host self._count = count self.data = {} self.available = False + +class PingDataICMPLib(PingData): + """The Class for handling the data retrieval using icmplib.""" + + def ping(self): + """Send ICMP echo request and return details.""" + return icmp_ping(self._ip_address, count=self._count) + + async def async_update(self) -> None: + """Retrieve the latest details from the host.""" + data = await self.hass.async_add_executor_job(self.ping) + self.data = { + "min": data.min_rtt, + "max": data.max_rtt, + "avg": data.avg_rtt, + "mdev": "", + } + self.available = data.is_alive + + +class PingDataSubProcess(PingData): + """The Class for handling the data retrieval using the ping binary.""" + + def __init__(self, hass, host, count) -> None: + """Initialize the data object.""" + super().__init__(hass, host, count) if sys.platform == "win32": self._ping_cmd = [ "ping", diff --git a/homeassistant/components/ping/const.py b/homeassistant/components/ping/const.py index 8be8c1bdaa3..89b93c84169 100644 --- a/homeassistant/components/ping/const.py +++ b/homeassistant/components/ping/const.py @@ -1,3 +1,4 @@ """Tracks devices by sending a ICMP echo request (ping).""" PING_TIMEOUT = 3 +PING_ATTEMPTS_COUNT = 3 diff --git a/homeassistant/components/ping/device_tracker.py b/homeassistant/components/ping/device_tracker.py index f4e2e806143..cbbce13171b 100644 --- a/homeassistant/components/ping/device_tracker.py +++ b/homeassistant/components/ping/device_tracker.py @@ -4,6 +4,7 @@ import logging import subprocess import sys +from icmplib import SocketPermissionError, ping as icmp_ping import voluptuous as vol from homeassistant import const, util @@ -16,7 +17,7 @@ from homeassistant.components.device_tracker.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.util.process import kill_subprocess -from .const import PING_TIMEOUT +from .const import PING_ATTEMPTS_COUNT, PING_TIMEOUT _LOGGER = logging.getLogger(__name__) @@ -31,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -class Host: +class HostSubProcess: """Host object with ping detection.""" def __init__(self, ip_address, dev_id, hass, config): @@ -72,10 +73,46 @@ class Host: _LOGGER.debug("No response from %s failed=%d", self.ip_address, failed) +class HostICMPLib: + """Host object with ping detection.""" + + def __init__(self, ip_address, dev_id, _, config): + """Initialize the Host pinger.""" + self.ip_address = ip_address + self.dev_id = dev_id + self._count = config[CONF_PING_COUNT] + + def ping(self): + """Send an ICMP echo request and return True if success.""" + return icmp_ping(self.ip_address, count=PING_ATTEMPTS_COUNT).is_alive + + def update(self, see): + """Update device state by sending one or more ping messages.""" + if self.ping(): + see(dev_id=self.dev_id, source_type=SOURCE_TYPE_ROUTER) + return True + + _LOGGER.debug( + "No response from %s (%s) failed=%d", + self.ip_address, + self.dev_id, + PING_ATTEMPTS_COUNT, + ) + + def setup_scanner(hass, config, see, discovery_info=None): """Set up the Host objects and return the update function.""" + + try: + # Verify we can create a raw socket, or + # fallback to using a subprocess + icmp_ping("127.0.0.1", count=0, timeout=0) + host_cls = HostICMPLib + except SocketPermissionError: + host_cls = HostSubProcess + hosts = [ - Host(ip, dev_id, hass, config) + host_cls(ip, dev_id, hass, config) for (dev_id, ip) in config[const.CONF_HOSTS].items() ] interval = config.get( diff --git a/homeassistant/components/ping/manifest.json b/homeassistant/components/ping/manifest.json index 887b48dbaae..f23697808a2 100644 --- a/homeassistant/components/ping/manifest.json +++ b/homeassistant/components/ping/manifest.json @@ -3,5 +3,6 @@ "name": "Ping (ICMP)", "documentation": "https://www.home-assistant.io/integrations/ping", "codeowners": [], + "requirements": ["icmplib==1.1.1"], "quality_scale": "internal" } diff --git a/requirements_all.txt b/requirements_all.txt index 96aecbde2ac..bb7f1aa0960 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -784,6 +784,9 @@ ibm-watson==4.0.1 # homeassistant.components.watson_iot ibmiotf==0.3.4 +# homeassistant.components.ping +icmplib==1.1.1 + # homeassistant.components.iglo iglo==1.2.7