Extend entities provided by Tailscale (#60785)
This commit is contained in:
parent
d0da0eef36
commit
8279873018
4 changed files with 163 additions and 3 deletions
|
@ -13,6 +13,7 @@ from homeassistant.components.binary_sensor import (
|
|||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TailscaleEntity
|
||||
|
@ -38,8 +39,51 @@ BINARY_SENSORS: tuple[TailscaleBinarySensorEntityDescription, ...] = (
|
|||
key="update_available",
|
||||
name="Client",
|
||||
device_class=BinarySensorDeviceClass.UPDATE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
is_on_fn=lambda device: device.update_available,
|
||||
),
|
||||
TailscaleBinarySensorEntityDescription(
|
||||
key="client_supports_hair_pinning",
|
||||
name="Supports Hairpinning",
|
||||
icon="mdi:wan",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
is_on_fn=lambda device: device.client_connectivity.client_supports.hair_pinning,
|
||||
),
|
||||
TailscaleBinarySensorEntityDescription(
|
||||
key="client_supports_ipv6",
|
||||
name="Supports IPv6",
|
||||
icon="mdi:wan",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
is_on_fn=lambda device: device.client_connectivity.client_supports.ipv6,
|
||||
),
|
||||
TailscaleBinarySensorEntityDescription(
|
||||
key="client_supports_pcp",
|
||||
name="Supports PCP",
|
||||
icon="mdi:wan",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
is_on_fn=lambda device: device.client_connectivity.client_supports.pcp,
|
||||
),
|
||||
TailscaleBinarySensorEntityDescription(
|
||||
key="client_supports_pmp",
|
||||
name="Supports NAT-PMP",
|
||||
icon="mdi:wan",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
is_on_fn=lambda device: device.client_connectivity.client_supports.pmp,
|
||||
),
|
||||
TailscaleBinarySensorEntityDescription(
|
||||
key="client_supports_udp",
|
||||
name="Supports UDP",
|
||||
icon="mdi:wan",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
is_on_fn=lambda device: device.client_connectivity.client_supports.udp,
|
||||
),
|
||||
TailscaleBinarySensorEntityDescription(
|
||||
key="client_supports_upnp",
|
||||
name="Supports UPnP",
|
||||
icon="mdi:wan",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
is_on_fn=lambda device: device.client_connectivity.client_supports.upnp,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.components.sensor import (
|
|||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import TailscaleEntity
|
||||
|
@ -24,7 +25,7 @@ from .const import DOMAIN
|
|||
class TailscaleSensorEntityDescriptionMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable[[TailscaleDevice], datetime | None]
|
||||
value_fn: Callable[[TailscaleDevice], datetime | str | None]
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -39,8 +40,22 @@ SENSORS: tuple[TailscaleSensorEntityDescription, ...] = (
|
|||
key="expires",
|
||||
name="Expires",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda device: device.expires,
|
||||
),
|
||||
TailscaleSensorEntityDescription(
|
||||
key="ip",
|
||||
name="IP Address",
|
||||
icon="mdi:ip-network",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda device: device.addresses[0] if device.addresses else None,
|
||||
),
|
||||
TailscaleSensorEntityDescription(
|
||||
key="last_seen",
|
||||
name="Last Seen",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=lambda device: device.last_seen,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@ -68,6 +83,6 @@ class TailscaleSensorEntity(TailscaleEntity, SensorEntity):
|
|||
entity_description: TailscaleSensorEntityDescription
|
||||
|
||||
@property
|
||||
def native_value(self) -> datetime | None:
|
||||
def native_value(self) -> datetime | str | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data[self.device_id])
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
"""Tests for the sensors provided by the Tailscale integration."""
|
||||
from homeassistant.components.binary_sensor import STATE_ON, BinarySensorDeviceClass
|
||||
from homeassistant.components.binary_sensor import (
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
BinarySensorDeviceClass,
|
||||
)
|
||||
from homeassistant.components.tailscale.const import DOMAIN
|
||||
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
@ -21,11 +26,83 @@ async def test_tailscale_binary_sensors(
|
|||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123456_update_available"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frencks-iPhone Client"
|
||||
assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.UPDATE
|
||||
assert ATTR_ICON not in state.attributes
|
||||
|
||||
state = hass.states.get("binary_sensor.frencks_iphone_supports_hairpinning")
|
||||
entry = entity_registry.async_get(
|
||||
"binary_sensor.frencks_iphone_supports_hairpinning"
|
||||
)
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123456_client_supports_hair_pinning"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == STATE_OFF
|
||||
assert (
|
||||
state.attributes.get(ATTR_FRIENDLY_NAME)
|
||||
== "Frencks-iPhone Supports Hairpinning"
|
||||
)
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:wan"
|
||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get("binary_sensor.frencks_iphone_supports_ipv6")
|
||||
entry = entity_registry.async_get("binary_sensor.frencks_iphone_supports_ipv6")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123456_client_supports_ipv6"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frencks-iPhone Supports IPv6"
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:wan"
|
||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get("binary_sensor.frencks_iphone_supports_pcp")
|
||||
entry = entity_registry.async_get("binary_sensor.frencks_iphone_supports_pcp")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123456_client_supports_pcp"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frencks-iPhone Supports PCP"
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:wan"
|
||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get("binary_sensor.frencks_iphone_supports_nat_pmp")
|
||||
entry = entity_registry.async_get("binary_sensor.frencks_iphone_supports_nat_pmp")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123456_client_supports_pmp"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frencks-iPhone Supports NAT-PMP"
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:wan"
|
||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get("binary_sensor.frencks_iphone_supports_udp")
|
||||
entry = entity_registry.async_get("binary_sensor.frencks_iphone_supports_udp")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123456_client_supports_udp"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frencks-iPhone Supports UDP"
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:wan"
|
||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get("binary_sensor.frencks_iphone_supports_upnp")
|
||||
entry = entity_registry.async_get("binary_sensor.frencks_iphone_supports_upnp")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123456_client_supports_upnp"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frencks-iPhone Supports UPnP"
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:wan"
|
||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||
|
||||
assert entry.device_id
|
||||
device_entry = device_registry.async_get(entry.device_id)
|
||||
assert device_entry
|
||||
|
|
|
@ -4,6 +4,7 @@ from homeassistant.components.tailscale.const import DOMAIN
|
|||
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
@ -21,11 +22,34 @@ async def test_tailscale_sensors(
|
|||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123457_expires"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == "2022-02-25T09:49:06+00:00"
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "router Expires"
|
||||
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP
|
||||
assert ATTR_ICON not in state.attributes
|
||||
|
||||
state = hass.states.get("sensor.router_last_seen")
|
||||
entry = entity_registry.async_get("sensor.router_last_seen")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123457_last_seen"
|
||||
assert entry.entity_category is None
|
||||
assert state.state == "2021-11-15T20:37:03+00:00"
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "router Last Seen"
|
||||
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP
|
||||
assert ATTR_ICON not in state.attributes
|
||||
|
||||
state = hass.states.get("sensor.router_ip_address")
|
||||
entry = entity_registry.async_get("sensor.router_ip_address")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "123457_ip"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert state.state == "100.11.11.112"
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "router IP Address"
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:ip-network"
|
||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||
|
||||
assert entry.device_id
|
||||
device_entry = device_registry.async_get(entry.device_id)
|
||||
assert device_entry
|
||||
|
|
Loading…
Add table
Reference in a new issue