Add UniFi device uptime and temperature sensors (#99307)
* Add UniFi device uptime and temperature sensors * Add native_unit_of_measurement to temperature Remove seconds and milliseconds from device uptime
This commit is contained in:
parent
f903cd6fc0
commit
cf47a6c515
2 changed files with 153 additions and 4 deletions
|
@ -27,6 +27,7 @@ from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
SensorEntityDescription,
|
SensorEntityDescription,
|
||||||
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EntityCategory, UnitOfInformation, UnitOfPower
|
from homeassistant.const import EntityCategory, UnitOfInformation, UnitOfPower
|
||||||
|
@ -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
|
@callback
|
||||||
def async_device_outlet_power_supported_fn(
|
def async_device_outlet_power_supported_fn(
|
||||||
controller: UniFiController, obj_id: str
|
controller: UniFiController, obj_id: str
|
||||||
|
@ -178,7 +189,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
|
||||||
value_fn=lambda _, obj: obj.poe_power if obj.poe_mode != "off" else "0",
|
value_fn=lambda _, obj: obj.poe_power if obj.poe_mode != "off" else "0",
|
||||||
),
|
),
|
||||||
UnifiSensorEntityDescription[Clients, Client](
|
UnifiSensorEntityDescription[Clients, Client](
|
||||||
key="Uptime sensor",
|
key="Client uptime",
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
has_entity_name=True,
|
has_entity_name=True,
|
||||||
|
@ -272,6 +283,43 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
|
||||||
unique_id_fn=lambda controller, obj_id: f"ac_power_conumption-{obj_id}",
|
unique_id_fn=lambda controller, obj_id: f"ac_power_conumption-{obj_id}",
|
||||||
value_fn=lambda controller, device: device.outlet_ac_power_consumption,
|
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,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -566,7 +566,7 @@ async def test_poe_port_switches(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the update_items function with some clients."""
|
"""Test the update_items function with some clients."""
|
||||||
await setup_unifi_integration(hass, aioclient_mock, devices_response=[DEVICE_1])
|
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 = er.async_get(hass)
|
||||||
ent_reg_entry = ent_reg.async_get("sensor.mock_name_port_1_poe_power")
|
ent_reg_entry = ent_reg.async_get("sensor.mock_name_port_1_poe_power")
|
||||||
|
@ -788,8 +788,8 @@ async def test_outlet_power_readings(
|
||||||
"""Test the outlet power reporting on PDU devices."""
|
"""Test the outlet power reporting on PDU devices."""
|
||||||
await setup_unifi_integration(hass, aioclient_mock, devices_response=[PDU_DEVICE_1])
|
await setup_unifi_integration(hass, aioclient_mock, devices_response=[PDU_DEVICE_1])
|
||||||
|
|
||||||
assert len(hass.states.async_all()) == 9
|
assert len(hass.states.async_all()) == 10
|
||||||
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3
|
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4
|
||||||
|
|
||||||
ent_reg = er.async_get(hass)
|
ent_reg = er.async_get(hass)
|
||||||
ent_reg_entry = ent_reg.async_get(f"sensor.{entity_id}")
|
ent_reg_entry = ent_reg.async_get(f"sensor.{entity_id}")
|
||||||
|
@ -809,3 +809,104 @@ async def test_outlet_power_readings(
|
||||||
|
|
||||||
sensor_data = hass.states.get(f"sensor.{entity_id}")
|
sensor_data = hass.states.get(f"sensor.{entity_id}")
|
||||||
assert sensor_data.state == expected_update_value
|
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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue