Add a Thread network status sensor to homekit_controller (#76209)

This commit is contained in:
Jc2k 2022-08-04 11:55:29 +01:00 committed by GitHub
parent d5695a2d86
commit aa3097a3be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 2 deletions

View File

@ -5,7 +5,7 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
from aiohomekit.model.characteristics.const import ThreadNodeCapabilities from aiohomekit.model.characteristics.const import ThreadNodeCapabilities, ThreadStatus
from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
@ -83,6 +83,54 @@ def thread_node_capability_to_str(char: Characteristic) -> str:
return "none" return "none"
def thread_status_to_str(char: Characteristic) -> str:
"""
Return the thread status as a string.
The underlying value is a bitmask, but we want to turn that to
a human readable string. So we check the flags in order. E.g. BORDER_ROUTER implies
ROUTER, so its more important to show that value.
"""
val = ThreadStatus(char.value)
if val & ThreadStatus.BORDER_ROUTER:
# Device has joined the Thread network and is participating
# in routing between mesh nodes.
# It's also the border router - bridging the thread network
# to WiFI/Ethernet/etc
return "border_router"
if val & ThreadStatus.LEADER:
# Device has joined the Thread network and is participating
# in routing between mesh nodes.
# It's also the leader. There's only one leader and it manages
# which nodes are routers.
return "leader"
if val & ThreadStatus.ROUTER:
# Device has joined the Thread network and is participating
# in routing between mesh nodes.
return "router"
if val & ThreadStatus.CHILD:
# Device has joined the Thread network as a child
# It's not participating in routing between mesh nodes
return "child"
if val & ThreadStatus.JOINING:
# Device is currently joining its Thread network
return "joining"
if val & ThreadStatus.DETACHED:
# Device is currently unable to reach its Thread network
return "detached"
# Must be ThreadStatus.DISABLED
# Device is not currently connected to Thread and will not try to.
return "disabled"
SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT: HomeKitSensorEntityDescription( CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT, key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT,
@ -243,6 +291,13 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
format=thread_node_capability_to_str, format=thread_node_capability_to_str,
), ),
CharacteristicsTypes.THREAD_STATUS: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.THREAD_STATUS,
name="Thread Status",
device_class="homekit_controller__thread_status",
entity_category=EntityCategory.DIAGNOSTIC,
format=thread_status_to_str,
),
} }

View File

@ -7,6 +7,15 @@
"minimal": "Minimal End Device", "minimal": "Minimal End Device",
"sleepy": "Sleepy End Device", "sleepy": "Sleepy End Device",
"none": "None" "none": "None"
},
"homekit_controller__thread_status": {
"border_router": "Border Router",
"leader": "Leader",
"router": "Router",
"child": "Child",
"joining": "Joining",
"detached": "Detached",
"disabled": "Disabled"
} }
} }
} }

View File

@ -7,6 +7,15 @@
"none": "None", "none": "None",
"router_eligible": "Router Eligible End Device", "router_eligible": "Router Eligible End Device",
"sleepy": "Sleepy End Device" "sleepy": "Sleepy End Device"
},
"homekit_controller__thread_status": {
"border_router": "Border Router",
"child": "Child",
"detached": "Detached",
"disabled": "Disabled",
"joining": "Joining",
"leader": "Leader",
"router": "Router"
} }
} }
} }

View File

@ -57,6 +57,13 @@ async def test_nanoleaf_nl55_setup(hass):
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
state="border_router_capable", state="border_router_capable",
), ),
EntityTestInfo(
entity_id="sensor.nanoleaf_strip_3b32_thread_status",
friendly_name="Nanoleaf Strip 3B32 Thread Status",
unique_id="homekit-AAAA011111111111-aid:1-sid:31-cid:117",
entity_category=EntityCategory.DIAGNOSTIC,
state="border_router",
),
], ],
), ),
) )

View File

@ -1,11 +1,12 @@
"""Basic checks for HomeKit sensor.""" """Basic checks for HomeKit sensor."""
from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.characteristics.const import ThreadNodeCapabilities from aiohomekit.model.characteristics.const import ThreadNodeCapabilities, ThreadStatus
from aiohomekit.model.services import ServicesTypes from aiohomekit.model.services import ServicesTypes
from aiohomekit.protocol.statuscodes import HapStatusCode from aiohomekit.protocol.statuscodes import HapStatusCode
from homeassistant.components.homekit_controller.sensor import ( from homeassistant.components.homekit_controller.sensor import (
thread_node_capability_to_str, thread_node_capability_to_str,
thread_status_to_str,
) )
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
@ -337,3 +338,14 @@ def test_thread_node_caps_to_str():
assert thread_node_capability_to_str(ThreadNodeCapabilities.MINIMAL) == "minimal" 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.SLEEPY) == "sleepy"
assert thread_node_capability_to_str(ThreadNodeCapabilities(128)) == "none" assert thread_node_capability_to_str(ThreadNodeCapabilities(128)) == "none"
def test_thread_status_to_str():
"""Test all values of this enum get a translatable string."""
assert thread_status_to_str(ThreadStatus.BORDER_ROUTER) == "border_router"
assert thread_status_to_str(ThreadStatus.LEADER) == "leader"
assert thread_status_to_str(ThreadStatus.ROUTER) == "router"
assert thread_status_to_str(ThreadStatus.CHILD) == "child"
assert thread_status_to_str(ThreadStatus.JOINING) == "joining"
assert thread_status_to_str(ThreadStatus.DETACHED) == "detached"
assert thread_status_to_str(ThreadStatus.DISABLED) == "disabled"