Prevent ping integration from delaying startup (#43869)
This commit is contained in:
parent
b26779a27a
commit
bee55a0494
8 changed files with 196 additions and 109 deletions
|
@ -10,7 +10,7 @@ import re
|
|||
import sys
|
||||
from typing import Any
|
||||
|
||||
from icmplib import SocketPermissionError, ping as icmp_ping
|
||||
from icmplib import NameLookupError, ping as icmp_ping
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
|
@ -18,12 +18,12 @@ from homeassistant.components.binary_sensor import (
|
|||
PLATFORM_SCHEMA,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, STATE_ON
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.reload import setup_reload_service
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from . import DOMAIN, PLATFORMS, async_get_next_ping_id
|
||||
from .const import PING_TIMEOUT
|
||||
from . import async_get_next_ping_id
|
||||
from .const import DOMAIN, ICMP_TIMEOUT, PING_PRIVS, PING_TIMEOUT
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -63,32 +63,30 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None) -> None:
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None
|
||||
) -> None:
|
||||
"""Set up the Ping Binary sensor."""
|
||||
setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||
|
||||
host = config[CONF_HOST]
|
||||
count = config[CONF_PING_COUNT]
|
||||
name = config.get(CONF_NAME, f"{DEFAULT_NAME} {host}")
|
||||
|
||||
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:
|
||||
privileged = hass.data[DOMAIN][PING_PRIVS]
|
||||
if privileged is None:
|
||||
ping_cls = PingDataSubProcess
|
||||
else:
|
||||
ping_cls = PingDataICMPLib
|
||||
|
||||
ping_data = ping_cls(hass, host, count)
|
||||
|
||||
add_entities([PingBinarySensor(name, ping_data)], True)
|
||||
async_add_entities(
|
||||
[PingBinarySensor(name, ping_cls(hass, host, count, privileged))]
|
||||
)
|
||||
|
||||
|
||||
class PingBinarySensor(BinarySensorEntity):
|
||||
class PingBinarySensor(RestoreEntity, BinarySensorEntity):
|
||||
"""Representation of a Ping Binary sensor."""
|
||||
|
||||
def __init__(self, name: str, ping) -> None:
|
||||
"""Initialize the Ping Binary sensor."""
|
||||
self._available = False
|
||||
self._name = name
|
||||
self._ping = ping
|
||||
|
||||
|
@ -97,6 +95,11 @@ class PingBinarySensor(BinarySensorEntity):
|
|||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def available(self) -> str:
|
||||
"""Return if we have done the first ping."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def device_class(self) -> str:
|
||||
"""Return the class of this sensor."""
|
||||
|
@ -105,7 +108,7 @@ class PingBinarySensor(BinarySensorEntity):
|
|||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._ping.available
|
||||
return self._ping.is_alive
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
|
@ -121,6 +124,28 @@ class PingBinarySensor(BinarySensorEntity):
|
|||
async def async_update(self) -> None:
|
||||
"""Get the latest data."""
|
||||
await self._ping.async_update()
|
||||
self._available = True
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Restore previous state on restart to avoid blocking startup."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
last_state = await self.async_get_last_state()
|
||||
if last_state is not None:
|
||||
self._available = True
|
||||
|
||||
if last_state is None or last_state.state != STATE_ON:
|
||||
self._ping.data = False
|
||||
return
|
||||
|
||||
attributes = last_state.attributes
|
||||
self._ping.is_alive = True
|
||||
self._ping.data = {
|
||||
"min": attributes[ATTR_ROUND_TRIP_TIME_AVG],
|
||||
"max": attributes[ATTR_ROUND_TRIP_TIME_MAX],
|
||||
"avg": attributes[ATTR_ROUND_TRIP_TIME_MDEV],
|
||||
"mdev": attributes[ATTR_ROUND_TRIP_TIME_MIN],
|
||||
}
|
||||
|
||||
|
||||
class PingData:
|
||||
|
@ -132,26 +157,37 @@ class PingData:
|
|||
self._ip_address = host
|
||||
self._count = count
|
||||
self.data = {}
|
||||
self.available = False
|
||||
self.is_alive = False
|
||||
|
||||
|
||||
class PingDataICMPLib(PingData):
|
||||
"""The Class for handling the data retrieval using icmplib."""
|
||||
|
||||
def __init__(self, hass, host, count, privileged) -> None:
|
||||
"""Initialize the data object."""
|
||||
super().__init__(hass, host, count)
|
||||
self._privileged = privileged
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Retrieve the latest details from the host."""
|
||||
_LOGGER.debug("ping address: %s", self._ip_address)
|
||||
data = await self.hass.async_add_executor_job(
|
||||
partial(
|
||||
icmp_ping,
|
||||
self._ip_address,
|
||||
count=self._count,
|
||||
timeout=1,
|
||||
id=async_get_next_ping_id(self.hass),
|
||||
try:
|
||||
data = await self.hass.async_add_executor_job(
|
||||
partial(
|
||||
icmp_ping,
|
||||
self._ip_address,
|
||||
count=self._count,
|
||||
timeout=ICMP_TIMEOUT,
|
||||
id=async_get_next_ping_id(self.hass),
|
||||
privileged=self._privileged,
|
||||
)
|
||||
)
|
||||
)
|
||||
self.available = data.is_alive
|
||||
if not self.available:
|
||||
except NameLookupError:
|
||||
self.is_alive = False
|
||||
return
|
||||
|
||||
self.is_alive = data.is_alive
|
||||
if not self.is_alive:
|
||||
self.data = False
|
||||
return
|
||||
|
||||
|
@ -166,7 +202,7 @@ class PingDataICMPLib(PingData):
|
|||
class PingDataSubProcess(PingData):
|
||||
"""The Class for handling the data retrieval using the ping binary."""
|
||||
|
||||
def __init__(self, hass, host, count) -> None:
|
||||
def __init__(self, hass, host, count, privileged) -> None:
|
||||
"""Initialize the data object."""
|
||||
super().__init__(hass, host, count)
|
||||
if sys.platform == "win32":
|
||||
|
@ -254,4 +290,4 @@ class PingDataSubProcess(PingData):
|
|||
async def async_update(self) -> None:
|
||||
"""Retrieve the latest details from the host."""
|
||||
self.data = await self.async_ping()
|
||||
self.available = bool(self.data)
|
||||
self.is_alive = bool(self.data)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue