mirror of https://github.com/home-assistant/core
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:
parent
b4bac0f7a0
commit
7c191388a9
|
@ -6,6 +6,7 @@ import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from icmplib import SocketPermissionError, ping as icmp_ping
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity
|
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]
|
count = config[CONF_PING_COUNT]
|
||||||
name = config.get(CONF_NAME, f"{DEFAULT_NAME} {host}")
|
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):
|
class PingBinarySensor(BinarySensorEntity):
|
||||||
|
@ -102,15 +113,42 @@ class PingBinarySensor(BinarySensorEntity):
|
||||||
|
|
||||||
|
|
||||||
class PingData:
|
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."""
|
"""Initialize the data object."""
|
||||||
|
self.hass = hass
|
||||||
self._ip_address = host
|
self._ip_address = host
|
||||||
self._count = count
|
self._count = count
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.available = False
|
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":
|
if sys.platform == "win32":
|
||||||
self._ping_cmd = [
|
self._ping_cmd = [
|
||||||
"ping",
|
"ping",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
"""Tracks devices by sending a ICMP echo request (ping)."""
|
"""Tracks devices by sending a ICMP echo request (ping)."""
|
||||||
|
|
||||||
PING_TIMEOUT = 3
|
PING_TIMEOUT = 3
|
||||||
|
PING_ATTEMPTS_COUNT = 3
|
||||||
|
|
|
@ -4,6 +4,7 @@ import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from icmplib import SocketPermissionError, ping as icmp_ping
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import const, util
|
from homeassistant import const, util
|
||||||
|
@ -16,7 +17,7 @@ from homeassistant.components.device_tracker.const import (
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util.process import kill_subprocess
|
from homeassistant.util.process import kill_subprocess
|
||||||
|
|
||||||
from .const import PING_TIMEOUT
|
from .const import PING_ATTEMPTS_COUNT, PING_TIMEOUT
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Host:
|
class HostSubProcess:
|
||||||
"""Host object with ping detection."""
|
"""Host object with ping detection."""
|
||||||
|
|
||||||
def __init__(self, ip_address, dev_id, hass, config):
|
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)
|
_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):
|
def setup_scanner(hass, config, see, discovery_info=None):
|
||||||
"""Set up the Host objects and return the update function."""
|
"""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 = [
|
hosts = [
|
||||||
Host(ip, dev_id, hass, config)
|
host_cls(ip, dev_id, hass, config)
|
||||||
for (dev_id, ip) in config[const.CONF_HOSTS].items()
|
for (dev_id, ip) in config[const.CONF_HOSTS].items()
|
||||||
]
|
]
|
||||||
interval = config.get(
|
interval = config.get(
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
"name": "Ping (ICMP)",
|
"name": "Ping (ICMP)",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ping",
|
"documentation": "https://www.home-assistant.io/integrations/ping",
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
|
"requirements": ["icmplib==1.1.1"],
|
||||||
"quality_scale": "internal"
|
"quality_scale": "internal"
|
||||||
}
|
}
|
||||||
|
|
|
@ -784,6 +784,9 @@ ibm-watson==4.0.1
|
||||||
# homeassistant.components.watson_iot
|
# homeassistant.components.watson_iot
|
||||||
ibmiotf==0.3.4
|
ibmiotf==0.3.4
|
||||||
|
|
||||||
|
# homeassistant.components.ping
|
||||||
|
icmplib==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.iglo
|
# homeassistant.components.iglo
|
||||||
iglo==1.2.7
|
iglo==1.2.7
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue