diff --git a/homeassistant/components/fully_kiosk/__init__.py b/homeassistant/components/fully_kiosk/__init__.py index e417d7c0bcb..dd1cc70c9f4 100644 --- a/homeassistant/components/fully_kiosk/__init__.py +++ b/homeassistant/components/fully_kiosk/__init__.py @@ -26,6 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + coordinator.async_update_listeners() await async_setup_services(hass) diff --git a/homeassistant/components/fully_kiosk/sensor.py b/homeassistant/components/fully_kiosk/sensor.py index 60009eb6ae4..eed14f24674 100644 --- a/homeassistant/components/fully_kiosk/sensor.py +++ b/homeassistant/components/fully_kiosk/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass +from typing import Any from homeassistant.components.sensor import ( SensorDeviceClass, @@ -12,7 +13,7 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfInformation -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType @@ -26,11 +27,25 @@ def round_storage(value: int) -> float: return round(value * 0.000001, 1) +def truncate_url(value: StateType) -> tuple[StateType, dict[str, Any]]: + """Truncate URL if longer than 256.""" + url = str(value) + truncated = len(url) > 256 + extra_state_attributes = { + "full_url": url, + "truncated": truncated, + } + if truncated: + return (url[0:255], extra_state_attributes) + return (url, extra_state_attributes) + + @dataclass class FullySensorEntityDescription(SensorEntityDescription): """Fully Kiosk Browser sensor description.""" - state_fn: Callable[[int], float] | None = None + round_state_value: bool = False + state_fn: Callable[[StateType], tuple[StateType, dict[str, Any]]] | None = None SENSORS: tuple[FullySensorEntityDescription, ...] = ( @@ -42,6 +57,12 @@ SENSORS: tuple[FullySensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, ), + FullySensorEntityDescription( + key="currentPage", + name="Current page", + entity_category=EntityCategory.DIAGNOSTIC, + state_fn=truncate_url, + ), FullySensorEntityDescription( key="screenOrientation", name="Screen orientation", @@ -52,11 +73,6 @@ SENSORS: tuple[FullySensorEntityDescription, ...] = ( name="Foreground app", entity_category=EntityCategory.DIAGNOSTIC, ), - FullySensorEntityDescription( - key="currentPage", - name="Current page", - entity_category=EntityCategory.DIAGNOSTIC, - ), FullySensorEntityDescription( key="internalStorageFreeSpace", name="Internal storage free space", @@ -64,7 +80,7 @@ SENSORS: tuple[FullySensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, state_class=SensorStateClass.MEASUREMENT, - state_fn=round_storage, + round_state_value=True, ), FullySensorEntityDescription( key="internalStorageTotalSpace", @@ -73,7 +89,7 @@ SENSORS: tuple[FullySensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, state_class=SensorStateClass.MEASUREMENT, - state_fn=round_storage, + round_state_value=True, ), FullySensorEntityDescription( key="ramFreeMemory", @@ -82,7 +98,7 @@ SENSORS: tuple[FullySensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, state_class=SensorStateClass.MEASUREMENT, - state_fn=round_storage, + round_state_value=True, ), FullySensorEntityDescription( key="ramTotalMemory", @@ -91,7 +107,7 @@ SENSORS: tuple[FullySensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, state_class=SensorStateClass.MEASUREMENT, - state_fn=round_storage, + round_state_value=True, ), ) @@ -129,13 +145,19 @@ class FullySensor(FullyKioskEntity, SensorEntity): super().__init__(coordinator) - @property - def native_value(self) -> StateType: - """Return the state of the sensor.""" - if (value := self.coordinator.data.get(self.entity_description.key)) is None: - return None + @callback + def _handle_coordinator_update(self) -> None: + extra_state_attributes: dict[str, Any] = {} + value = self.coordinator.data.get(self.entity_description.key) - if self.entity_description.state_fn is not None: - return self.entity_description.state_fn(value) + if value is not None: + if self.entity_description.state_fn is not None: + value, extra_state_attributes = self.entity_description.state_fn(value) - return value # type: ignore[no-any-return] + if self.entity_description.round_state_value: + value = round_storage(value) + + self._attr_native_value = value + self._attr_extra_state_attributes = extra_state_attributes + + self.async_write_ha_state() diff --git a/tests/components/fully_kiosk/test_sensor.py b/tests/components/fully_kiosk/test_sensor.py index c7ff09fe32c..6454784a251 100644 --- a/tests/components/fully_kiosk/test_sensor.py +++ b/tests/components/fully_kiosk/test_sensor.py @@ -66,6 +66,8 @@ async def test_sensors_sensors( assert state.state == "https://homeassistant.local" assert state.attributes.get(ATTR_DEVICE_CLASS) is None assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Amazon Fire Current page" + assert state.attributes.get("full_url") == "https://homeassistant.local" + assert not state.attributes.get("truncated") entry = entity_registry.async_get("sensor.amazon_fire_current_page") assert entry @@ -154,3 +156,31 @@ async def test_sensors_sensors( state = hass.states.get("sensor.amazon_fire_internal_storage_free_space") assert state assert state.state == STATE_UNAVAILABLE + + +async def test_url_sensor_truncating( + hass: HomeAssistant, + mock_fully_kiosk: MagicMock, + init_integration: MockConfigEntry, +) -> None: + """Test that long URLs get truncated.""" + state = hass.states.get("sensor.amazon_fire_current_page") + assert state + assert state.state == "https://homeassistant.local" + assert state.attributes.get("full_url") == "https://homeassistant.local" + assert not state.attributes.get("truncated") + + long_url = "https://01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + assert len(long_url) > 256 + + mock_fully_kiosk.getDeviceInfo.return_value = { + "currentPage": long_url, + } + async_fire_time_changed(hass, dt.utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get("sensor.amazon_fire_current_page") + assert state + assert state.state == long_url[0:255] + assert state.attributes.get("full_url") == long_url + assert state.attributes.get("truncated")