Add homekit_controller thread node capabilties diagnostic sensor (#76120)
This commit is contained in:
parent
842cc060f8
commit
72a0ca4871
6 changed files with 104 additions and 1 deletions
|
@ -88,6 +88,7 @@ CHARACTERISTIC_PLATFORMS = {
|
|||
CharacteristicsTypes.DENSITY_SO2: "sensor",
|
||||
CharacteristicsTypes.DENSITY_VOC: "sensor",
|
||||
CharacteristicsTypes.IDENTIFY: "button",
|
||||
CharacteristicsTypes.THREAD_NODE_CAPABILITIES: "sensor",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from collections.abc import Callable
|
|||
from dataclasses import dataclass
|
||||
|
||||
from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
|
||||
from aiohomekit.model.characteristics.const import ThreadNodeCapabilities
|
||||
from aiohomekit.model.services import Service, ServicesTypes
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
|
@ -27,6 +28,7 @@ from homeassistant.const import (
|
|||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
|
@ -40,6 +42,45 @@ class HomeKitSensorEntityDescription(SensorEntityDescription):
|
|||
"""Describes Homekit sensor."""
|
||||
|
||||
probe: Callable[[Characteristic], bool] | None = None
|
||||
format: Callable[[Characteristic], str] | None = None
|
||||
|
||||
|
||||
def thread_node_capability_to_str(char: Characteristic) -> str:
|
||||
"""
|
||||
Return the thread device type as a string.
|
||||
|
||||
The underlying value is a bitmask, but we want to turn that to
|
||||
a human readable string. Some devices will have multiple capabilities.
|
||||
For example, an NL55 is SLEEPY | MINIMAL. In that case we return the
|
||||
"best" capability.
|
||||
|
||||
https://openthread.io/guides/thread-primer/node-roles-and-types
|
||||
"""
|
||||
|
||||
val = ThreadNodeCapabilities(char.value)
|
||||
|
||||
if val & ThreadNodeCapabilities.BORDER_ROUTER_CAPABLE:
|
||||
# can act as a bridge between thread network and e.g. WiFi
|
||||
return "border_router_capable"
|
||||
|
||||
if val & ThreadNodeCapabilities.ROUTER_ELIGIBLE:
|
||||
# radio always on, can be a router
|
||||
return "router_eligible"
|
||||
|
||||
if val & ThreadNodeCapabilities.FULL:
|
||||
# radio always on, but can't be a router
|
||||
return "full"
|
||||
|
||||
if val & ThreadNodeCapabilities.MINIMAL:
|
||||
# transceiver always on, does not need to poll for messages from its parent
|
||||
return "minimal"
|
||||
|
||||
if val & ThreadNodeCapabilities.SLEEPY:
|
||||
# normally disabled, wakes on occasion to poll for messages from its parent
|
||||
return "sleepy"
|
||||
|
||||
# Device has no known thread capabilities
|
||||
return "none"
|
||||
|
||||
|
||||
SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
|
||||
|
@ -195,6 +236,13 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
|
|||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
),
|
||||
CharacteristicsTypes.THREAD_NODE_CAPABILITIES: HomeKitSensorEntityDescription(
|
||||
key=CharacteristicsTypes.THREAD_NODE_CAPABILITIES,
|
||||
name="Thread Capabilities",
|
||||
device_class="homekit_controller__thread_node_capabilities",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
format=thread_node_capability_to_str,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
@ -399,7 +447,10 @@ class SimpleSensor(CharacteristicEntity, SensorEntity):
|
|||
@property
|
||||
def native_value(self) -> str | int | float:
|
||||
"""Return the current sensor value."""
|
||||
return self._char.value
|
||||
val = self._char.value
|
||||
if self.entity_description.format:
|
||||
return self.entity_description.format(val)
|
||||
return val
|
||||
|
||||
|
||||
ENTITY_TYPES = {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"state": {
|
||||
"homekit_controller__thread_node_capabilities": {
|
||||
"border_router_capable": "Border Router Capable",
|
||||
"router_eligible": "Router Eligible End Device",
|
||||
"full": "Full End Device",
|
||||
"minimal": "Minimal End Device",
|
||||
"sleepy": "Sleepy End Device",
|
||||
"none": "None"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"state": {
|
||||
"homekit_controller__thread_node_capabilities": {
|
||||
"border_router_capable": "Border Router Capable",
|
||||
"full": "Full End Device",
|
||||
"minimal": "Minimal End Device",
|
||||
"none": "None",
|
||||
"router_eligible": "Router Eligible End Device",
|
||||
"sleepy": "Sleepy End Device"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,6 +50,13 @@ async def test_nanoleaf_nl55_setup(hass):
|
|||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state="unknown",
|
||||
),
|
||||
EntityTestInfo(
|
||||
entity_id="sensor.nanoleaf_strip_3b32_thread_capabilities",
|
||||
friendly_name="Nanoleaf Strip 3B32 Thread Capabilities",
|
||||
unique_id="homekit-AAAA011111111111-aid:1-sid:31-cid:115",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state="border_router_capable",
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
"""Basic checks for HomeKit sensor."""
|
||||
from aiohomekit.model.characteristics import CharacteristicsTypes
|
||||
from aiohomekit.model.characteristics.const import ThreadNodeCapabilities
|
||||
from aiohomekit.model.services import ServicesTypes
|
||||
from aiohomekit.protocol.statuscodes import HapStatusCode
|
||||
|
||||
from homeassistant.components.homekit_controller.sensor import (
|
||||
thread_node_capability_to_str,
|
||||
)
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||
|
||||
from tests.components.homekit_controller.common import Helper, setup_test_component
|
||||
|
@ -315,3 +319,19 @@ async def test_sensor_unavailable(hass, utcnow):
|
|||
# Energy sensor has non-responsive characteristics so should be unavailable
|
||||
state = await energy_helper.poll_and_get_state()
|
||||
assert state.state == "unavailable"
|
||||
|
||||
|
||||
def test_thread_node_caps_to_str():
|
||||
"""Test all values of this enum get a translatable string."""
|
||||
assert (
|
||||
thread_node_capability_to_str(ThreadNodeCapabilities.BORDER_ROUTER_CAPABLE)
|
||||
== "border_router_capable"
|
||||
)
|
||||
assert (
|
||||
thread_node_capability_to_str(ThreadNodeCapabilities.ROUTER_ELIGIBLE)
|
||||
== "router_eligible"
|
||||
)
|
||||
assert thread_node_capability_to_str(ThreadNodeCapabilities.FULL) == "full"
|
||||
assert thread_node_capability_to_str(ThreadNodeCapabilities.MINIMAL) == "minimal"
|
||||
assert thread_node_capability_to_str(ThreadNodeCapabilities.SLEEPY) == "sleepy"
|
||||
assert thread_node_capability_to_str(ThreadNodeCapabilities(128)) == "none"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue