From b6c8b787e82e7790f2c4befcbaf39895a60184cf Mon Sep 17 00:00:00 2001 From: Manu <4445816+tr4nt0r@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:53:42 +0100 Subject: [PATCH] Add device storage sensor entities to Xbox (#155657) --- homeassistant/components/xbox/__init__.py | 25 +- .../components/xbox/binary_sensor.py | 2 +- homeassistant/components/xbox/coordinator.py | 50 ++- homeassistant/components/xbox/image.py | 2 +- homeassistant/components/xbox/media_player.py | 2 +- homeassistant/components/xbox/media_source.py | 18 +- homeassistant/components/xbox/remote.py | 2 +- homeassistant/components/xbox/sensor.py | 127 ++++++- homeassistant/components/xbox/strings.json | 6 + .../xbox/snapshots/test_sensor.ambr | 354 ++++++++++++++++++ 10 files changed, 561 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index 411c0a35a07..73d3e435432 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -9,7 +9,12 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from .const import DOMAIN -from .coordinator import XboxConfigEntry, XboxUpdateCoordinator +from .coordinator import ( + XboxConfigEntry, + XboxConsolesCoordinator, + XboxCoordinators, + XboxUpdateCoordinator, +) _LOGGER = logging.getLogger(__name__) @@ -30,7 +35,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: XboxConfigEntry) -> bool coordinator = XboxUpdateCoordinator(hass, entry) await coordinator.async_config_entry_first_refresh() - entry.runtime_data = coordinator + consoles = XboxConsolesCoordinator(hass, entry, coordinator) + + entry.runtime_data = XboxCoordinators(coordinator, consoles) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -53,16 +60,14 @@ async def async_migrate_unique_id(hass: HomeAssistant, entry: XboxConfigEntry) - if entry.version == 1 and entry.minor_version < 2: # Migrate unique_id from `xbox` to account xuid and # change generic entry name to user's gamertag + coordinator = entry.runtime_data.status + xuid = coordinator.client.xuid + gamertag = coordinator.data.presence[xuid].gamertag + return hass.config_entries.async_update_entry( entry, - unique_id=entry.runtime_data.client.xuid, - title=( - entry.runtime_data.data.presence[ - entry.runtime_data.client.xuid - ].gamertag - if entry.title == "Home Assistant Cloud" - else entry.title - ), + unique_id=xuid, + title=(gamertag if entry.title == "Home Assistant Cloud" else entry.title), minor_version=2, ) diff --git a/homeassistant/components/xbox/binary_sensor.py b/homeassistant/components/xbox/binary_sensor.py index 449c8551b95..2a1a18f6f1d 100644 --- a/homeassistant/components/xbox/binary_sensor.py +++ b/homeassistant/components/xbox/binary_sensor.py @@ -111,7 +111,7 @@ async def async_setup_entry( ) -> None: """Set up Xbox Live friends.""" xuids_added: set[str] = set() - coordinator = entry.runtime_data + coordinator = entry.runtime_data.status @callback def add_entities() -> None: diff --git a/homeassistant/components/xbox/coordinator.py b/homeassistant/components/xbox/coordinator.py index dca48b78462..05cf25af9b1 100644 --- a/homeassistant/components/xbox/coordinator.py +++ b/homeassistant/components/xbox/coordinator.py @@ -35,7 +35,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -type XboxConfigEntry = ConfigEntry[XboxUpdateCoordinator] +type XboxConfigEntry = ConfigEntry[XboxCoordinators] @dataclass @@ -55,6 +55,14 @@ class XboxData: title_info: dict[str, Title] = field(default_factory=dict) +@dataclass +class XboxCoordinators: + """Xbox coordinators.""" + + status: XboxUpdateCoordinator + consoles: XboxConsolesCoordinator + + class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]): """Store Xbox Console Status.""" @@ -280,3 +288,43 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]): for entry in self.hass.config_entries.async_entries(DOMAIN) if entry.unique_id is not None } + + +class XboxConsolesCoordinator(DataUpdateCoordinator[SmartglassConsoleList]): + """Update list of Xbox consoles.""" + + config_entry: XboxConfigEntry + + def __init__( + self, + hass: HomeAssistant, + config_entry: XboxConfigEntry, + coordinator: XboxUpdateCoordinator, + ) -> None: + """Initialize.""" + super().__init__( + hass, + _LOGGER, + config_entry=config_entry, + name=DOMAIN, + update_interval=timedelta(minutes=10), + ) + self.client = coordinator.client + self.async_set_updated_data(coordinator.consoles) + + async def _async_update_data(self) -> SmartglassConsoleList: + """Fetch console data.""" + + try: + return await self.client.smartglass.get_console_list() + except TimeoutException as e: + raise UpdateFailed( + translation_domain=DOMAIN, + translation_key="timeout_exception", + ) from e + except (RequestError, HTTPStatusError) as e: + _LOGGER.debug("Xbox exception:", exc_info=True) + raise UpdateFailed( + translation_domain=DOMAIN, + translation_key="request_exception", + ) from e diff --git a/homeassistant/components/xbox/image.py b/homeassistant/components/xbox/image.py index 60d574b3330..ac5d3d580a9 100644 --- a/homeassistant/components/xbox/image.py +++ b/homeassistant/components/xbox/image.py @@ -64,7 +64,7 @@ async def async_setup_entry( ) -> None: """Set up Xbox images.""" - coordinator = config_entry.runtime_data + coordinator = config_entry.runtime_data.status xuids_added: set[str] = set() diff --git a/homeassistant/components/xbox/media_player.py b/homeassistant/components/xbox/media_player.py index c43a0634751..73bee4ea901 100644 --- a/homeassistant/components/xbox/media_player.py +++ b/homeassistant/components/xbox/media_player.py @@ -56,7 +56,7 @@ async def async_setup_entry( ) -> None: """Set up Xbox media_player from a config entry.""" - coordinator = entry.runtime_data + coordinator = entry.runtime_data.status async_add_entities( [ diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index bcefa6829a9..76baeee166e 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -112,7 +112,7 @@ class XboxSource(MediaSource): translation_key="account_not_configured", ) from e - client = entry.runtime_data.client + client = entry.runtime_data.status.client if identifier.media_type in (ATTR_GAMECLIPS, ATTR_COMMUNITY_GAMECLIPS): try: @@ -302,7 +302,7 @@ class XboxSource(MediaSource): async def _build_games(self, entry: XboxConfigEntry) -> list[BrowseMediaSource]: """List Xbox games for the selected account.""" - client = entry.runtime_data.client + client = entry.runtime_data.status.client if TYPE_CHECKING: assert entry.unique_id fields = [ @@ -346,7 +346,7 @@ class XboxSource(MediaSource): self, entry: XboxConfigEntry, identifier: XboxMediaSourceIdentifier ) -> BrowseMediaSource: """Display game title.""" - client = entry.runtime_data.client + client = entry.runtime_data.status.client try: game = (await client.titlehub.get_title_info(identifier.title_id)).titles[0] except TimeoutException as e: @@ -402,7 +402,7 @@ class XboxSource(MediaSource): self, entry: XboxConfigEntry, identifier: XboxMediaSourceIdentifier ) -> BrowseMediaSource: """List game media.""" - client = entry.runtime_data.client + client = entry.runtime_data.status.client try: game = (await client.titlehub.get_title_info(identifier.title_id)).titles[0] except TimeoutException as e: @@ -439,7 +439,7 @@ class XboxSource(MediaSource): self, entry: XboxConfigEntry, identifier: XboxMediaSourceIdentifier ) -> list[BrowseMediaSource]: """List media items.""" - client = entry.runtime_data.client + client = entry.runtime_data.status.client if identifier.media_type != ATTR_GAMECLIPS: return [] @@ -483,7 +483,7 @@ class XboxSource(MediaSource): self, entry: XboxConfigEntry, identifier: XboxMediaSourceIdentifier ) -> list[BrowseMediaSource]: """List media items.""" - client = entry.runtime_data.client + client = entry.runtime_data.status.client if identifier.media_type != ATTR_COMMUNITY_GAMECLIPS: return [] @@ -527,7 +527,7 @@ class XboxSource(MediaSource): self, entry: XboxConfigEntry, identifier: XboxMediaSourceIdentifier ) -> list[BrowseMediaSource]: """List media items.""" - client = entry.runtime_data.client + client = entry.runtime_data.status.client if identifier.media_type != ATTR_SCREENSHOTS: return [] @@ -571,7 +571,7 @@ class XboxSource(MediaSource): self, entry: XboxConfigEntry, identifier: XboxMediaSourceIdentifier ) -> list[BrowseMediaSource]: """List media items.""" - client = entry.runtime_data.client + client = entry.runtime_data.status.client if identifier.media_type != ATTR_COMMUNITY_SCREENSHOTS: return [] @@ -640,7 +640,7 @@ class XboxSource(MediaSource): def gamerpic(config_entry: XboxConfigEntry) -> str | None: """Return gamerpic.""" - coordinator = config_entry.runtime_data + coordinator = config_entry.runtime_data.status if TYPE_CHECKING: assert config_entry.unique_id person = coordinator.data.presence[coordinator.client.xuid] diff --git a/homeassistant/components/xbox/remote.py b/homeassistant/components/xbox/remote.py index 9b42a6b265c..2e10ce89f68 100644 --- a/homeassistant/components/xbox/remote.py +++ b/homeassistant/components/xbox/remote.py @@ -27,7 +27,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up Xbox media_player from a config entry.""" - coordinator = entry.runtime_data + coordinator = entry.runtime_data.status async_add_entities( [XboxRemote(console, coordinator) for console in coordinator.consoles.result] diff --git a/homeassistant/components/xbox/sensor.py b/homeassistant/components/xbox/sensor.py index 0f24ad176b7..45d5c7a6e47 100644 --- a/homeassistant/components/xbox/sensor.py +++ b/homeassistant/components/xbox/sensor.py @@ -9,20 +9,32 @@ from enum import StrEnum from typing import Any from pythonxbox.api.provider.people.models import Person +from pythonxbox.api.provider.smartglass.models import SmartglassConsole, StorageDevice from pythonxbox.api.provider.titlehub.models import Title from homeassistant.components.sensor import ( DOMAIN as SENSOR_DOMAIN, + EntityCategory, SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) +from homeassistant.const import CONF_NAME, UnitOfInformation from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .coordinator import XboxConfigEntry -from .entity import XboxBaseEntity, XboxBaseEntityDescription, check_deprecated_entity +from .const import DOMAIN +from .coordinator import XboxConfigEntry, XboxConsolesCoordinator +from .entity import ( + MAP_MODEL, + XboxBaseEntity, + XboxBaseEntityDescription, + check_deprecated_entity, +) MAP_JOIN_RESTRICTIONS = { "local": "invite_only", @@ -44,6 +56,8 @@ class XboxSensor(StrEnum): FRIENDS = "friends" IN_PARTY = "in_party" JOIN_RESTRICTIONS = "join_restrictions" + TOTAL_STORAGE = "total_storage" + FREE_STORAGE = "free_storage" @dataclass(kw_only=True, frozen=True) @@ -53,6 +67,15 @@ class XboxSensorEntityDescription(XboxBaseEntityDescription, SensorEntityDescrip value_fn: Callable[[Person, Title | None], StateType | datetime] +@dataclass(kw_only=True, frozen=True) +class XboxStorageDeviceSensorEntityDescription( + XboxBaseEntityDescription, SensorEntityDescription +): + """Xbox console sensor description.""" + + value_fn: Callable[[StorageDevice], StateType] + + def now_playing_attributes(_: Person, title: Title | None) -> dict[str, Any]: """Attributes of the currently played title.""" attributes: dict[str, Any] = { @@ -195,6 +218,31 @@ SENSOR_DESCRIPTIONS: tuple[XboxSensorEntityDescription, ...] = ( ), ) +STORAGE_SENSOR_DESCRIPTIONS: tuple[XboxStorageDeviceSensorEntityDescription, ...] = ( + XboxStorageDeviceSensorEntityDescription( + key=XboxSensor.TOTAL_STORAGE, + translation_key=XboxSensor.TOTAL_STORAGE, + value_fn=lambda x: x.total_space_bytes, + device_class=SensorDeviceClass.DATA_SIZE, + native_unit_of_measurement=UnitOfInformation.BYTES, + suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, + suggested_display_precision=0, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), + XboxStorageDeviceSensorEntityDescription( + key=XboxSensor.FREE_STORAGE, + translation_key=XboxSensor.FREE_STORAGE, + value_fn=lambda x: x.free_space_bytes, + device_class=SensorDeviceClass.DATA_SIZE, + native_unit_of_measurement=UnitOfInformation.BYTES, + suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES, + suggested_display_precision=0, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -203,7 +251,7 @@ async def async_setup_entry( ) -> None: """Set up Xbox Live friends.""" xuids_added: set[str] = set() - coordinator = config_entry.runtime_data + coordinator = config_entry.runtime_data.status @callback def add_entities() -> None: @@ -225,6 +273,20 @@ async def async_setup_entry( coordinator.async_add_listener(add_entities) add_entities() + consoles_coordinator = config_entry.runtime_data.consoles + + async_add_entities( + [ + XboxStorageDeviceSensorEntity( + console, storage_device, consoles_coordinator, description + ) + for description in STORAGE_SENSOR_DESCRIPTIONS + for console in coordinator.consoles.result + if console.storage_devices + for storage_device in console.storage_devices + ] + ) + class XboxSensorEntity(XboxBaseEntity, SensorEntity): """Representation of a Xbox presence state.""" @@ -235,3 +297,62 @@ class XboxSensorEntity(XboxBaseEntity, SensorEntity): def native_value(self) -> StateType | datetime: """Return the state of the requested attribute.""" return self.entity_description.value_fn(self.data, self.title_info) + + +class XboxStorageDeviceSensorEntity( + CoordinatorEntity[XboxConsolesCoordinator], SensorEntity +): + """Console storage device entity for the Xbox integration.""" + + _attr_has_entity_name = True + entity_description: XboxStorageDeviceSensorEntityDescription + + def __init__( + self, + console: SmartglassConsole, + storage_device: StorageDevice, + coordinator: XboxConsolesCoordinator, + entity_description: XboxStorageDeviceSensorEntityDescription, + ) -> None: + """Initialize the Xbox Console entity.""" + super().__init__(coordinator) + self.entity_description = entity_description + self.client = coordinator.client + self._console = console + self._storage_device = storage_device + self._attr_unique_id = ( + f"{console.id}_{storage_device.storage_device_id}_{entity_description.key}" + ) + self._attr_translation_placeholders = { + CONF_NAME: storage_device.storage_device_name + } + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, console.id)}, + manufacturer="Microsoft", + model=MAP_MODEL.get(self._console.console_type, "Unknown"), + name=console.name, + ) + + @property + def data(self): + """Storage device data.""" + consoles = self.coordinator.data.result + console = next((c for c in consoles if c.id == self._console.id), None) + if not console or not console.storage_devices: + return None + + return next( + ( + d + for d in console.storage_devices + if d.storage_device_id == self._storage_device.storage_device_id + ), + None, + ) + + @property + def native_value(self) -> StateType: + """Return the state of the requested attribute.""" + + return self.entity_description.value_fn(self.data) if self.data else None diff --git a/homeassistant/components/xbox/strings.json b/homeassistant/components/xbox/strings.json index 61e958f0d1d..096747c816e 100644 --- a/homeassistant/components/xbox/strings.json +++ b/homeassistant/components/xbox/strings.json @@ -65,6 +65,9 @@ "name": "Following", "unit_of_measurement": "people" }, + "free_storage": { + "name": "Free space - {name}" + }, "friends": { "name": "Friends", "unit_of_measurement": "[%key:component::xbox::entity::sensor::following::unit_of_measurement%]" @@ -105,6 +108,9 @@ }, "status": { "name": "Status" + }, + "total_storage": { + "name": "Total space - {name}" } } }, diff --git a/tests/components/xbox/snapshots/test_sensor.ambr b/tests/components/xbox/snapshots/test_sensor.ambr index b8422dfdfa6..0d68963b42f 100644 --- a/tests/components/xbox/snapshots/test_sensor.ambr +++ b/tests/components/xbox/snapshots/test_sensor.ambr @@ -1371,3 +1371,357 @@ 'state': 'Offline', }) # --- +# name: test_sensors[sensor.xone_free_space_external-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.xone_free_space_external', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Free space - External', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': 'HIJKLMN_3_free_storage', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.xone_free_space_external-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'XONE Free space - External', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.xone_free_space_external', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2980.89726638794', + }) +# --- +# name: test_sensors[sensor.xone_free_space_internal-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.xone_free_space_internal', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Free space - Internal', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': 'HIJKLMN_2_free_storage', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.xone_free_space_internal-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'XONE Free space - Internal', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.xone_free_space_internal', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '137.056728363037', + }) +# --- +# name: test_sensors[sensor.xone_total_space_external-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.xone_total_space_external', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total space - External', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': 'HIJKLMN_3_total_storage', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.xone_total_space_external-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'XONE Total space - External', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.xone_total_space_external', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3726.02327680588', + }) +# --- +# name: test_sensors[sensor.xone_total_space_internal-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.xone_total_space_internal', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total space - Internal', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': 'HIJKLMN_2_total_storage', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.xone_total_space_internal-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'XONE Total space - Internal', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.xone_total_space_internal', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '364.999996185303', + }) +# --- +# name: test_sensors[sensor.xonex_free_space_internal-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.xonex_free_space_internal', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Free space - Internal', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': 'ABCDEFG_1_free_storage', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.xonex_free_space_internal-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'XONEX Free space - Internal', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.xonex_free_space_internal', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '220.041568756104', + }) +# --- +# name: test_sensors[sensor.xonex_total_space_internal-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.xonex_total_space_internal', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total space - Internal', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': 'ABCDEFG_1_total_storage', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.xonex_total_space_internal-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'XONEX Total space - Internal', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.xonex_total_space_internal', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '780.999996185303', + }) +# ---