diff --git a/homeassistant/components/unifi/const.py b/homeassistant/components/unifi/const.py index c78313f66e2..2b16895a9a8 100644 --- a/homeassistant/components/unifi/const.py +++ b/homeassistant/components/unifi/const.py @@ -2,6 +2,8 @@ import logging +from aiounifi.models.device import DeviceState + from homeassistant.const import Platform LOGGER = logging.getLogger(__package__) @@ -46,3 +48,19 @@ ATTR_MANUFACTURER = "Ubiquiti Networks" BLOCK_SWITCH = "block" DPI_SWITCH = "dpi" OUTLET_SWITCH = "outlet" + +DEVICE_STATES = { + DeviceState.DISCONNECTED: "Disconnected", + DeviceState.CONNECTED: "Connected", + DeviceState.PENDING: "Pending", + DeviceState.FIRMWARE_MISMATCH: "Firmware Mismatch", + DeviceState.UPGRADING: "Upgrading", + DeviceState.PROVISIONING: "Provisioning", + DeviceState.HEARTBEAT_MISSED: "Heartbeat Missed", + DeviceState.ADOPTING: "Adopting", + DeviceState.DELETING: "Deleting", + DeviceState.INFORM_ERROR: "Inform Error", + DeviceState.ADOPTION_FALIED: "Adoption Failed", + DeviceState.ISOLATED: "Isolated", + DeviceState.UNKNOWN: "Unknown", +} diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 7d4717d3fff..4a43a65d5bb 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "loggers": ["aiounifi"], "quality_scale": "platinum", - "requirements": ["aiounifi==67"], + "requirements": ["aiounifi==68"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 4d5cf49b5c9..cc825ea51af 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -36,6 +36,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback import homeassistant.util.dt as dt_util +from .const import DEVICE_STATES from .controller import UniFiController from .entity import ( HandlerT, @@ -138,6 +139,12 @@ class UnifiSensorEntityDescriptionMixin(Generic[HandlerT, ApiItemT]): value_fn: Callable[[UniFiController, ApiItemT], datetime | float | str | None] +@callback +def async_device_state_value_fn(controller: UniFiController, device: Device) -> str: + """Retrieve the state of the device.""" + return DEVICE_STATES[device.state] + + @dataclass class UnifiSensorEntityDescription( SensorEntityDescription, @@ -343,6 +350,25 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( unique_id_fn=lambda controller, obj_id: f"device_temperature-{obj_id}", value_fn=lambda ctrlr, device: device.general_temperature, ), + UnifiSensorEntityDescription[Devices, Device]( + key="Device State", + device_class=SensorDeviceClass.ENUM, + 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: "State", + 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_state-{obj_id}", + value_fn=async_device_state_value_fn, + options=list(DEVICE_STATES.values()), + ), ) diff --git a/requirements_all.txt b/requirements_all.txt index ac326d767b9..e7b00cf42bf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -377,7 +377,7 @@ aiosyncthing==0.5.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==67 +aiounifi==68 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8fd36e8c6a6..119a926ed48 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -350,7 +350,7 @@ aiosyncthing==0.5.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==67 +aiounifi==68 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index 854d136f3dd..6eb6c05209c 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -3,6 +3,7 @@ from copy import deepcopy from datetime import datetime, timedelta from unittest.mock import patch +from aiounifi.models.device import DeviceState from aiounifi.models.message import MessageKey from freezegun.api import FrozenDateTimeFactory import pytest @@ -20,6 +21,7 @@ from homeassistant.components.unifi.const import ( CONF_ALLOW_UPTIME_SENSORS, CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, + DEVICE_STATES, ) from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE, EntityCategory @@ -584,7 +586,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)) == 1 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 ent_reg = er.async_get(hass) ent_reg_entry = ent_reg.async_get("sensor.mock_name_port_1_poe_power") @@ -807,8 +809,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()) == 10 - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 + assert len(hass.states.async_all()) == 11 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 5 ent_reg = er.async_get(hass) ent_reg_entry = ent_reg.async_get(f"sensor.{entity_id}") @@ -856,7 +858,7 @@ async def test_device_uptime( 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 len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 assert hass.states.get("sensor.device_uptime").state == "2021-01-01T01:00:00+00:00" ent_reg = er.async_get(hass) @@ -912,7 +914,7 @@ async def test_device_temperature( } await setup_unifi_integration(hass, aioclient_mock, devices_response=[device]) - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3 assert hass.states.get("sensor.device_temperature").state == "30" ent_reg = er.async_get(hass) @@ -925,3 +927,43 @@ async def test_device_temperature( device["general_temperature"] = 60 mock_unifi_websocket(message=MessageKey.DEVICE, data=device) assert hass.states.get("sensor.device_temperature").state == "60" + + +async def test_device_state( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, mock_unifi_websocket +) -> None: + """Verify that state 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)) == 3 + + ent_reg = er.async_get(hass) + assert ( + ent_reg.async_get("sensor.device_state").entity_category + is EntityCategory.DIAGNOSTIC + ) + + for i in list(map(int, DeviceState)): + device["state"] = i + mock_unifi_websocket(message=MessageKey.DEVICE, data=device) + assert hass.states.get("sensor.device_state").state == DEVICE_STATES[i] diff --git a/tests/components/unifi/test_update.py b/tests/components/unifi/test_update.py index 4f7a3dfe11d..a9fe3fdae7c 100644 --- a/tests/components/unifi/test_update.py +++ b/tests/components/unifi/test_update.py @@ -117,7 +117,7 @@ async def test_device_updates( # Simulate update finished - device_1["state"] = "0" + device_1["state"] = 0 device_1["version"] = "4.3.17.11279" device_1["upgradable"] = False del device_1["upgrade_to_firmware"]