Skip to content

Commit b45294d

Browse files
louispiresCopilotKane610
authored
unifi: Add wired client link speed sensor and related tests (home-assistant#155086)
Co-authored-by: Copilot <[email protected]> Co-authored-by: Robert Svensson <[email protected]>
1 parent 82d3190 commit b45294d

File tree

4 files changed

+125
-1
lines changed

4 files changed

+125
-1
lines changed

homeassistant/components/unifi/sensor.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ def async_client_uptime_value_fn(hub: UnifiHub, client: Client) -> datetime:
104104
return dt_util.utc_from_timestamp(float(client.uptime))
105105

106106

107+
@callback
108+
def async_wired_client_allowed_fn(hub: UnifiHub, obj_id: str) -> bool:
109+
"""Check if client is wired and allowed."""
110+
client = hub.api.clients[obj_id]
111+
if not client.is_wired or client.wired_rate_mbps <= 0:
112+
return False
113+
return True
114+
115+
107116
@callback
108117
def async_wlan_client_value_fn(hub: UnifiHub, wlan: Wlan) -> int:
109118
"""Calculate the amount of clients connected to a wlan."""
@@ -407,6 +416,23 @@ class UnifiSensorEntityDescription[HandlerT: APIHandler, ApiItemT: ApiItem](
407416
unique_id_fn=lambda hub, obj_id: f"tx-{obj_id}",
408417
value_fn=async_client_tx_value_fn,
409418
),
419+
UnifiSensorEntityDescription[Clients, Client](
420+
key="Wired client speed",
421+
translation_key="wired_client_link_speed",
422+
device_class=SensorDeviceClass.DATA_RATE,
423+
entity_category=EntityCategory.DIAGNOSTIC,
424+
state_class=SensorStateClass.MEASUREMENT,
425+
native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
426+
entity_registry_enabled_default=False,
427+
allowed_fn=async_wired_client_allowed_fn,
428+
api_handler_fn=lambda api: api.clients,
429+
device_info_fn=async_client_device_info_fn,
430+
is_connected_fn=async_client_is_connected_fn,
431+
name_fn=lambda _: "Link speed",
432+
object_fn=lambda api, obj_id: api.clients[obj_id],
433+
unique_id_fn=lambda hub, obj_id: f"wired_speed-{obj_id}",
434+
value_fn=lambda hub, client: client.wired_rate_mbps,
435+
),
410436
UnifiSensorEntityDescription[Ports, Port](
411437
key="PoE port power sensor",
412438
device_class=SensorDeviceClass.POWER,

homeassistant/components/unifi/strings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
"provisioning": "Provisioning",
5656
"upgrading": "Upgrading"
5757
}
58+
},
59+
"wired_client_link_speed": {
60+
"name": "Link speed"
5861
}
5962
}
6063
},

