diff --git a/.github/actions/setup-uv-python/action.yml b/.github/actions/setup-uv-python/action.yml index 821640ac06b4a1..f1a30c51faada4 100644 --- a/.github/actions/setup-uv-python/action.yml +++ b/.github/actions/setup-uv-python/action.yml @@ -25,7 +25,7 @@ runs: steps: - name: Set up uv id: uv - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 with: version: ${{ inputs.uv-version }} python-version: ${{ inputs.python-version }} diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index fac33704de72ad..d98a2b21bbbc97 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -215,6 +215,50 @@ }, "dpt": { "options": { + "1": "Generic 1-bit", + "1_001": "Switch", + "1_002": "Boolean", + "1_003": "Enable", + "1_004": "Ramp", + "1_005": "Alarm", + "1_006": "Binary value", + "1_007": "Step", + "1_008": "Up / Down", + "1_009": "Open / Close", + "1_010": "Start", + "1_011": "State", + "1_012": "Invert", + "1_013": "Dimming send style", + "1_014": "Input source", + "1_015": "Reset", + "1_016": "Acknowledge", + "1_017": "Trigger", + "1_018": "Occupancy", + "1_019": "Window / Door", + "1_021": "Logical function", + "1_022": "Scene A/B", + "1_023": "Shutter / Blinds mode", + "1_024": "Day / Night", + "1_100": "Heat / Cool", + "1_1200": "Consumer / Producer", + "1_1201": "Energy direction", + "2": "Generic 2-bit boolean control", + "2_001": "Switch control", + "2_002": "Boolean control", + "2_003": "Enable control", + "2_004": "Ramp control", + "2_005": "Alarm control", + "2_006": "Binary value control", + "2_007": "Step control", + "2_008": "Direction 1 control", + "2_009": "Direction 2 control", + "2_010": "Start control", + "2_011": "State control", + "2_012": "Invert control", + "3_007": "Dimming control", + "3_008": "Blinds control", + "4_001": "Character (ASCII)", + "4_002": "Character (Latin-1)", "5": "Generic 1-byte unsigned integer", "5_001": "Percent (0 … 100)", "5_003": "Angle", @@ -273,6 +317,8 @@ "9_029": "Absolute humidity", "9_030": "Concentration", "9_60000": "Enthalpy", + "10_001": "Time of day", + "11_001": "Date", "12": "Generic 4-byte unsigned integer", "12_001": "Counter (0 … 4 294 967 295)", "12_100": "Time period (4-byte)", @@ -380,10 +426,25 @@ "16_000": "String (ASCII)", "16_001": "String (Latin-1)", "17_001": "Scene number", + "18_001": "Scene control", + "19_001": "Date and time", + "20_102": "HVAC mode", + "20_105": "HVAC controller mode", + "20_60102": "HVAC status", "29": "Generic 8-byte signed integer", "29_010": "Active energy (8-byte)", "29_011": "Apparent energy (8-byte)", - "29_012": "Reactive energy (8-byte)" + "29_012": "Reactive energy (8-byte)", + "232_600": "RGB color", + "235_001": "Tariff active energy", + "242_600": "xyY color", + "243_600": "xyY color transition", + "249_600": "Brightness and color temperature transition", + "250_600": "Brightness and color temperature control", + "251_600": "RGBW color", + "252_600": "Relative RGBW control", + "253_600": "Relative xyY control", + "254_600": "Relative RGB control" }, "selector": { "label": "Select a datapoint type", diff --git a/homeassistant/components/linode/manifest.json b/homeassistant/components/linode/manifest.json index 975747de86de35..fa8c178274588d 100644 --- a/homeassistant/components/linode/manifest.json +++ b/homeassistant/components/linode/manifest.json @@ -2,6 +2,7 @@ "domain": "linode", "name": "Linode", "codeowners": [], + "disabled": "Linode relies on setuptools.pkg_resources, which is no longer available in setuptools 82.0.0 and later.", "documentation": "https://www.home-assistant.io/integrations/linode", "iot_class": "cloud_polling", "loggers": ["linode"], diff --git a/homeassistant/components/mealie/manifest.json b/homeassistant/components/mealie/manifest.json index 24fcd3c1538e32..ae80e386cbdc8e 100644 --- a/homeassistant/components/mealie/manifest.json +++ b/homeassistant/components/mealie/manifest.json @@ -7,5 +7,5 @@ "integration_type": "service", "iot_class": "local_polling", "quality_scale": "platinum", - "requirements": ["aiomealie==2.1.1"] + "requirements": ["aiomealie==2.1.2"] } diff --git a/homeassistant/components/monarch_money/manifest.json b/homeassistant/components/monarch_money/manifest.json index a204277c465f21..063bd01474fd19 100644 --- a/homeassistant/components/monarch_money/manifest.json +++ b/homeassistant/components/monarch_money/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/monarchmoney", "integration_type": "service", "iot_class": "cloud_polling", - "requirements": ["monarchmoneycommunity==1.5.2"] + "requirements": ["monarchmoneycommunity==1.6.0"] } diff --git a/homeassistant/components/mystrom/strings.json b/homeassistant/components/mystrom/strings.json index b4c8669386614e..c4bb37ceb23f88 100644 --- a/homeassistant/components/mystrom/strings.json +++ b/homeassistant/components/mystrom/strings.json @@ -34,5 +34,10 @@ "name": "Last restart" } } + }, + "exceptions": { + "switch_action_failed": { + "message": "Failed to perform the action on the myStrom plug." + } } } diff --git a/homeassistant/components/mystrom/switch.py b/homeassistant/components/mystrom/switch.py index 746a7c3aa76005..ed4d52ddbb3951 100644 --- a/homeassistant/components/mystrom/switch.py +++ b/homeassistant/components/mystrom/switch.py @@ -7,6 +7,7 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import DeviceInfo, format_mac from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -52,18 +53,22 @@ async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" try: await self.plug.turn_on() - # pylint: disable-next=home-assistant-action-swallowed-exception - except MyStromConnectionError: - _LOGGER.error("No route to myStrom plug") + except MyStromConnectionError as err: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="switch_action_failed", + ) from err @override async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" try: await self.plug.turn_off() - # pylint: disable-next=home-assistant-action-swallowed-exception - except MyStromConnectionError: - _LOGGER.error("No route to myStrom plug") + except MyStromConnectionError as err: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="switch_action_failed", + ) from err async def async_update(self) -> None: """Get the latest data from the device and update the data.""" diff --git a/homeassistant/components/opower/coordinator.py b/homeassistant/components/opower/coordinator.py index 96da7fe0388133..02882dca9e24c7 100644 --- a/homeassistant/components/opower/coordinator.py +++ b/homeassistant/components/opower/coordinator.py @@ -585,6 +585,15 @@ def _update_with_finer_cost_reads( _LOGGER.error("Error getting daily cost reads: %s", err) raise _LOGGER.debug("Got %s daily cost reads", len(daily_cost_reads)) + if not daily_cost_reads and start_time is not None: + if account.read_resolution is ReadResolution.DAY: + # Nothing finer to fall back to, and see the hourly reads below. + _LOGGER.debug("No daily cost reads. Skipping update") + return [] + # The hourly reads below are still finer than the statistics they + # land on, so only drop the bill reads and carry on with those. + _LOGGER.debug("No daily cost reads. Trying the hourly ones") + cost_reads = [] _update_with_finer_cost_reads(cost_reads, daily_cost_reads) if account.read_resolution is ReadResolution.DAY: return cost_reads @@ -603,6 +612,14 @@ def _update_with_finer_cost_reads( _LOGGER.error("Error getting hourly cost reads: %s", err) raise _LOGGER.debug("Got %s hourly cost reads", len(hourly_cost_reads)) + if not hourly_cost_reads and start_time is not None: + # A day read starts at local midnight, where that day's first hourly + # statistic already is, so keeping the daily reads would replace it + # with a coarser one carrying a different running sum. Only a concern + # once statistics exist: on the initial import there is nothing to + # overwrite and the daily reads are the best available data. + _LOGGER.debug("No hourly cost reads. Skipping update") + return [] _update_with_finer_cost_reads(cost_reads, hourly_cost_reads) _LOGGER.debug("Got %s cost reads", len(cost_reads)) return cost_reads diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index 42866fa88dbe6e..4d933b995437f7 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -9,5 +9,5 @@ "iot_class": "cloud_polling", "loggers": ["opower"], "quality_scale": "platinum", - "requirements": ["opower==0.21.0"] + "requirements": ["opower==0.22.1"] } diff --git a/homeassistant/components/select/__init__.py b/homeassistant/components/select/__init__.py index d502a27c93cb27..d8a6ff18f4ea26 100644 --- a/homeassistant/components/select/__init__.py +++ b/homeassistant/components/select/__init__.py @@ -4,7 +4,6 @@ import logging from typing import Any, final, override -import probatio from propcache.api import cached_property from homeassistant.config_entries import ConfigEntry @@ -15,11 +14,11 @@ from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType -from homeassistant.util.hass_dict import HassKey from .const import ( ATTR_CYCLE, ATTR_OPTIONS, + DATA_COMPONENT, DOMAIN, SERVICE_SELECT_FIRST, SERVICE_SELECT_LAST, @@ -27,10 +26,10 @@ SERVICE_SELECT_PREVIOUS, SelectEntityCapabilityAttribute, ) +from .services import async_setup_services _LOGGER = logging.getLogger(__name__) -DATA_COMPONENT: HassKey[EntityComponent[SelectEntity]] = HassKey(DOMAIN) ENTITY_ID_FORMAT = DOMAIN + ".{}" PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE @@ -65,35 +64,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) await component.async_setup(config) - component.async_register_entity_service( - SERVICE_SELECT_FIRST, - None, - SelectEntity.async_first.__name__, - ) - - component.async_register_entity_service( - SERVICE_SELECT_LAST, - None, - SelectEntity.async_last.__name__, - ) - - component.async_register_entity_service( - SERVICE_SELECT_NEXT, - {probatio.Optional(ATTR_CYCLE, default=True): bool}, - SelectEntity.async_next.__name__, - ) - - component.async_register_entity_service( - SERVICE_SELECT_OPTION, - {probatio.Required(ATTR_OPTION): cv.string}, - SelectEntity.async_handle_select_option.__name__, - ) - - component.async_register_entity_service( - SERVICE_SELECT_PREVIOUS, - {probatio.Optional(ATTR_CYCLE, default=True): bool}, - SelectEntity.async_previous.__name__, - ) + async_setup_services(hass) return True diff --git a/homeassistant/components/select/const.py b/homeassistant/components/select/const.py index bb63dbdc3f0155..271b2d5e97a91a 100644 --- a/homeassistant/components/select/const.py +++ b/homeassistant/components/select/const.py @@ -1,9 +1,17 @@ """Provides the constants needed for the component.""" from enum import StrEnum -from typing import Final +from typing import TYPE_CHECKING, Final + +from homeassistant.util.hass_dict import HassKey + +if TYPE_CHECKING: + from homeassistant.helpers.entity_component import EntityComponent + + from . import SelectEntity DOMAIN: Final = "select" +DATA_COMPONENT: HassKey[EntityComponent[SelectEntity]] = HassKey(DOMAIN) class SelectEntityCapabilityAttribute(StrEnum): diff --git a/homeassistant/components/select/services.py b/homeassistant/components/select/services.py new file mode 100644 index 00000000000000..c4be3f7dd31c96 --- /dev/null +++ b/homeassistant/components/select/services.py @@ -0,0 +1,52 @@ +"""Services for the Select integration.""" + +import probatio + +from homeassistant.const import ATTR_OPTION, SERVICE_SELECT_OPTION +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import config_validation as cv + +from .const import ( + ATTR_CYCLE, + DATA_COMPONENT, + SERVICE_SELECT_FIRST, + SERVICE_SELECT_LAST, + SERVICE_SELECT_NEXT, + SERVICE_SELECT_PREVIOUS, +) + + +@callback +def async_setup_services(hass: HomeAssistant) -> None: + """Register the select services.""" + component = hass.data[DATA_COMPONENT] + + component.async_register_entity_service( + SERVICE_SELECT_FIRST, + None, + "async_first", + ) + + component.async_register_entity_service( + SERVICE_SELECT_LAST, + None, + "async_last", + ) + + component.async_register_entity_service( + SERVICE_SELECT_NEXT, + {probatio.Optional(ATTR_CYCLE, default=True): bool}, + "async_next", + ) + + component.async_register_entity_service( + SERVICE_SELECT_OPTION, + {probatio.Required(ATTR_OPTION): cv.string}, + "async_handle_select_option", + ) + + component.async_register_entity_service( + SERVICE_SELECT_PREVIOUS, + {probatio.Optional(ATTR_CYCLE, default=True): bool}, + "async_previous", + ) diff --git a/homeassistant/components/subaru/manifest.json b/homeassistant/components/subaru/manifest.json index eec35f15a175c1..84b6890dd62442 100644 --- a/homeassistant/components/subaru/manifest.json +++ b/homeassistant/components/subaru/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["stdiomask", "subarulink"], "quality_scale": "bronze", - "requirements": ["subarulink==0.7.19"] + "requirements": ["subarulink==0.7.21"] } diff --git a/homeassistant/components/tank_utility/manifest.json b/homeassistant/components/tank_utility/manifest.json index 76240252696b55..a1d58ee229d854 100644 --- a/homeassistant/components/tank_utility/manifest.json +++ b/homeassistant/components/tank_utility/manifest.json @@ -2,6 +2,7 @@ "domain": "tank_utility", "name": "Tank Utility", "codeowners": [], + "disabled": "Tank Utility relies on setuptools.pkg_resources, which is no longer available in setuptools 82.0.0 and later.", "documentation": "https://www.home-assistant.io/integrations/tank_utility", "iot_class": "cloud_polling", "loggers": ["tank_utility"], diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 7a876c31d20d57..fc5fe4ffe23c34 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -18,7 +18,7 @@ from aiohttp import web import mutagen -from mutagen.id3 import ID3, TextFrame as ID3Text +from mutagen.id3 import TALB, TIT2, TPE1, Encoding import probatio from propcache.api import cached_property @@ -355,6 +355,9 @@ async def _async_convert_audio( if to_sample_bytes == 2: # 16-bit samples. command.extend(["-sample_fmt", "s16"]) + # Do not write the muxer's own encoder metadata; metadata from the input + # is still copied. + command.extend(["-fflags", "+bitexact"]) command.append("pipe:1") # Send output to stdout. process = await asyncio.create_subprocess_exec( @@ -1195,23 +1198,9 @@ def write_tags( if tts_file is not None: if not tts_file.tags: tts_file.add_tags() - if isinstance(tts_file.tags, ID3): - tts_file["artist"] = ID3Text( - encoding=3, - text=artist, # type: ignore[no-untyped-call] - ) - tts_file["album"] = ID3Text( - encoding=3, - text=album, # type: ignore[no-untyped-call] - ) - tts_file["title"] = ID3Text( - encoding=3, - text=message, # type: ignore[no-untyped-call] - ) - else: - tts_file["artist"] = artist - tts_file["album"] = album - tts_file["title"] = message + tts_file.tags.add(TPE1(encoding=Encoding.UTF8, text=artist)) # type: ignore[no-untyped-call] + tts_file.tags.add(TALB(encoding=Encoding.UTF8, text=album)) # type: ignore[no-untyped-call] + tts_file.tags.add(TIT2(encoding=Encoding.UTF8, text=message)) # type: ignore[no-untyped-call] data_bytes.seek(0) tts_file.save(data_bytes) except mutagen.MutagenError as err: diff --git a/homeassistant/components/xbox/image.py b/homeassistant/components/xbox/image.py index cb1a6b2d83ef7b..8462718950d0a3 100644 --- a/homeassistant/components/xbox/image.py +++ b/homeassistant/components/xbox/image.py @@ -25,6 +25,12 @@ class XboxImage(StrEnum): NOW_PLAYING = "now_playing" GAMERPIC = "gamerpic" AVATAR = "avatar" + POSTER = "poster" + BRANDED_KEY_ART = "branded_key_art" + TITLED_HERO_ART = "titled_hero_art" + SUPER_HERO_ART = "super_hero_art" + BOX_ART = "box_art" + FEATURE_PROMOTIONAL_SQUARE_ART = "feature_promotional_square_art" @dataclass(kw_only=True, frozen=True) @@ -45,6 +51,67 @@ class XboxImageEntityDescription(XboxBaseEntityDescription, ImageEntityDescripti translation_key=XboxImage.NOW_PLAYING, image_url_fn=lambda _, title: title.display_image if title else None, ), + XboxImageEntityDescription( + key=XboxImage.POSTER, + translation_key=XboxImage.POSTER, + image_url_fn=lambda _, title: ( + next((img.url for img in title.images if img.type == "Poster"), None) + if title and title.images + else None + ), + ), + XboxImageEntityDescription( + key=XboxImage.BRANDED_KEY_ART, + translation_key=XboxImage.BRANDED_KEY_ART, + image_url_fn=lambda _, title: ( + next((img.url for img in title.images if img.type == "BrandedKeyArt"), None) + if title and title.images + else None + ), + ), + XboxImageEntityDescription( + key=XboxImage.TITLED_HERO_ART, + translation_key=XboxImage.TITLED_HERO_ART, + image_url_fn=lambda _, title: ( + next((img.url for img in title.images if img.type == "TitledHeroArt"), None) + if title and title.images + else None + ), + ), + XboxImageEntityDescription( + key=XboxImage.SUPER_HERO_ART, + translation_key=XboxImage.SUPER_HERO_ART, + image_url_fn=lambda _, title: ( + next((img.url for img in title.images if img.type == "SuperHeroArt"), None) + if title and title.images + else None + ), + ), + XboxImageEntityDescription( + key=XboxImage.BOX_ART, + translation_key=XboxImage.BOX_ART, + image_url_fn=lambda _, title: ( + next((img.url for img in title.images if img.type == "BoxArt"), None) + if title and title.images + else None + ), + ), + XboxImageEntityDescription( + key=XboxImage.FEATURE_PROMOTIONAL_SQUARE_ART, + translation_key=XboxImage.FEATURE_PROMOTIONAL_SQUARE_ART, + image_url_fn=lambda _, title: ( + next( + ( + img.url + for img in title.images + if img.type == "FeaturePromotionalSquareArt" + ), + None, + ) + if title and title.images + else None + ), + ), XboxImageEntityDescription( key=XboxImage.AVATAR, translation_key=XboxImage.AVATAR, diff --git a/homeassistant/components/xbox/strings.json b/homeassistant/components/xbox/strings.json index 4008026bef060f..cccafd9f9efa4e 100644 --- a/homeassistant/components/xbox/strings.json +++ b/homeassistant/components/xbox/strings.json @@ -83,11 +83,29 @@ "avatar": { "name": "Avatar" }, + "box_art": { + "name": "Box art" + }, + "branded_key_art": { + "name": "Branded key art" + }, + "feature_promotional_square_art": { + "name": "Feature promotional square art" + }, "gamerpic": { "name": "Gamerpic" }, "now_playing": { "name": "[%key:component::xbox::entity::sensor::now_playing::name%]" + }, + "poster": { + "name": "Poster" + }, + "super_hero_art": { + "name": "Superhero art" + }, + "titled_hero_art": { + "name": "Titled hero art" } }, "sensor": { diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5c739ba5f33eae..6b79833b1c4f01 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -253,9 +253,6 @@ caio<0.12.3 # https://github.com/auth0/auth0-python/releases/tag/5.0.0 auth0-python<5.0 -# Setuptools >=82.0.0 doesn't contain pkg_resources anymore -setuptools<82.0.0 - # backoff and python-backoff share the same package name # pin versions which are mostly compatible to each other backoff==2.2.1 @@ -268,7 +265,7 @@ python-backoff<2.4.0 azure-kusto-data==4.5.1 azure-kusto-ingest==4.5.1 coloredlogs==15.0.1 -setuptools==81.0.0 +setuptools==84.0.0 # Pin cffi to 2.0.0 to avoid version mismatch with the pre-baked _cffi_backend in the base image. # https://github.com/home-assistant/core/issues/175832 diff --git a/pyproject.toml b/pyproject.toml index e27f93d1c10382..0d7a504f43c13c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools==78.1.1"] +requires = ["setuptools==84.0.0"] build-backend = "setuptools.build_meta" [project] diff --git a/requirements_all.txt b/requirements_all.txt index 5149f9b618839c..a0a82a1fb34f36 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -354,7 +354,7 @@ aiolookin==1.0.0 aiolyric==2.1.2 # homeassistant.components.mealie -aiomealie==2.1.1 +aiomealie==2.1.2 # homeassistant.components.melcloud_home aiomelcloudhome==0.2.3 @@ -1549,9 +1549,6 @@ lightwave==0.24 # homeassistant.components.limitlessled limitlessled==1.1.3 -# homeassistant.components.linode -linode-api==4.1.9b1 - # homeassistant.components.livisi livisi==0.0.25 @@ -1665,7 +1662,7 @@ moehlenhoff-alpha2==1.4.0 momonga==0.3.0 # homeassistant.components.monarch_money -monarchmoneycommunity==1.5.2 +monarchmoneycommunity==1.6.0 # homeassistant.components.monzo monzopy==1.8.0 @@ -1863,7 +1860,7 @@ openwrt-luci-rpc==1.1.17 openwrt-ubus-rpc==0.0.3 # homeassistant.components.opower -opower==0.21.0 +opower==0.22.1 # homeassistant.components.oralb oralb-ble==1.1.3 @@ -3204,7 +3201,7 @@ stookwijzer==1.6.1 streamlabswater==1.0.1 # homeassistant.components.subaru -subarulink==0.7.19 +subarulink==0.7.21 # homeassistant.components.sunsynk sunsynk-api-client==1.4.0 @@ -3227,9 +3224,6 @@ systembridgeconnector==5.4.3 # homeassistant.components.tailscale tailscale==0.7.0 -# homeassistant.components.tank_utility -tank-utility==1.5.0 - # homeassistant.components.tapsaff tapsaff==0.2.1 diff --git a/requirements_test.txt b/requirements_test.txt index f6521495b7489a..399f3c5a991192 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,7 +10,7 @@ # ast-serialize is an internal mypy dependency ast-serialize==0.6.0 astroid==4.0.4 -coverage==7.16.0 +coverage==7.16.1 freezegun==1.5.5 # librt is an internal mypy dependency librt==0.13.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 4782ecea897d5a..615504f8632fef 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -237,9 +237,6 @@ # https://github.com/auth0/auth0-python/releases/tag/5.0.0 auth0-python<5.0 -# Setuptools >=82.0.0 doesn't contain pkg_resources anymore -setuptools<82.0.0 - # backoff and python-backoff share the same package name # pin versions which are mostly compatible to each other backoff==2.2.1 @@ -252,7 +249,7 @@ azure-kusto-data==4.5.1 azure-kusto-ingest==4.5.1 coloredlogs==15.0.1 -setuptools==81.0.0 +setuptools==84.0.0 # Pin cffi to 2.0.0 to avoid version mismatch with the pre-baked _cffi_backend in the base image. # https://github.com/home-assistant/core/issues/175832 diff --git a/script/sqlite3_vendor/pyproject.toml b/script/sqlite3_vendor/pyproject.toml index ea87fdf17189e1..325e8de01341f8 100644 --- a/script/sqlite3_vendor/pyproject.toml +++ b/script/sqlite3_vendor/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools==78.1.1"] +requires = ["setuptools==84.0.0"] build-backend = "setuptools.build_meta" diff --git a/tests/components/eurotronic_cometblue/test_services.py b/tests/components/eurotronic_cometblue/test_services.py index ef22cceb7f4079..cb5fda23acbedd 100644 --- a/tests/components/eurotronic_cometblue/test_services.py +++ b/tests/components/eurotronic_cometblue/test_services.py @@ -6,8 +6,8 @@ from syrupy.assertion import SnapshotAssertion from homeassistant.components.eurotronic_cometblue import DOMAIN -from homeassistant.components.number import ServiceValidationError from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ServiceValidationError from homeassistant.util import dt as dt_util from .conftest import setup_with_selected_platforms diff --git a/tests/components/mystrom/test_switch.py b/tests/components/mystrom/test_switch.py new file mode 100644 index 00000000000000..e73d6ae62c18e1 --- /dev/null +++ b/tests/components/mystrom/test_switch.py @@ -0,0 +1,41 @@ +"""Test the myStrom switch.""" + +from unittest.mock import AsyncMock + +from pymystrom.exceptions import MyStromConnectionError +import pytest + +from homeassistant.components.mystrom.const import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from .test_init import init_integration + +from tests.common import MockConfigEntry + + +@pytest.mark.parametrize("service", [SERVICE_TURN_ON, SERVICE_TURN_OFF]) +async def test_switch_action_raises_when_plug_unreachable( + hass: HomeAssistant, + config_entry: MockConfigEntry, + service: str, +) -> None: + """Test the switch action reports the failure to the caller.""" + await init_integration(hass, config_entry, 106) + + device = config_entry.runtime_data.device + device._state["on"] = True + device.turn_on = AsyncMock(side_effect=MyStromConnectionError()) + device.turn_off = AsyncMock(side_effect=MyStromConnectionError()) + + with pytest.raises(HomeAssistantError) as exc_info: + await hass.services.async_call( + "switch", + service, + {ATTR_ENTITY_ID: "switch.mystrom_device"}, + blocking=True, + ) + + assert exc_info.value.translation_domain == DOMAIN + assert exc_info.value.translation_key == "switch_action_failed" diff --git a/tests/components/opower/test_coordinator.py b/tests/components/opower/test_coordinator.py index 7e33fe606ce951..df0d4616b55c48 100644 --- a/tests/components/opower/test_coordinator.py +++ b/tests/components/opower/test_coordinator.py @@ -1,6 +1,8 @@ """Tests for the Opower coordinator.""" from datetime import datetime, timedelta +from itertools import pairwise +from typing import Any from unittest.mock import AsyncMock, patch from opower import AggregateType, CostRead @@ -636,3 +638,325 @@ async def test_coordinator_no_new_cost_reads_after_initial_load( get_last_statistics, hass, 1, statistic_id, True, {"sum"} ) assert stats[statistic_id][0]["sum"] == 1.5 + + +async def test_coordinator_skips_update_when_hourly_reads_are_empty( + recorder_mock: Recorder, + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_opower_api: AsyncMock, +) -> None: + """Test that an empty hourly response does not write daily data over hourly data. + + The account provides hourly reads, so an empty hourly response is a + transient failure. Falling back to the daily reads would write a day + resolution statistic at the same timestamp as the first hourly statistic of + that day, carrying a different running sum, which corrupts the series. + """ + coordinator = OpowerCoordinator(hass, mock_config_entry) + account = mock_opower_api.async_get_accounts.return_value[0] + mock_opower_api.async_get_accounts.return_value = [account] + + statistic_id = "opower:pge_elec_111111_energy_consumption" + # 08:00 UTC is local midnight for the utility's timezone in January. + day1 = dt_util.as_utc(datetime(2023, 1, 1, 8)) + day2 = dt_util.as_utc(datetime(2023, 1, 2, 8)) + day3 = dt_util.as_utc(datetime(2023, 1, 3, 8)) + + def bill_reads() -> list[CostRead]: + return [ + CostRead( + start_time=day1, + end_time=dt_util.as_utc(datetime(2023, 2, 1, 8)), + consumption=60.0, + provided_cost=12.0, + ) + ] + + def daily_reads() -> list[CostRead]: + return [ + CostRead( + start_time=start, end_time=end, consumption=30.0, provided_cost=6.0 + ) + for start, end in ((day1, day2), (day2, day3)) + ] + + def hourly_reads() -> list[CostRead]: + return [ + CostRead( + start_time=day + timedelta(hours=hour), + end_time=day + timedelta(hours=hour + 1), + consumption=1.5, + provided_cost=0.3, + ) + for day in (day1, day2) + for hour in range(3) + ] + + async def all_resolutions(acc, aggregate_type, start, end): + return { + AggregateType.BILL: bill_reads(), + AggregateType.DAY: daily_reads(), + AggregateType.HOUR: hourly_reads(), + }[aggregate_type] + + mock_opower_api.async_get_cost_reads.side_effect = all_resolutions + await coordinator._async_update_data() + await async_wait_recording_done(hass) + + async def stats() -> list[dict[str, Any]]: + period_stats = await hass.async_add_executor_job( + statistics_during_period, + hass, + day1, + None, + {statistic_id}, + "hour", + None, + {"start", "state", "sum"}, + ) + return period_stats[statistic_id] + + before = await stats() + assert len(before) == len(hourly_reads()) + + # The hourly endpoint transiently returns nothing. + async def no_hourly(acc, aggregate_type, start, end): + return { + AggregateType.BILL: bill_reads(), + AggregateType.DAY: daily_reads(), + AggregateType.HOUR: [], + }[aggregate_type] + + mock_opower_api.async_get_cost_reads.side_effect = no_hourly + await coordinator._async_update_data() + await async_wait_recording_done(hass) + + after = await stats() + assert after == before + # Every sum must still be the previous sum plus that hour's state. A daily + # read written at local midnight breaks this because it carries its own sum. + for previous, current in pairwise(after): + assert current["sum"] == pytest.approx(previous["sum"] + current["state"]) + + +async def test_coordinator_skips_update_when_daily_reads_are_empty( + recorder_mock: Recorder, + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_opower_api: AsyncMock, +) -> None: + """Test that an empty daily response does not write bill data over daily data.""" + coordinator = OpowerCoordinator(hass, mock_config_entry) + # The gas account provides daily reads. + account = mock_opower_api.async_get_accounts.return_value[1] + mock_opower_api.async_get_accounts.return_value = [account] + + statistic_id = "opower:pge_gas_222222_energy_consumption" + jan1 = dt_util.as_utc(datetime(2023, 1, 1, 8)) + feb1 = dt_util.as_utc(datetime(2023, 2, 1, 8)) + mar1 = dt_util.as_utc(datetime(2023, 3, 1, 8)) + + # Two bill periods, so the second one is not skipped as the starting anchor. + # The coordinator splices the finer reads into the list it is given, so each + # call has to hand back a fresh list. + def bill_reads() -> list[CostRead]: + return [ + CostRead( + start_time=jan1, end_time=feb1, consumption=60.0, provided_cost=12.0 + ), + CostRead( + start_time=feb1, end_time=mar1, consumption=55.0, provided_cost=11.0 + ), + ] + + def daily_reads() -> list[CostRead]: + return [ + CostRead( + start_time=start, + end_time=start + timedelta(days=1), + consumption=2.0, + provided_cost=0.4, + ) + for start in (jan1, jan1 + timedelta(days=1), feb1) + ] + + async def all_resolutions(acc, aggregate_type, start, end): + return { + AggregateType.BILL: bill_reads(), + AggregateType.DAY: daily_reads(), + }[aggregate_type] + + mock_opower_api.async_get_cost_reads.side_effect = all_resolutions + await coordinator._async_update_data() + await async_wait_recording_done(hass) + + async def stats() -> list[dict[str, Any]]: + period_stats = await hass.async_add_executor_job( + statistics_during_period, + hass, + jan1, + None, + {statistic_id}, + "hour", + None, + {"start", "state", "sum"}, + ) + return period_stats[statistic_id] + + before = await stats() + assert len(before) == len(daily_reads()) + + # The daily endpoint transiently returns nothing. + async def no_daily(acc, aggregate_type, start, end): + return {AggregateType.BILL: bill_reads(), AggregateType.DAY: []}[aggregate_type] + + mock_opower_api.async_get_cost_reads.side_effect = no_daily + await coordinator._async_update_data() + await async_wait_recording_done(hass) + + after = await stats() + assert after == before + for previous, current in pairwise(after): + assert current["sum"] == pytest.approx(previous["sum"] + current["state"]) + + +async def test_coordinator_initial_import_keeps_older_billing_history( + recorder_mock: Recorder, + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_opower_api: AsyncMock, +) -> None: + """Test that an account with only history older than the finer windows imports. + + The daily reads are fetched for the last three years and the hourly ones for + the last two months, so an account whose data ends before those windows + returns nothing for both. There are no statistics to overwrite on the first + import, so the bill reads it does return must still be stored. + """ + coordinator = OpowerCoordinator(hass, mock_config_entry) + account = mock_opower_api.async_get_accounts.return_value[0] + mock_opower_api.async_get_accounts.return_value = [account] + + statistic_id = "opower:pge_elec_111111_energy_consumption" + old = dt_util.as_utc(datetime(2015, 1, 1, 8)) + + async def only_bills(acc, aggregate_type, start, end): + if aggregate_type is not AggregateType.BILL: + return [] + return [ + CostRead( + start_time=old + timedelta(days=31 * month), + end_time=old + timedelta(days=31 * (month + 1)), + consumption=100.0, + provided_cost=20.0, + ) + for month in range(3) + ] + + mock_opower_api.async_get_cost_reads.side_effect = only_bills + await coordinator._async_update_data() + await async_wait_recording_done(hass) + + stats = await hass.async_add_executor_job( + statistics_during_period, + hass, + old, + None, + {statistic_id}, + "hour", + None, + {"start", "state", "sum"}, + ) + # The first read starts the series, so it is stored along with the rest. + assert len(stats[statistic_id]) == 3 + assert stats[statistic_id][-1]["sum"] == pytest.approx(300.0) + + +async def test_coordinator_uses_hourly_reads_when_daily_are_empty( + recorder_mock: Recorder, + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_opower_api: AsyncMock, +) -> None: + """Test that an empty daily response still lets the hourly reads through. + + Daily and hourly reads come from the same endpoint at different aggregations, + so one can fail while the other succeeds. Skipping on the empty daily + response would silently drop hourly statistics for as long as that lasts. + Only the bill reads have to be dropped, since they are the ones that would + overwrite finer statistics. + """ + coordinator = OpowerCoordinator(hass, mock_config_entry) + account = mock_opower_api.async_get_accounts.return_value[0] + mock_opower_api.async_get_accounts.return_value = [account] + + statistic_id = "opower:pge_elec_111111_energy_consumption" + day1 = dt_util.as_utc(datetime(2023, 1, 1, 8)) + day2 = dt_util.as_utc(datetime(2023, 1, 2, 8)) + + def bill_reads() -> list[CostRead]: + return [ + CostRead( + start_time=day1, + end_time=dt_util.as_utc(datetime(2023, 2, 1, 8)), + consumption=60.0, + provided_cost=12.0, + ) + ] + + def hourly_reads(days: tuple[datetime, ...]) -> list[CostRead]: + return [ + CostRead( + start_time=day + timedelta(hours=hour), + end_time=day + timedelta(hours=hour + 1), + consumption=1.5, + provided_cost=0.3, + ) + for day in days + for hour in range(3) + ] + + async def all_resolutions(acc, aggregate_type, start, end): + return { + AggregateType.BILL: bill_reads(), + AggregateType.DAY: [ + CostRead( + start_time=day1, end_time=day2, consumption=30.0, provided_cost=6.0 + ) + ], + AggregateType.HOUR: hourly_reads((day1,)), + }[aggregate_type] + + mock_opower_api.async_get_cost_reads.side_effect = all_resolutions + await coordinator._async_update_data() + await async_wait_recording_done(hass) + + # The daily endpoint fails while the hourly one returns a new day of reads. + async def no_daily(acc, aggregate_type, start, end): + return { + AggregateType.BILL: bill_reads(), + AggregateType.DAY: [], + AggregateType.HOUR: hourly_reads((day1, day2)), + }[aggregate_type] + + mock_opower_api.async_get_cost_reads.side_effect = no_daily + await coordinator._async_update_data() + await async_wait_recording_done(hass) + + stats = await hass.async_add_executor_job( + statistics_during_period, + hass, + day1, + None, + {statistic_id}, + "hour", + None, + {"start", "state", "sum"}, + ) + rows = stats[statistic_id] + # The second day's hourly reads were stored rather than skipped. + assert len(rows) == 6 + assert rows[-1]["sum"] == pytest.approx(9.0) + for previous, current in pairwise(rows): + assert current["sum"] == pytest.approx(previous["sum"] + current["state"]) diff --git a/tests/components/tts/fixtures/id3v1.mp3 b/tests/components/tts/fixtures/id3v1.mp3 new file mode 100644 index 00000000000000..441cb3223f3381 Binary files /dev/null and b/tests/components/tts/fixtures/id3v1.mp3 differ diff --git a/tests/components/tts/fixtures/untagged.mp3 b/tests/components/tts/fixtures/untagged.mp3 new file mode 100644 index 00000000000000..69f66c2b834a0e Binary files /dev/null and b/tests/components/tts/fixtures/untagged.mp3 differ diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index e6402ce2e7866f..3382c63f69b0e0 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -11,6 +11,8 @@ import wave from freezegun.api import FrozenDateTimeFactory +import mutagen +from mutagen.id3 import TIT2, TPE1, Encoding import pytest from homeassistant.components import ffmpeg, tts @@ -1918,6 +1920,8 @@ async def test_async_convert_audio_probe_size( "1", "-sample_fmt", "s16", + "-fflags", + "+bitexact", "pipe:1", ] @@ -1966,6 +1970,8 @@ async def test_async_convert_audio_mp3_bitrate( "-ac", "1", *expected_encoder_args, + "-fflags", + "+bitexact", "pipe:1", ] @@ -2327,3 +2333,111 @@ def test_write_tags_keeps_single_id3_tag() -> None: assert tagged.startswith(b"ID3") assert tagged.count(b"ID3") == 1 + + +def test_write_tags_sets_standard_id3_frames() -> None: + """Test tagging audio carrying only the encoder frame sets standard frames.""" + data = load_fixture_bytes("tagged.mp3", DOMAIN) + assert list(mutagen.File(io.BytesIO(data)).tags) == ["TSSE"] + + tagged = ORIG_WRITE_TAGS( + "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3", + data, + "Test", + "There is someone at the door.", + "en", + None, + ) + + tags = mutagen.File(io.BytesIO(tagged)).tags + assert tags["TPE1"].text == ["en"] + assert tags["TALB"].text == ["Test"] + assert tags["TIT2"].text == ["There is someone at the door."] + assert "TSSE" in tags + + +def test_write_tags_adds_tag_to_untagged_audio() -> None: + """Test audio arriving without an ID3 tag gets one holding the frames.""" + data = load_fixture_bytes("untagged.mp3", DOMAIN) + assert not data.startswith(b"ID3") + + tagged = ORIG_WRITE_TAGS( + "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3", + data, + "Test", + "There is someone at the door.", + "en", + None, + ) + + assert tagged.startswith(b"ID3") + assert tagged.count(b"ID3") == 1 + tags = mutagen.File(io.BytesIO(tagged)).tags + assert tags["TPE1"].text == ["en"] + assert tags["TALB"].text == ["Test"] + assert tags["TIT2"].text == ["There is someone at the door."] + + +def test_write_tags_overwrites_id3v1_metadata() -> None: + """Test audio arriving with only an ID3v1 trailer gets the frames rewritten.""" + data = load_fixture_bytes("id3v1.mp3", DOMAIN) + assert not data.startswith(b"ID3") + assert data[-128:-125] == b"TAG" + + tagged = ORIG_WRITE_TAGS( + "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3", + data, + "Test", + "There is someone at the door.", + "en", + None, + ) + + assert tagged.startswith(b"ID3") + tags = mutagen.File(io.BytesIO(tagged)).tags + assert tags["TPE1"].text == ["en"] + assert tags["TALB"].text == ["Test"] + assert tags["TIT2"].text == ["There is someone at the door."] + + +def test_write_tags_replaces_existing_frames() -> None: + """Test frames carried through conversion from the provider are replaced.""" + data = load_fixture_bytes("untagged.mp3", DOMAIN) + source = io.BytesIO(data) + source.name = "source.mp3" + source_file = mutagen.File(source) + source_file.add_tags() + source_file.tags.add(TIT2(encoding=Encoding.UTF8, text="Provider title")) + source_file.tags.add(TPE1(encoding=Encoding.UTF8, text="Provider artist")) + source.seek(0) + source_file.save(source) + + tagged = ORIG_WRITE_TAGS( + "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3", + source.getvalue(), + "Test", + "There is someone at the door.", + "en", + None, + ) + + tags = mutagen.File(io.BytesIO(tagged)).tags + assert tags["TIT2"].text == ["There is someone at the door."] + assert tags["TPE1"].text == ["en"] + + +def test_write_tags_uses_voice_as_artist() -> None: + """Test the voice option replaces the language as the artist frame.""" + data = load_fixture_bytes("tagged.mp3", DOMAIN) + + tagged = ORIG_WRITE_TAGS( + "42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3", + data, + "Test", + "There is someone at the door.", + "en", + {"voice": "JennyNeural"}, + ) + + tags = mutagen.File(io.BytesIO(tagged)).tags + assert tags["TPE1"].text == ["JennyNeural"] diff --git a/tests/components/xbox/snapshots/test_image.ambr b/tests/components/xbox/snapshots/test_image.ambr index 29f3f7214b5a1d..6458dee71865c8 100644 --- a/tests/components/xbox/snapshots/test_image.ambr +++ b/tests/components/xbox/snapshots/test_image.ambr @@ -51,6 +51,162 @@ 'state': '2013-12-13T12:13:12+00:00', }) # --- +# name: test_image_platform[image.erics273_box_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.erics273_box_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Box art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Box art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274913657542_box_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.erics273_box_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.erics273_box_art?token=520', + : 'erics273 Box art', + }), + 'context': , + 'entity_id': 'image.erics273_box_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.erics273_branded_key_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.erics273_branded_key_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Branded key art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Branded key art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274913657542_branded_key_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.erics273_branded_key_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.erics273_branded_key_art?token=520', + : 'erics273 Branded key art', + }), + 'context': , + 'entity_id': 'image.erics273_branded_key_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.erics273_feature_promotional_square_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.erics273_feature_promotional_square_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Feature promotional square art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Feature promotional square art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274913657542_feature_promotional_square_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.erics273_feature_promotional_square_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.erics273_feature_promotional_square_art?token=520', + : 'erics273 Feature promotional square art', + }), + 'context': , + 'entity_id': 'image.erics273_feature_promotional_square_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- # name: test_image_platform[image.erics273_gamerpic-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -65,7 +221,579 @@ 'disabled_by': None, 'domain': 'image', 'entity_category': None, - 'entity_id': 'image.erics273_gamerpic', + 'entity_id': 'image.erics273_gamerpic', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Gamerpic', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Gamerpic', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274913657542_gamerpic', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.erics273_gamerpic-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.erics273_gamerpic?token=520', + : 'erics273 Gamerpic', + }), + 'context': , + 'entity_id': 'image.erics273_gamerpic', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.erics273_now_playing-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.erics273_now_playing', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Now playing', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Now playing', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274913657542_now_playing', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.erics273_now_playing-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.erics273_now_playing?token=520', + : 'erics273 Now playing', + }), + 'context': , + 'entity_id': 'image.erics273_now_playing', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.erics273_poster-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.erics273_poster', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Poster', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Poster', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274913657542_poster', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.erics273_poster-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.erics273_poster?token=520', + : 'erics273 Poster', + }), + 'context': , + 'entity_id': 'image.erics273_poster', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.erics273_superhero_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.erics273_superhero_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Superhero art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Superhero art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274913657542_super_hero_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.erics273_superhero_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.erics273_superhero_art?token=520', + : 'erics273 Superhero art', + }), + 'context': , + 'entity_id': 'image.erics273_superhero_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.erics273_titled_hero_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.erics273_titled_hero_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Titled hero art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Titled hero art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274913657542_titled_hero_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.erics273_titled_hero_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.erics273_titled_hero_art?token=520', + : 'erics273 Titled hero art', + }), + 'context': , + 'entity_id': 'image.erics273_titled_hero_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.gsr_ae_avatar-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.gsr_ae_avatar', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Avatar', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Avatar', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '271958441785640_avatar', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.gsr_ae_avatar-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.gsr_ae_avatar?token=520', + : 'GSR Ae Avatar', + }), + 'context': , + 'entity_id': 'image.gsr_ae_avatar', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.gsr_ae_box_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.gsr_ae_box_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Box art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Box art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '271958441785640_box_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.gsr_ae_box_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.gsr_ae_box_art?token=520', + : 'GSR Ae Box art', + }), + 'context': , + 'entity_id': 'image.gsr_ae_box_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.gsr_ae_branded_key_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.gsr_ae_branded_key_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Branded key art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Branded key art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '271958441785640_branded_key_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.gsr_ae_branded_key_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.gsr_ae_branded_key_art?token=520', + : 'GSR Ae Branded key art', + }), + 'context': , + 'entity_id': 'image.gsr_ae_branded_key_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.gsr_ae_feature_promotional_square_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.gsr_ae_feature_promotional_square_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Feature promotional square art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Feature promotional square art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '271958441785640_feature_promotional_square_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.gsr_ae_feature_promotional_square_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.gsr_ae_feature_promotional_square_art?token=520', + : 'GSR Ae Feature promotional square art', + }), + 'context': , + 'entity_id': 'image.gsr_ae_feature_promotional_square_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.gsr_ae_gamerpic-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.gsr_ae_gamerpic', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Gamerpic', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Gamerpic', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '271958441785640_gamerpic', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.gsr_ae_gamerpic-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.gsr_ae_gamerpic?token=520', + : 'GSR Ae Gamerpic', + }), + 'context': , + 'entity_id': 'image.gsr_ae_gamerpic', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.gsr_ae_now_playing-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.gsr_ae_now_playing', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Now playing', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Now playing', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '271958441785640_now_playing', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.gsr_ae_now_playing-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.gsr_ae_now_playing?token=520', + : 'GSR Ae Now playing', + }), + 'context': , + 'entity_id': 'image.gsr_ae_now_playing', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.gsr_ae_poster-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.gsr_ae_poster', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -73,37 +801,37 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Gamerpic', + 'object_id_base': 'Poster', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Gamerpic', + 'original_name': 'Poster', 'platform': 'xbox', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': , - 'unique_id': '2533274913657542_gamerpic', + 'translation_key': , + 'unique_id': '271958441785640_poster', 'unit_of_measurement': None, }) # --- -# name: test_image_platform[image.erics273_gamerpic-state] +# name: test_image_platform[image.gsr_ae_poster-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : '520', - : '/api/image_proxy/image.erics273_gamerpic?token=520', - : 'erics273 Gamerpic', + : '/api/image_proxy/image.gsr_ae_poster?token=520', + : 'GSR Ae Poster', }), 'context': , - 'entity_id': 'image.erics273_gamerpic', + 'entity_id': 'image.gsr_ae_poster', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2013-12-13T12:13:12+00:00', }) # --- -# name: test_image_platform[image.erics273_now_playing-entry] +# name: test_image_platform[image.gsr_ae_superhero_art-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -117,7 +845,7 @@ 'disabled_by': None, 'domain': 'image', 'entity_category': None, - 'entity_id': 'image.erics273_now_playing', + 'entity_id': 'image.gsr_ae_superhero_art', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -125,37 +853,37 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Now playing', + 'object_id_base': 'Superhero art', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Now playing', + 'original_name': 'Superhero art', 'platform': 'xbox', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': , - 'unique_id': '2533274913657542_now_playing', + 'translation_key': , + 'unique_id': '271958441785640_super_hero_art', 'unit_of_measurement': None, }) # --- -# name: test_image_platform[image.erics273_now_playing-state] +# name: test_image_platform[image.gsr_ae_superhero_art-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : '520', - : '/api/image_proxy/image.erics273_now_playing?token=520', - : 'erics273 Now playing', + : '/api/image_proxy/image.gsr_ae_superhero_art?token=520', + : 'GSR Ae Superhero art', }), 'context': , - 'entity_id': 'image.erics273_now_playing', + 'entity_id': 'image.gsr_ae_superhero_art', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2013-12-13T12:13:12+00:00', }) # --- -# name: test_image_platform[image.gsr_ae_avatar-entry] +# name: test_image_platform[image.gsr_ae_titled_hero_art-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -169,7 +897,59 @@ 'disabled_by': None, 'domain': 'image', 'entity_category': None, - 'entity_id': 'image.gsr_ae_avatar', + 'entity_id': 'image.gsr_ae_titled_hero_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Titled hero art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Titled hero art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '271958441785640_titled_hero_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.gsr_ae_titled_hero_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.gsr_ae_titled_hero_art?token=520', + : 'GSR Ae Titled hero art', + }), + 'context': , + 'entity_id': 'image.gsr_ae_titled_hero_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.ikken_hissatsuu_avatar-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.ikken_hissatsuu_avatar', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -188,26 +968,26 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': , - 'unique_id': '271958441785640_avatar', + 'unique_id': '2533274838782903_avatar', 'unit_of_measurement': None, }) # --- -# name: test_image_platform[image.gsr_ae_avatar-state] +# name: test_image_platform[image.ikken_hissatsuu_avatar-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : '520', - : '/api/image_proxy/image.gsr_ae_avatar?token=520', - : 'GSR Ae Avatar', + : '/api/image_proxy/image.ikken_hissatsuu_avatar?token=520', + : 'Ikken Hissatsuu Avatar', }), 'context': , - 'entity_id': 'image.gsr_ae_avatar', + 'entity_id': 'image.ikken_hissatsuu_avatar', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2013-12-13T12:13:12+00:00', }) # --- -# name: test_image_platform[image.gsr_ae_gamerpic-entry] +# name: test_image_platform[image.ikken_hissatsuu_box_art-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -221,7 +1001,7 @@ 'disabled_by': None, 'domain': 'image', 'entity_category': None, - 'entity_id': 'image.gsr_ae_gamerpic', + 'entity_id': 'image.ikken_hissatsuu_box_art', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -229,37 +1009,37 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Gamerpic', + 'object_id_base': 'Box art', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Gamerpic', + 'original_name': 'Box art', 'platform': 'xbox', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': , - 'unique_id': '271958441785640_gamerpic', + 'translation_key': , + 'unique_id': '2533274838782903_box_art', 'unit_of_measurement': None, }) # --- -# name: test_image_platform[image.gsr_ae_gamerpic-state] +# name: test_image_platform[image.ikken_hissatsuu_box_art-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : '520', - : '/api/image_proxy/image.gsr_ae_gamerpic?token=520', - : 'GSR Ae Gamerpic', + : '/api/image_proxy/image.ikken_hissatsuu_box_art?token=520', + : 'Ikken Hissatsuu Box art', }), 'context': , - 'entity_id': 'image.gsr_ae_gamerpic', + 'entity_id': 'image.ikken_hissatsuu_box_art', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2013-12-13T12:13:12+00:00', }) # --- -# name: test_image_platform[image.gsr_ae_now_playing-entry] +# name: test_image_platform[image.ikken_hissatsuu_branded_key_art-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -273,7 +1053,7 @@ 'disabled_by': None, 'domain': 'image', 'entity_category': None, - 'entity_id': 'image.gsr_ae_now_playing', + 'entity_id': 'image.ikken_hissatsuu_branded_key_art', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -281,37 +1061,37 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Now playing', + 'object_id_base': 'Branded key art', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Now playing', + 'original_name': 'Branded key art', 'platform': 'xbox', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': , - 'unique_id': '271958441785640_now_playing', + 'translation_key': , + 'unique_id': '2533274838782903_branded_key_art', 'unit_of_measurement': None, }) # --- -# name: test_image_platform[image.gsr_ae_now_playing-state] +# name: test_image_platform[image.ikken_hissatsuu_branded_key_art-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : '520', - : '/api/image_proxy/image.gsr_ae_now_playing?token=520', - : 'GSR Ae Now playing', + : '/api/image_proxy/image.ikken_hissatsuu_branded_key_art?token=520', + : 'Ikken Hissatsuu Branded key art', }), 'context': , - 'entity_id': 'image.gsr_ae_now_playing', + 'entity_id': 'image.ikken_hissatsuu_branded_key_art', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2013-12-13T12:13:12+00:00', }) # --- -# name: test_image_platform[image.ikken_hissatsuu_avatar-entry] +# name: test_image_platform[image.ikken_hissatsuu_feature_promotional_square_art-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -325,7 +1105,7 @@ 'disabled_by': None, 'domain': 'image', 'entity_category': None, - 'entity_id': 'image.ikken_hissatsuu_avatar', + 'entity_id': 'image.ikken_hissatsuu_feature_promotional_square_art', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -333,30 +1113,30 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Avatar', + 'object_id_base': 'Feature promotional square art', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Avatar', + 'original_name': 'Feature promotional square art', 'platform': 'xbox', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': , - 'unique_id': '2533274838782903_avatar', + 'translation_key': , + 'unique_id': '2533274838782903_feature_promotional_square_art', 'unit_of_measurement': None, }) # --- -# name: test_image_platform[image.ikken_hissatsuu_avatar-state] +# name: test_image_platform[image.ikken_hissatsuu_feature_promotional_square_art-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : '520', - : '/api/image_proxy/image.ikken_hissatsuu_avatar?token=520', - : 'Ikken Hissatsuu Avatar', + : '/api/image_proxy/image.ikken_hissatsuu_feature_promotional_square_art?token=520', + : 'Ikken Hissatsuu Feature promotional square art', }), 'context': , - 'entity_id': 'image.ikken_hissatsuu_avatar', + 'entity_id': 'image.ikken_hissatsuu_feature_promotional_square_art', 'last_changed': , 'last_reported': , 'last_updated': , @@ -467,3 +1247,159 @@ 'state': '2013-12-13T12:13:12+00:00', }) # --- +# name: test_image_platform[image.ikken_hissatsuu_poster-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.ikken_hissatsuu_poster', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Poster', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Poster', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274838782903_poster', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.ikken_hissatsuu_poster-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.ikken_hissatsuu_poster?token=520', + : 'Ikken Hissatsuu Poster', + }), + 'context': , + 'entity_id': 'image.ikken_hissatsuu_poster', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.ikken_hissatsuu_superhero_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.ikken_hissatsuu_superhero_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Superhero art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Superhero art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274838782903_super_hero_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.ikken_hissatsuu_superhero_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.ikken_hissatsuu_superhero_art?token=520', + : 'Ikken Hissatsuu Superhero art', + }), + 'context': , + 'entity_id': 'image.ikken_hissatsuu_superhero_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# --- +# name: test_image_platform[image.ikken_hissatsuu_titled_hero_art-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'image', + 'entity_category': None, + 'entity_id': 'image.ikken_hissatsuu_titled_hero_art', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Titled hero art', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Titled hero art', + 'platform': 'xbox', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '2533274838782903_titled_hero_art', + 'unit_of_measurement': None, + }) +# --- +# name: test_image_platform[image.ikken_hissatsuu_titled_hero_art-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : '520', + : '/api/image_proxy/image.ikken_hissatsuu_titled_hero_art?token=520', + : 'Ikken Hissatsuu Titled hero art', + }), + 'context': , + 'entity_id': 'image.ikken_hissatsuu_titled_hero_art', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2013-12-13T12:13:12+00:00', + }) +# ---