From 8417da370a408f0dbfb99e3560d4f1a66aeca1f0 Mon Sep 17 00:00:00 2001 From: tpjanssen <25168870+tpjanssen@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:29:42 +0100 Subject: [PATCH 1/3] Added uptime sensor Added uptime sensor --- custom_components/frigate/const.py | 1 + custom_components/frigate/icons.py | 1 + custom_components/frigate/sensor.py | 63 ++++++++++++++++++++++++++++- tests/__init__.py | 1 + tests/test_sensor.py | 14 +++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/custom_components/frigate/const.py b/custom_components/frigate/const.py index 9204d452..dd3a667c 100644 --- a/custom_components/frigate/const.py +++ b/custom_components/frigate/const.py @@ -25,6 +25,7 @@ # Unit of measurement FPS = "fps" MS = "ms" +S = "s" # Attributes ATTR_CLIENT = "client" diff --git a/custom_components/frigate/icons.py b/custom_components/frigate/icons.py index e8a12807..c62e1e1f 100644 --- a/custom_components/frigate/icons.py +++ b/custom_components/frigate/icons.py @@ -20,6 +20,7 @@ ICON_SERVER = "mdi:server" ICON_SPEEDOMETER = "mdi:speedometer" ICON_WAVEFORM = "mdi:waveform" +ICON_UPTIME = "mdi:clock-time-five" ICON_DEFAULT_ON = "mdi:home" diff --git a/custom_components/frigate/sensor.py b/custom_components/frigate/sensor.py index 8049c3c3..111ec13e 100644 --- a/custom_components/frigate/sensor.py +++ b/custom_components/frigate/sensor.py @@ -29,12 +29,13 @@ get_frigate_entity_unique_id, get_zones, ) -from .const import ATTR_CONFIG, ATTR_COORDINATOR, DOMAIN, FPS, MS, NAME +from .const import ATTR_CONFIG, ATTR_COORDINATOR, DOMAIN, FPS, MS, S, NAME from .icons import ( ICON_CORAL, ICON_SERVER, ICON_SPEEDOMETER, ICON_WAVEFORM, + ICON_UPTIME, get_icon_from_type, ) @@ -104,6 +105,7 @@ async def async_setup_entry( ] ) entities.append(FrigateStatusSensor(coordinator, entry)) + entities.append(FrigateUptimeSensor(coordinator, entry)) async_add_entities(entities) @@ -207,6 +209,65 @@ def icon(self) -> str: return ICON_SERVER +class FrigateUptimeSensor( + FrigateEntity, CoordinatorEntity[FrigateDataUpdateCoordinator] +): + """Frigate Uptime Sensor class.""" + + _attr_entity_category = EntityCategory.DIAGNOSTIC + _attr_name = "Uptime" + + def __init__( + self, coordinator: FrigateDataUpdateCoordinator, config_entry: ConfigEntry + ) -> None: + """Construct a FrigateUptimeSensor.""" + FrigateEntity.__init__(self, config_entry) + CoordinatorEntity.__init__(self, coordinator) + self._attr_entity_registry_enabled_default = False + + @property + def unique_id(self) -> str: + """Return a unique ID to use for this entity.""" + return get_frigate_entity_unique_id( + self._config_entry.entry_id, "uptime", "frigate" + ) + + @property + def device_info(self) -> DeviceInfo: + """Get device information.""" + return { + "identifiers": {get_frigate_device_identifier(self._config_entry)}, + "name": NAME, + "model": self._get_model(), + "configuration_url": self._config_entry.data.get(CONF_URL), + "manufacturer": NAME, + } + + @property + def state(self) -> int | None: + """Return the state of the sensor.""" + if self.coordinator.data: + data = ( + self.coordinator.data.get("service", {}) + .get("uptime", 0) + ) + try: + return int(data) + except (TypeError, ValueError): + pass + return None + + @property + def unit_of_measurement(self) -> str: + """Return the unit of measurement of the sensor.""" + return S + + @property + def icon(self) -> str: + """Return the icon of the sensor.""" + return ICON_UPTIME + + class DetectorSpeedSensor( FrigateEntity, CoordinatorEntity[FrigateDataUpdateCoordinator] ): diff --git a/tests/__init__.py b/tests/__init__.py index 0fa10b5c..1923a334 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -69,6 +69,7 @@ TEST_SENSOR_FRONT_DOOR_SKIPPED_FPS_ENTITY_ID = "sensor.front_door_skipped_fps" TEST_SENSOR_FRONT_DOOR_SOUND_LEVEL_ID = "sensor.front_door_sound_level" TEST_SENSOR_FRIGATE_STATUS_ENTITY_ID = "sensor.frigate_status" +TEST_SENSOR_FRIGATE_UPTIME_ENTITY_ID = "sensor.frigate_uptime" TEST_UPDATE_FRIGATE_CONTAINER_ENTITY_ID = "update.frigate_server" TEST_SERVER_VERSION = "0.14.1-f4f3cfa" diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 9771fd35..9a7f9ffa 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -44,6 +44,7 @@ TEST_SENSOR_CPU2_INTFERENCE_SPEED_ENTITY_ID, TEST_SENSOR_DETECTION_FPS_ENTITY_ID, TEST_SENSOR_FRIGATE_STATUS_ENTITY_ID, + TEST_SENSOR_FRIGATE_UPTIME_ENTITY_ID, TEST_SENSOR_FRONT_DOOR_ALL_ACTIVE_ENTITY_ID, TEST_SENSOR_FRONT_DOOR_ALL_ENTITY_ID, TEST_SENSOR_FRONT_DOOR_CAMERA_FPS_ENTITY_ID, @@ -375,6 +376,19 @@ async def test_status_sensor_error(hass: HomeAssistant) -> None: assert entity_state.attributes["icon"] == ICON_SERVER +async def test_uptime_sensor(hass: HomeAssistant) -> None: + """Test FrigateUptimeSensor expected state.""" + + client = create_mock_frigate_client() + await setup_mock_frigate_config_entry(hass, client=client) + await enable_and_load_entity(hass, client, TEST_SENSOR_FRIGATE_UPTIME_ENTITY_ID) + + entity_state = hass.states.get(TEST_SENSOR_FRIGATE_UPTIME_ENTITY_ID) + assert entity_state + assert entity_state.state == "101113" + assert entity_state.attributes["icon"] == ICON_UPTIME + + async def test_per_entry_device_info(hass: HomeAssistant) -> None: """Verify switch device information.""" config_entry = await setup_mock_frigate_config_entry(hass) From cbf10eb780650de33b74128a58ab4956d21aa53e Mon Sep 17 00:00:00 2001 From: tpjanssen <25168870+tpjanssen@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:37:33 +0100 Subject: [PATCH 2/3] Code formatting + fix test --- custom_components/frigate/sensor.py | 9 +++------ tests/test_sensor.py | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/custom_components/frigate/sensor.py b/custom_components/frigate/sensor.py index 111ec13e..85f8725a 100644 --- a/custom_components/frigate/sensor.py +++ b/custom_components/frigate/sensor.py @@ -29,13 +29,13 @@ get_frigate_entity_unique_id, get_zones, ) -from .const import ATTR_CONFIG, ATTR_COORDINATOR, DOMAIN, FPS, MS, S, NAME +from .const import ATTR_CONFIG, ATTR_COORDINATOR, DOMAIN, FPS, MS, NAME, S from .icons import ( ICON_CORAL, ICON_SERVER, ICON_SPEEDOMETER, - ICON_WAVEFORM, ICON_UPTIME, + ICON_WAVEFORM, get_icon_from_type, ) @@ -247,10 +247,7 @@ def device_info(self) -> DeviceInfo: def state(self) -> int | None: """Return the state of the sensor.""" if self.coordinator.data: - data = ( - self.coordinator.data.get("service", {}) - .get("uptime", 0) - ) + data = self.coordinator.data.get("service", {}).get("uptime", 0) try: return int(data) except (TypeError, ValueError): diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 9a7f9ffa..eadbc3ec 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -29,6 +29,7 @@ ICON_PERSON, ICON_SERVER, ICON_SPEEDOMETER, + ICON_UPTIME, ICON_WAVEFORM, ) from homeassistant.const import PERCENTAGE, UnitOfSoundPressure, UnitOfTemperature From 862eadab87b8a939969f16dd869e54190e574c70 Mon Sep 17 00:00:00 2001 From: tpjanssen <25168870+tpjanssen@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:06:54 +0100 Subject: [PATCH 3/3] Fix tests --- tests/test_sensor.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_sensor.py b/tests/test_sensor.py index eadbc3ec..fbcae876 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -389,6 +389,25 @@ async def test_uptime_sensor(hass: HomeAssistant) -> None: assert entity_state.state == "101113" assert entity_state.attributes["icon"] == ICON_UPTIME + stats: dict[str, Any] = copy.deepcopy(TEST_STATS) + client.async_get_stats = AsyncMock(return_value=stats) + + stats["service"]["uptime"] = None + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + entity_state = hass.states.get(TEST_SENSOR_FRIGATE_UPTIME_ENTITY_ID) + assert entity_state + assert entity_state.state == "unknown" + + stats["service"]["uptime"] = "NOT_A_NUMBER" + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + entity_state = hass.states.get(TEST_SENSOR_FRIGATE_UPTIME_ENTITY_ID) + assert entity_state + assert entity_state.state == "unknown" + async def test_per_entry_device_info(hass: HomeAssistant) -> None: """Verify switch device information."""