Extend entities provided by Tailscale (#60785)

This commit is contained in:
Franck Nijhof 2021-12-02 03:47:10 +01:00 committed by GitHub
parent d0da0eef36
commit 8279873018
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 3 deletions

View file

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

View file

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

View file

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

View file

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