Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UniFi device uptime and temperature sensors #99307

Merged
merged 2 commits into from
Sep 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
edenhaus marked this conversation as resolved.
Show resolved Hide resolved
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](
Kane610 marked this conversation as resolved.
Show resolved Hide resolved
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"