Skip to content

Commit

Permalink
Add UniFi device uptime and temperature sensors (#99307)
Browse files Browse the repository at this point in the history
* Add UniFi device uptime and temperature sensors

* Add native_unit_of_measurement to temperature
Remove seconds and milliseconds from device uptime
  • Loading branch information
Kane610 authored Sep 9, 2023
1 parent f903cd6 commit cf47a6c
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 4 deletions.
50 changes: 49 additions & 1 deletion homeassistant/components/unifi/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
UnitOfTemperature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory, UnitOfInformation, UnitOfPower
Expand Down Expand Up @@ -88,6 +89,16 @@ def async_wlan_client_value_fn(controller: UniFiController, wlan: Wlan) -> int:
)


@callback
def async_device_uptime_value_fn(
controller: UniFiController, device: Device
) -> datetime:
"""Calculate the uptime of the device."""
return (dt_util.now() - timedelta(seconds=device.uptime)).replace(
second=0, microsecond=0
)


@callback
def async_device_outlet_power_supported_fn(
controller: UniFiController, obj_id: str
Expand Down Expand Up @@ -178,7 +189,7 @@ class UnifiSensorEntityDescription(
value_fn=lambda _, obj: obj.poe_power if obj.poe_mode != "off" else "0",
),
UnifiSensorEntityDescription[Clients, Client](
key="Uptime sensor",
key="Client uptime",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
has_entity_name=True,
Expand Down Expand Up @@ -272,6 +283,43 @@ class UnifiSensorEntityDescription(
unique_id_fn=lambda controller, obj_id: f"ac_power_conumption-{obj_id}",
value_fn=lambda controller, device: device.outlet_ac_power_consumption,
),
UnifiSensorEntityDescription[Devices, Device](
key="Device uptime",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
has_entity_name=True,
allowed_fn=lambda controller, obj_id: True,
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda device: "Uptime",
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda controller, obj_id: True,
unique_id_fn=lambda controller, obj_id: f"device_uptime-{obj_id}",
value_fn=async_device_uptime_value_fn,
),
UnifiSensorEntityDescription[Devices, Device](
key="Device temperature",
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
has_entity_name=True,
allowed_fn=lambda controller, obj_id: True,
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda device: "Temperature",
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].has_temperature,
unique_id_fn=lambda controller, obj_id: f"device_temperature-{obj_id}",
value_fn=lambda ctrlr, device: device.general_temperature,
),
)


Expand Down
107 changes: 104 additions & 3 deletions tests/components/unifi/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ async def test_poe_port_switches(
) -> None:
"""Test the update_items function with some clients."""
await setup_unifi_integration(hass, aioclient_mock, devices_response=[DEVICE_1])
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 1

ent_reg = er.async_get(hass)
ent_reg_entry = ent_reg.async_get("sensor.mock_name_port_1_poe_power")
Expand Down Expand Up @@ -788,8 +788,8 @@ async def test_outlet_power_readings(
"""Test the outlet power reporting on PDU devices."""
await setup_unifi_integration(hass, aioclient_mock, devices_response=[PDU_DEVICE_1])

assert len(hass.states.async_all()) == 9
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3
assert len(hass.states.async_all()) == 10
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4

ent_reg = er.async_get(hass)
ent_reg_entry = ent_reg.async_get(f"sensor.{entity_id}")
Expand All @@ -809,3 +809,104 @@ async def test_outlet_power_readings(

sensor_data = hass.states.get(f"sensor.{entity_id}")
assert sensor_data.state == expected_update_value


async def test_device_uptime(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, mock_unifi_websocket
) -> None:
"""Verify that uptime sensors are working as expected."""
device = {
"board_rev": 3,
"device_id": "mock-id",
"has_fan": True,
"fan_level": 0,
"ip": "10.0.1.1",
"last_seen": 1562600145,
"mac": "00:00:00:00:01:01",
"model": "US16P150",
"name": "Device",
"next_interval": 20,
"overheating": True,
"state": 1,
"type": "usw",
"upgradable": True,
"uptime": 60,
"version": "4.0.42.10433",
}

now = datetime(2021, 1, 1, 1, 1, 0, tzinfo=dt_util.UTC)
with patch("homeassistant.util.dt.now", return_value=now):
await setup_unifi_integration(hass, aioclient_mock, devices_response=[device])

assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 1
assert hass.states.get("sensor.device_uptime").state == "2021-01-01T01:00:00+00:00"

ent_reg = er.async_get(hass)
assert (
ent_reg.async_get("sensor.device_uptime").entity_category
is EntityCategory.DIAGNOSTIC
)

# Verify normal new event doesn't change uptime
# 4 seconds has passed

device["uptime"] = 64
now = datetime(2021, 1, 1, 1, 1, 4, tzinfo=dt_util.UTC)
with patch("homeassistant.util.dt.now", return_value=now):
mock_unifi_websocket(message=MessageKey.DEVICE, data=device)
await hass.async_block_till_done()

assert hass.states.get("sensor.device_uptime").state == "2021-01-01T01:00:00+00:00"

# Verify new event change uptime
# 1 month has passed

device["uptime"] = 60
now = datetime(2021, 2, 1, 1, 1, 0, tzinfo=dt_util.UTC)
with patch("homeassistant.util.dt.now", return_value=now):
mock_unifi_websocket(message=MessageKey.DEVICE, data=device)
await hass.async_block_till_done()

assert hass.states.get("sensor.device_uptime").state == "2021-02-01T01:00:00+00:00"


async def test_device_temperature(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, mock_unifi_websocket
) -> None:
"""Verify that temperature sensors are working as expected."""
device = {
"board_rev": 3,
"device_id": "mock-id",
"general_temperature": 30,
"has_fan": True,
"has_temperature": True,
"fan_level": 0,
"ip": "10.0.1.1",
"last_seen": 1562600145,
"mac": "00:00:00:00:01:01",
"model": "US16P150",
"name": "Device",
"next_interval": 20,
"overheating": True,
"state": 1,
"type": "usw",
"upgradable": True,
"uptime": 60,
"version": "4.0.42.10433",
}

await setup_unifi_integration(hass, aioclient_mock, devices_response=[device])
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2
assert hass.states.get("sensor.device_temperature").state == "30"

ent_reg = er.async_get(hass)
assert (
ent_reg.async_get("sensor.device_temperature").entity_category
is EntityCategory.DIAGNOSTIC
)

# Verify new event change temperature
device["general_temperature"] = 60
mock_unifi_websocket(message=MessageKey.DEVICE, data=device)
await hass.async_block_till_done()
assert hass.states.get("sensor.device_temperature").state == "60"

0 comments on commit cf47a6c

Please sign in to comment.