tests/components/unifi/snapshots/test_sensor.ambr

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,6 +1885,62 @@
18851885
'state': '0',
18861886
})
18871887
# ---
1888+
# name: test_entity_and_device_data[wlan_payload0-device_payload0-client_payload0-config_entry_options0][sensor.wired_client_link_speed-entry]
1889+
EntityRegistryEntrySnapshot({
1890+
'aliases': set({
1891+
}),
1892+
'area_id': None,
1893+
'capabilities': dict({
1894+
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
1895+
}),
1896+
'config_entry_id': <ANY>,
1897+
'config_subentry_id': <ANY>,
1898+
'device_class': None,
1899+
'device_id': <ANY>,
1900+
'disabled_by': None,
1901+
'domain': 'sensor',
1902+
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
1903+
'entity_id': 'sensor.wired_client_link_speed',
1904+
'has_entity_name': True,
1905+
'hidden_by': None,
1906+
'icon': None,
1907+
'id': <ANY>,
1908+
'labels': set({
1909+
}),
1910+
'name': None,
1911+
'options': dict({
1912+
'sensor': dict({
1913+
'suggested_display_precision': 2,
1914+
}),
1915+
}),
1916+
'original_device_class': <SensorDeviceClass.DATA_RATE: 'data_rate'>,
1917+
'original_icon': None,
1918+
'original_name': 'Link speed',
1919+
'platform': 'unifi',
1920+
'previous_unique_id': None,
1921+
'suggested_object_id': None,
1922+
'supported_features': 0,
1923+
'translation_key': 'wired_client_link_speed',
1924+
'unique_id': 'wired_speed-00:00:00:00:00:01',
1925+
'unit_of_measurement': <UnitOfDataRate.MEGABITS_PER_SECOND: 'Mbit/s'>,
1926+
})
1927+
# ---
1928+
# name: test_entity_and_device_data[wlan_payload0-device_payload0-client_payload0-config_entry_options0][sensor.wired_client_link_speed-state]
1929+
StateSnapshot({
1930+
'attributes': ReadOnlyDict({
1931+
'device_class': 'data_rate',
1932+
'friendly_name': 'Wired client Link speed',
1933+
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
1934+
'unit_of_measurement': <UnitOfDataRate.MEGABITS_PER_SECOND: 'Mbit/s'>,
1935+
}),
1936+
'context': <ANY>,
1937+
'entity_id': 'sensor.wired_client_link_speed',
1938+
'last_changed': <ANY>,
1939+
'last_reported': <ANY>,
1940+
'last_updated': <ANY>,
1941+
'state': '1000',
1942+
})
1943+
# ---
18881944
# name: test_entity_and_device_data[wlan_payload0-device_payload0-client_payload0-config_entry_options0][sensor.wired_client_rx-entry]
18891945
EntityRegistryEntrySnapshot({
18901946
'aliases': set({

tests/components/unifi/test_sensor.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"wired-rx_bytes-r": 1234000000,
5656
"wired-tx_bytes-r": 5678000000,
5757
"uptime": 1600094505,
58+
"wired_rate_mbps": 1000,
5859
}
5960
WIRELESS_CLIENT = {
6061
"is_wired": False,
@@ -542,6 +543,42 @@ async def test_bandwidth_sensors(
542543
assert hass.states.get("sensor.wired_client_tx")
543544

544545

546+
@pytest.mark.parametrize("client_payload", [[WIRED_CLIENT]])
547+
@pytest.mark.usefixtures("config_entry_setup")
548+
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
549+
async def test_wired_client_speed_sensor(
550+
hass: HomeAssistant,
551+
mock_websocket_message: WebsocketMessageMock,
552+
client_payload: list[dict[str, Any]],
553+
) -> None:
554+
"""Verify that wired client speed sensor is working as expected."""
555+
# Verify sensor is created and has correct state
556+
assert hass.states.get("sensor.wired_client_link_speed").state == "1000"
557+
558+
# Verify state update
559+
wired_client = deepcopy(client_payload[0])
560+
wired_client["wired_rate_mbps"] = 2500
561+
562+
mock_websocket_message(message=MessageKey.CLIENT, data=wired_client)
563+
await hass.async_block_till_done()
564+
565+
assert hass.states.get("sensor.wired_client_link_speed").state == "2500"
566+
567+
# Verify sensor is unavailable when client disconnects
568+
new_time = dt_util.utcnow()
569+
wired_client["last_seen"] = dt_util.as_timestamp(new_time)
570+
571+
mock_websocket_message(message=MessageKey.CLIENT, data=wired_client)
572+
await hass.async_block_till_done()
573+
574+
new_time += timedelta(seconds=(DEFAULT_DETECTION_TIME + 1))
575+
with freeze_time(new_time):
576+
async_fire_time_changed(hass, new_time)
577+
await hass.async_block_till_done()
578+
579+
assert hass.states.get("sensor.wired_client_link_speed").state == STATE_UNAVAILABLE
580+
581+
545582
@pytest.mark.parametrize(
546583
"config_entry_options",
547584
[{CONF_ALLOW_BANDWIDTH_SENSORS: True, CONF_ALLOW_UPTIME_SENSORS: True}],
@@ -555,9 +592,10 @@ async def test_remove_sensors(
555592
client_payload: list[dict[str, Any]],
556593
) -> None:
557594
"""Verify removing of clients work as expected."""
558-
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6
595+
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 7
559596
assert hass.states.get("sensor.wired_client_rx")
560597
assert hass.states.get("sensor.wired_client_tx")
598+
assert hass.states.get("sensor.wired_client_link_speed")
561599
assert hass.states.get("sensor.wired_client_uptime")
562600
assert hass.states.get("sensor.wireless_client_rx")
563601
assert hass.states.get("sensor.wireless_client_tx")
@@ -570,6 +608,7 @@ async def test_remove_sensors(
570608
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3
571609
assert hass.states.get("sensor.wired_client_rx") is None
572610
assert hass.states.get("sensor.wired_client_tx") is None
611+
assert hass.states.get("sensor.wired_client_link_speed") is None
573612
assert hass.states.get("sensor.wired_client_uptime") is None
574613
assert hass.states.get("sensor.wireless_client_rx")
575614
assert hass.states.get("sensor.wireless_client_tx")

0 commit comments

Comments
 (0)