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 <paulus@home-assistant.io>

* Revert "Update homeassistant/components/ping/binary_sensor.py"

This reverts commit 618f42512a.

* move it up so its easier to see

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
J. Nick Koston 2020-08-28 08:50:09 -05:00 committed by GitHub
parent b4bac0f7a0
commit 7c191388a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 6 deletions

View File

@ -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",

View File

@ -1,3 +1,4 @@
"""Tracks devices by sending a ICMP echo request (ping)."""
PING_TIMEOUT = 3
PING_ATTEMPTS_COUNT = 3

View File

@ -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(

View File

@ -3,5 +3,6 @@
"name": "Ping (ICMP)",
"documentation": "https://www.home-assistant.io/integrations/ping",
"codeowners": [],
"requirements": ["icmplib==1.1.1"],
"quality_scale": "internal"
}

View File

@ -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