Add UniFi power stats for PDU overall AC outlet metrics (#98217)
parent
ae8f9dcb77
commit
87753bdb82
|
@ -8,7 +8,7 @@
|
|||
"iot_class": "local_push",
|
||||
"loggers": ["aiounifi"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiounifi==52"],
|
||||
"requirements": ["aiounifi==53"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
|
|
|
@ -12,11 +12,13 @@ from typing import Generic
|
|||
|
||||
from aiounifi.interfaces.api_handlers import ItemEvent
|
||||
from aiounifi.interfaces.clients import Clients
|
||||
from aiounifi.interfaces.devices import Devices
|
||||
from aiounifi.interfaces.outlets import Outlets
|
||||
from aiounifi.interfaces.ports import Ports
|
||||
from aiounifi.interfaces.wlans import Wlans
|
||||
from aiounifi.models.api import ApiItemT
|
||||
from aiounifi.models.client import Client
|
||||
from aiounifi.models.device import Device
|
||||
from aiounifi.models.outlet import Outlet
|
||||
from aiounifi.models.port import Port
|
||||
from aiounifi.models.wlan import Wlan
|
||||
|
@ -96,6 +98,12 @@ def async_device_outlet_power_supported_fn(
|
|||
return controller.api.outlets[obj_id].caps == 3
|
||||
|
||||
|
||||
@callback
|
||||
def async_device_outlet_supported_fn(controller: UniFiController, obj_id: str) -> bool:
|
||||
"""Determine if a device supports reading overall power metrics."""
|
||||
return controller.api.devices[obj_id].outlet_ac_power_budget is not None
|
||||
|
||||
|
||||
@dataclass
|
||||
class UnifiSensorEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
|
||||
"""Validate and load entities from different UniFi handlers."""
|
||||
|
@ -224,6 +232,46 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
|
|||
unique_id_fn=lambda controller, obj_id: f"outlet_power-{obj_id}",
|
||||
value_fn=lambda _, obj: obj.power if obj.relay_state else "0",
|
||||
),
|
||||
UnifiSensorEntityDescription[Devices, Device](
|
||||
key="SmartPower AC power budget",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_display_precision=1,
|
||||
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: "AC Power Budget",
|
||||
object_fn=lambda api, obj_id: api.devices[obj_id],
|
||||
should_poll=False,
|
||||
supported_fn=async_device_outlet_supported_fn,
|
||||
unique_id_fn=lambda controller, obj_id: f"ac_power_budget-{obj_id}",
|
||||
value_fn=lambda controller, device: device.outlet_ac_power_budget,
|
||||
),
|
||||
UnifiSensorEntityDescription[Devices, Device](
|
||||
key="SmartPower AC power consumption",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_display_precision=1,
|
||||
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: "AC Power Consumption",
|
||||
object_fn=lambda api, obj_id: api.devices[obj_id],
|
||||
should_poll=False,
|
||||
supported_fn=async_device_outlet_supported_fn,
|
||||
unique_id_fn=lambda controller, obj_id: f"ac_power_conumption-{obj_id}",
|
||||
value_fn=lambda controller, device: device.outlet_ac_power_consumption,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -360,7 +360,7 @@ aiosyncthing==0.5.1
|
|||
aiotractive==0.5.5
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==52
|
||||
aiounifi==53
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
|
|
|
@ -335,7 +335,7 @@ aiosyncthing==0.5.1
|
|||
aiotractive==0.5.5
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==52
|
||||
aiounifi==53
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
|
|
|
@ -278,6 +278,27 @@ PDU_DEVICE_1 = {
|
|||
"x_has_ssh_hostkey": True,
|
||||
}
|
||||
|
||||
PDU_OUTLETS_UPDATE_DATA = [
|
||||
{
|
||||
"index": 1,
|
||||
"relay_state": True,
|
||||
"cycle_enabled": False,
|
||||
"name": "USB Outlet 1",
|
||||
"outlet_caps": 1,
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"relay_state": True,
|
||||
"cycle_enabled": False,
|
||||
"name": "Outlet 2",
|
||||
"outlet_caps": 3,
|
||||
"outlet_voltage": "119.644",
|
||||
"outlet_current": "0.935",
|
||||
"outlet_power": "123.45",
|
||||
"outlet_power_factor": "0.659",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
async def test_no_clients(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
|
@ -719,31 +740,69 @@ async def test_wlan_client_sensors(
|
|||
assert hass.states.get("sensor.ssid_1").state == "0"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"entity_id",
|
||||
"expected_unique_id",
|
||||
"expected_value",
|
||||
"changed_data",
|
||||
"expected_update_value",
|
||||
),
|
||||
[
|
||||
(
|
||||
"dummy_usp_pdu_pro_outlet_2_outlet_power",
|
||||
"outlet_power-01:02:03:04:05:ff_2",
|
||||
"73.827",
|
||||
{"outlet_table": PDU_OUTLETS_UPDATE_DATA},
|
||||
"123.45",
|
||||
),
|
||||
(
|
||||
"dummy_usp_pdu_pro_ac_power_budget",
|
||||
"ac_power_budget-01:02:03:04:05:ff",
|
||||
"1875.000",
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
"dummy_usp_pdu_pro_ac_power_consumption",
|
||||
"ac_power_conumption-01:02:03:04:05:ff",
|
||||
"201.683",
|
||||
{"outlet_ac_power_consumption": "456.78"},
|
||||
"456.78",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_outlet_power_readings(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, mock_unifi_websocket
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
mock_unifi_websocket,
|
||||
entity_id: str,
|
||||
expected_unique_id: str,
|
||||
expected_value: any,
|
||||
changed_data: dict | None,
|
||||
expected_update_value: any,
|
||||
) -> None:
|
||||
"""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()) == 5
|
||||
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 1
|
||||
assert len(hass.states.async_all()) == 7
|
||||
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3
|
||||
|
||||
ent_reg = er.async_get(hass)
|
||||
ent_reg_entry = ent_reg.async_get("sensor.dummy_usp_pdu_pro_outlet_2_outlet_power")
|
||||
assert ent_reg_entry.unique_id == "outlet_power-01:02:03:04:05:ff_2"
|
||||
ent_reg_entry = ent_reg.async_get(f"sensor.{entity_id}")
|
||||
assert ent_reg_entry.unique_id == expected_unique_id
|
||||
assert ent_reg_entry.entity_category is EntityCategory.DIAGNOSTIC
|
||||
|
||||
outlet_2 = hass.states.get("sensor.dummy_usp_pdu_pro_outlet_2_outlet_power")
|
||||
assert outlet_2.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER
|
||||
assert outlet_2.state == "73.827"
|
||||
sensor_data = hass.states.get(f"sensor.{entity_id}")
|
||||
assert sensor_data.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER
|
||||
assert sensor_data.state == expected_value
|
||||
|
||||
# Verify state update
|
||||
pdu_device_state_update = deepcopy(PDU_DEVICE_1)
|
||||
if changed_data is not None:
|
||||
updated_device_data = deepcopy(PDU_DEVICE_1)
|
||||
updated_device_data.update(changed_data)
|
||||
|
||||
pdu_device_state_update["outlet_table"][1]["outlet_power"] = "123.45"
|
||||
mock_unifi_websocket(message=MessageKey.DEVICE, data=updated_device_data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_unifi_websocket(message=MessageKey.DEVICE, data=pdu_device_state_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
outlet_2 = hass.states.get("sensor.dummy_usp_pdu_pro_outlet_2_outlet_power")
|
||||
assert outlet_2.state == "123.45"
|
||||
sensor_data = hass.states.get(f"sensor.{entity_id}")
|
||||
assert sensor_data.state == expected_update_value
|
||||
|
|
Loading…
Reference in New Issue