diff --git a/homeassistant/components/harmony/data.py b/homeassistant/components/harmony/data.py index 75678391e7b..992eaf52326 100644 --- a/homeassistant/components/harmony/data.py +++ b/homeassistant/components/harmony/data.py @@ -29,7 +29,7 @@ class HarmonyData(HarmonySubscriberMixin): ) -> None: """Initialize a data object.""" super().__init__(hass) - self._name = name + self.name = name self._unique_id = unique_id self._available = False self._address = address @@ -60,11 +60,6 @@ class HarmonyData(HarmonySubscriberMixin): return devices - @property - def name(self) -> str: - """Return the Harmony device's name.""" - return self._name - @property def unique_id(self): """Return the Harmony device's unique_id.""" @@ -105,7 +100,7 @@ class HarmonyData(HarmonySubscriberMixin): async def connect(self) -> None: """Connect to the Harmony Hub.""" - _LOGGER.debug("%s: Connecting", self._name) + _LOGGER.debug("%s: Connecting", self.name) callbacks = { "config_updated": self._config_updated, @@ -124,27 +119,27 @@ class HarmonyData(HarmonySubscriberMixin): except (TimeoutError, aioexc.TimeOut) as err: await self._client.close() raise ConfigEntryNotReady( - f"{self._name}: Connection timed-out to {self._address}:8088" + f"{self.name}: Connection timed-out to {self._address}:8088" ) from err except (ValueError, AttributeError) as err: await self._client.close() raise ConfigEntryNotReady( - f"{self._name}: Error {err} while connected HUB at:" + f"{self.name}: Error {err} while connected HUB at:" f" {self._address}:8088" ) from err if not connected: await self._client.close() raise ConfigEntryNotReady( - f"{self._name}: Unable to connect to HUB at: {self._address}:8088" + f"{self.name}: Unable to connect to HUB at: {self._address}:8088" ) async def shutdown(self) -> None: """Close connection on shutdown.""" - _LOGGER.debug("%s: Closing Harmony Hub", self._name) + _LOGGER.debug("%s: Closing Harmony Hub", self.name) try: await self._client.close() except aioexc.TimeOut: - _LOGGER.warning("%s: Disconnect timed-out", self._name) + _LOGGER.warning("%s: Disconnect timed-out", self.name) async def async_start_activity(self, activity: str) -> None: """Start an activity from the Harmony device.""" diff --git a/homeassistant/components/harmony/entity.py b/homeassistant/components/harmony/entity.py index f87d30a0e40..99b5744e0ed 100644 --- a/homeassistant/components/harmony/entity.py +++ b/homeassistant/components/harmony/entity.py @@ -19,11 +19,12 @@ TIME_MARK_DISCONNECTED = 10 class HarmonyEntity(Entity): """Base entity for Harmony with connection state handling.""" + _attr_has_entity_name = True + def __init__(self, data: HarmonyData) -> None: """Initialize the Harmony base entity.""" super().__init__() self._unsub_mark_disconnected: Callable[[], None] | None = None - self._name = data.name self._data = data self._attr_should_poll = False @@ -34,14 +35,14 @@ class HarmonyEntity(Entity): async def async_got_connected(self, _: str | None = None) -> None: """Notification that we're connected to the HUB.""" - _LOGGER.debug("%s: connected to the HUB", self._name) + _LOGGER.debug("%s: connected to the HUB", self._data.name) self.async_write_ha_state() self._clear_disconnection_delay() async def async_got_disconnected(self, _: str | None = None) -> None: """Notification that we're disconnected from the HUB.""" - _LOGGER.debug("%s: disconnected from the HUB", self._name) + _LOGGER.debug("%s: disconnected from the HUB", self._data.name) # We're going to wait for 10 seconds before announcing we're # unavailable, this to allow a reconnection to happen. self._unsub_mark_disconnected = async_call_later( diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 37a6811a2b2..c6b2e9be718 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -87,6 +87,7 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): """Remote representation used to control a Harmony device.""" _attr_supported_features = RemoteEntityFeature.ACTIVITY + _attr_name = None def __init__( self, data: HarmonyData, activity: str | None, delay_secs: float, out_path: str @@ -103,7 +104,6 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): self._config_path = out_path self._attr_unique_id = data.unique_id self._attr_device_info = self._data.device_info(DOMAIN) - self._attr_name = data.name async def _async_update_options(self, data: dict[str, Any]) -> None: """Change options when the options flow does.""" @@ -136,7 +136,7 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): """Complete the initialization.""" await super().async_added_to_hass() - _LOGGER.debug("%s: Harmony Hub added", self.name) + _LOGGER.debug("%s: Harmony Hub added", self._data.name) self.async_on_remove(self._clear_disconnection_delay) self._setup_callbacks() @@ -193,7 +193,7 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): def async_new_activity(self, activity_info: tuple) -> None: """Call for updating the current activity.""" activity_id, activity_name = activity_info - _LOGGER.debug("%s: activity reported as: %s", self.name, activity_name) + _LOGGER.debug("%s: activity reported as: %s", self._data.name, activity_name) self._current_activity = activity_name if self._is_initial_update: self._is_initial_update = False @@ -209,13 +209,13 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): async def async_new_config(self, _: dict | None = None) -> None: """Call for updating the current activity.""" - _LOGGER.debug("%s: configuration has been updated", self.name) + _LOGGER.debug("%s: configuration has been updated", self._data.name) self.async_new_activity(self._data.current_activity) await self.hass.async_add_executor_job(self.write_config_file) async def async_turn_on(self, **kwargs: Any) -> None: """Start an activity from the Harmony device.""" - _LOGGER.debug("%s: Turn On", self.name) + _LOGGER.debug("%s: Turn On", self._data.name) activity = kwargs.get(ATTR_ACTIVITY, self.default_activity) @@ -228,7 +228,9 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): if activity: await self._data.async_start_activity(activity) else: - _LOGGER.error("%s: No activity specified with turn_on service", self.name) + _LOGGER.error( + "%s: No activity specified with turn_on service", self._data.name + ) async def async_turn_off(self, **kwargs: Any) -> None: """Start the PowerOff activity.""" @@ -236,9 +238,9 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send a list of commands to one device.""" - _LOGGER.debug("%s: Send Command", self.name) + _LOGGER.debug("%s: Send Command", self._data.name) if (device := kwargs.get(ATTR_DEVICE)) is None: - _LOGGER.error("%s: Missing required argument: device", self.name) + _LOGGER.error("%s: Missing required argument: device", self._data.name) return num_repeats = kwargs[ATTR_NUM_REPEATS] @@ -263,10 +265,12 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): This is a handy way for users to figure out the available commands for automations. """ _LOGGER.debug( - "%s: Writing hub configuration to file: %s", self.name, self._config_path + "%s: Writing hub configuration to file: %s", + self._data.name, + self._config_path, ) if (json_config := self._data.json_config) is None: - _LOGGER.warning("%s: No configuration received from hub", self.name) + _LOGGER.warning("%s: No configuration received from hub", self._data.name) return try: @@ -275,7 +279,7 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): except OSError as exc: _LOGGER.error( "%s: Unable to write HUB configuration to %s: %s", - self.name, + self._data.name, self._config_path, exc, ) diff --git a/homeassistant/components/harmony/select.py b/homeassistant/components/harmony/select.py index ca653e03ccc..0bb8f462419 100644 --- a/homeassistant/components/harmony/select.py +++ b/homeassistant/components/harmony/select.py @@ -6,7 +6,6 @@ import logging from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -25,10 +24,7 @@ async def async_setup_entry( ) -> None: """Set up harmony activities select.""" data: HarmonyData = hass.data[DOMAIN][entry.entry_id][HARMONY_DATA] - _LOGGER.debug("creating select for %s hub activities", entry.data[CONF_NAME]) - async_add_entities( - [HarmonyActivitySelect(f"{entry.data[CONF_NAME]} Activities", data)] - ) + async_add_entities([HarmonyActivitySelect(data)]) class HarmonyActivitySelect(HarmonyEntity, SelectEntity): @@ -36,13 +32,12 @@ class HarmonyActivitySelect(HarmonyEntity, SelectEntity): _attr_translation_key = "activities" - def __init__(self, name: str, data: HarmonyData) -> None: + def __init__(self, data: HarmonyData) -> None: """Initialize HarmonyActivitySelect class.""" super().__init__(data=data) self._data = data self._attr_unique_id = self._data.unique_id self._attr_device_info = self._data.device_info(DOMAIN) - self._attr_name = name @property def options(self) -> list[str]: diff --git a/homeassistant/components/harmony/strings.json b/homeassistant/components/harmony/strings.json index f6862ca3c83..444097395c9 100644 --- a/homeassistant/components/harmony/strings.json +++ b/homeassistant/components/harmony/strings.json @@ -39,6 +39,7 @@ "entity": { "select": { "activities": { + "name": "Activities", "state": { "power_off": "Power Off" } diff --git a/homeassistant/components/harmony/switch.py b/homeassistant/components/harmony/switch.py index df32af36ec5..0cb07e5cb1e 100644 --- a/homeassistant/components/harmony/switch.py +++ b/homeassistant/components/harmony/switch.py @@ -7,7 +7,6 @@ from homeassistant.components.automation import automations_with_entity from homeassistant.components.script import scripts_with_entity from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue @@ -25,28 +24,22 @@ async def async_setup_entry( ) -> None: """Set up harmony activity switches.""" data: HarmonyData = hass.data[DOMAIN][entry.entry_id][HARMONY_DATA] - activities = data.activities - switches = [] - for activity in activities: - _LOGGER.debug("creating switch for activity: %s", activity) - name = f"{entry.data[CONF_NAME]} {activity['label']}" - switches.append(HarmonyActivitySwitch(name, activity, data)) - - async_add_entities(switches, True) + async_add_entities( + (HarmonyActivitySwitch(activity, data) for activity in data.activities), True + ) class HarmonyActivitySwitch(HarmonyEntity, SwitchEntity): """Switch representation of a Harmony activity.""" - def __init__(self, name: str, activity: dict, data: HarmonyData) -> None: + def __init__(self, activity: dict, data: HarmonyData) -> None: """Initialize HarmonyActivitySwitch class.""" super().__init__(data=data) - self._activity_name = activity["label"] + self._activity_name = self._attr_name = activity["label"] self._activity_id = activity["id"] self._attr_entity_registry_enabled_default = False self._attr_unique_id = f"activity_{self._activity_id}" - self._attr_name = name self._attr_device_info = self._data.device_info(DOMAIN) @property diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index c7594848990..97449749667 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -5,9 +5,17 @@ from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch from aioharmony.const import ClientCallbackType import pytest -from homeassistant.components.harmony.const import ACTIVITY_POWER_OFF +from homeassistant.components.harmony.const import ACTIVITY_POWER_OFF, DOMAIN +from homeassistant.const import CONF_HOST, CONF_NAME -from .const import NILE_TV_ACTIVITY_ID, PLAY_MUSIC_ACTIVITY_ID, WATCH_TV_ACTIVITY_ID +from .const import ( + HUB_NAME, + NILE_TV_ACTIVITY_ID, + PLAY_MUSIC_ACTIVITY_ID, + WATCH_TV_ACTIVITY_ID, +) + +from tests.common import MockConfigEntry ACTIVITIES_TO_IDS = { ACTIVITY_POWER_OFF: -1, @@ -166,3 +174,13 @@ def mock_write_config(): "homeassistant.components.harmony.remote.HarmonyRemote.write_config_file", ) as mock: yield mock + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + unique_id="123", + data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME}, + ) diff --git a/tests/components/harmony/test_remote.py b/tests/components/harmony/test_remote.py index 14b8bdd63cf..c0ec2235b84 100644 --- a/tests/components/harmony/test_remote.py +++ b/tests/components/harmony/test_remote.py @@ -43,15 +43,16 @@ STOP_COMMAND = "Stop" async def test_connection_state_changes( - harmony_client, mock_hc, hass: HomeAssistant, mock_write_config + harmony_client, + mock_hc, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure connection changes are reflected in the remote state.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start with current activity == Watch TV @@ -82,14 +83,13 @@ async def test_connection_state_changes( assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) -async def test_remote_toggles(mock_hc, hass: HomeAssistant, mock_write_config) -> None: +async def test_remote_toggles( + mock_hc, hass: HomeAssistant, mock_write_config, mock_config_entry: MockConfigEntry +) -> None: """Ensure calls to the remote also updates the switches.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start remote with Watch TV default activity @@ -151,15 +151,16 @@ async def test_remote_toggles(mock_hc, hass: HomeAssistant, mock_write_config) - async def test_async_send_command( - mock_hc, harmony_client, hass: HomeAssistant, mock_write_config + mock_hc, + harmony_client, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure calls to send remote commands properly propagate to devices.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() send_commands_mock = harmony_client.send_commands @@ -285,11 +286,16 @@ async def test_async_send_command( async def test_async_send_command_custom_delay( - mock_hc, harmony_client, hass: HomeAssistant, mock_write_config + mock_hc, + harmony_client, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure calls to send remote commands properly propagate to devices with custom delays.""" entry = MockConfigEntry( domain=DOMAIN, + unique_id="123", data={ CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME, @@ -327,15 +333,16 @@ async def test_async_send_command_custom_delay( async def test_change_channel( - mock_hc, harmony_client, hass: HomeAssistant, mock_write_config + mock_hc, + harmony_client, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Test change channel commands.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() change_channel_mock = harmony_client.change_channel @@ -353,15 +360,16 @@ async def test_change_channel( async def test_sync( - mock_hc, harmony_client, mock_write_config, hass: HomeAssistant + mock_hc, + harmony_client, + mock_write_config, + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, ) -> None: """Test the sync command.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() sync_mock = harmony_client.sync diff --git a/tests/components/harmony/test_select.py b/tests/components/harmony/test_select.py index da699be5372..2568feb1412 100644 --- a/tests/components/harmony/test_select.py +++ b/tests/components/harmony/test_select.py @@ -2,38 +2,31 @@ from datetime import timedelta -from homeassistant.components.harmony.const import DOMAIN from homeassistant.components.select import ( ATTR_OPTION, DOMAIN as SELECT_DOMAIN, SERVICE_SELECT_OPTION, ) -from homeassistant.const import ( - ATTR_ENTITY_ID, - CONF_HOST, - CONF_NAME, - STATE_OFF, - STATE_ON, - STATE_UNAVAILABLE, -) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.util import utcnow -from .const import ENTITY_REMOTE, ENTITY_SELECT, HUB_NAME +from .const import ENTITY_REMOTE, ENTITY_SELECT from tests.common import MockConfigEntry, async_fire_time_changed async def test_connection_state_changes( - harmony_client, mock_hc, hass: HomeAssistant, mock_write_config + harmony_client, + mock_hc, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure connection changes are reflected in the switch states.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start with current activity == Watch TV @@ -56,14 +49,13 @@ async def test_connection_state_changes( assert hass.states.is_state(ENTITY_SELECT, "Watch TV") -async def test_options(mock_hc, hass: HomeAssistant, mock_write_config) -> None: +async def test_options( + mock_hc, hass: HomeAssistant, mock_write_config, mock_config_entry: MockConfigEntry +) -> None: """Ensure calls to the switch modify the harmony state.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # assert we have all options @@ -76,14 +68,12 @@ async def test_options(mock_hc, hass: HomeAssistant, mock_write_config) -> None: ] -async def test_select_option(mock_hc, hass: HomeAssistant, mock_write_config) -> None: +async def test_select_option( + mock_hc, hass: HomeAssistant, mock_write_config, mock_config_entry: MockConfigEntry +) -> None: """Ensure calls to the switch modify the harmony state.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start with current activity == Watch TV diff --git a/tests/components/harmony/test_switch.py b/tests/components/harmony/test_switch.py index b5603938972..01f9287ae57 100644 --- a/tests/components/harmony/test_switch.py +++ b/tests/components/harmony/test_switch.py @@ -90,21 +90,22 @@ async def test_connection_state_changes( async def test_switch_toggles( - mock_hc, hass: HomeAssistant, mock_write_config, entity_registry: er.EntityRegistry + mock_hc, + hass: HomeAssistant, + mock_write_config, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure calls to the switch modify the harmony state.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # enable switch entities entity_registry.async_update_entity(ENTITY_WATCH_TV, disabled_by=None) entity_registry.async_update_entity(ENTITY_PLAY_MUSIC, disabled_by=None) - await hass.config_entries.async_reload(entry.entry_id) + await hass.config_entries.async_reload(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start with current activity == Watch TV