mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add select entity to Logitech Harmony (#53943)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
778fa2e3fe
commit
979797136a
@ -202,7 +202,7 @@ homeassistant/components/group/* @home-assistant/core
|
|||||||
homeassistant/components/growatt_server/* @indykoning @muppet3000 @JasperPlant
|
homeassistant/components/growatt_server/* @indykoning @muppet3000 @JasperPlant
|
||||||
homeassistant/components/guardian/* @bachya
|
homeassistant/components/guardian/* @bachya
|
||||||
homeassistant/components/habitica/* @ASMfreaK @leikoilja
|
homeassistant/components/habitica/* @ASMfreaK @leikoilja
|
||||||
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey
|
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan
|
||||||
homeassistant/components/hassio/* @home-assistant/supervisor
|
homeassistant/components/hassio/* @home-assistant/supervisor
|
||||||
homeassistant/components/heatmiser/* @andylockran
|
homeassistant/components/heatmiser/* @andylockran
|
||||||
homeassistant/components/heos/* @andrewsayre
|
homeassistant/components/heos/* @andrewsayre
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
DOMAIN = "harmony"
|
DOMAIN = "harmony"
|
||||||
SERVICE_SYNC = "sync"
|
SERVICE_SYNC = "sync"
|
||||||
SERVICE_CHANGE_CHANNEL = "change_channel"
|
SERVICE_CHANGE_CHANNEL = "change_channel"
|
||||||
PLATFORMS = ["remote", "switch"]
|
PLATFORMS = ["remote", "switch", "select"]
|
||||||
UNIQUE_ID = "unique_id"
|
UNIQUE_ID = "unique_id"
|
||||||
ACTIVITY_POWER_OFF = "PowerOff"
|
ACTIVITY_POWER_OFF = "PowerOff"
|
||||||
HARMONY_OPTIONS_UPDATE = "harmony_options_update"
|
HARMONY_OPTIONS_UPDATE = "harmony_options_update"
|
||||||
|
@ -1,20 +1,31 @@
|
|||||||
"""Mixin class for handling connection state changes."""
|
"""Base class Harmony entities."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
|
||||||
|
from .data import HarmonyData
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
TIME_MARK_DISCONNECTED = 10
|
TIME_MARK_DISCONNECTED = 10
|
||||||
|
|
||||||
|
|
||||||
class ConnectionStateMixin:
|
class HarmonyEntity(Entity):
|
||||||
"""Base implementation for connection state handling."""
|
"""Base entity for Harmony with connection state handling."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, data: HarmonyData) -> None:
|
||||||
"""Initialize this mixin instance."""
|
"""Initialize the Harmony base entity."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._unsub_mark_disconnected = None
|
self._unsub_mark_disconnected = None
|
||||||
|
self._name = data.name
|
||||||
|
self._data = data
|
||||||
|
self._attr_should_poll = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return True if we're connected to the Hub, otherwise False."""
|
||||||
|
return self._data.available
|
||||||
|
|
||||||
async def async_got_connected(self, _=None):
|
async def async_got_connected(self, _=None):
|
||||||
"""Notification that we're connected to the HUB."""
|
"""Notification that we're connected to the HUB."""
|
@ -3,7 +3,13 @@
|
|||||||
"name": "Logitech Harmony Hub",
|
"name": "Logitech Harmony Hub",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/harmony",
|
"documentation": "https://www.home-assistant.io/integrations/harmony",
|
||||||
"requirements": ["aioharmony==0.2.7"],
|
"requirements": ["aioharmony==0.2.7"],
|
||||||
"codeowners": ["@ehendrix23", "@bramkragten", "@bdraco", "@mkeesey"],
|
"codeowners": [
|
||||||
|
"@ehendrix23",
|
||||||
|
"@bramkragten",
|
||||||
|
"@bdraco",
|
||||||
|
"@mkeesey",
|
||||||
|
"@Aohzan"
|
||||||
|
],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Logitech",
|
"manufacturer": "Logitech",
|
||||||
|
@ -21,7 +21,6 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
|
|
||||||
from .connection_state import ConnectionStateMixin
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ACTIVITY_POWER_OFF,
|
ACTIVITY_POWER_OFF,
|
||||||
ATTR_ACTIVITY_STARTING,
|
ATTR_ACTIVITY_STARTING,
|
||||||
@ -34,6 +33,7 @@ from .const import (
|
|||||||
SERVICE_CHANGE_CHANNEL,
|
SERVICE_CHANGE_CHANNEL,
|
||||||
SERVICE_SYNC,
|
SERVICE_SYNC,
|
||||||
)
|
)
|
||||||
|
from .entity import HarmonyEntity
|
||||||
from .subscriber import HarmonyCallback
|
from .subscriber import HarmonyCallback
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -76,28 +76,24 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class HarmonyRemote(ConnectionStateMixin, remote.RemoteEntity, RestoreEntity):
|
class HarmonyRemote(HarmonyEntity, remote.RemoteEntity, RestoreEntity):
|
||||||
"""Remote representation used to control a Harmony device."""
|
"""Remote representation used to control a Harmony device."""
|
||||||
|
|
||||||
def __init__(self, data, activity, delay_secs, out_path):
|
def __init__(self, data, activity, delay_secs, out_path):
|
||||||
"""Initialize HarmonyRemote class."""
|
"""Initialize HarmonyRemote class."""
|
||||||
super().__init__()
|
super().__init__(data=data)
|
||||||
self._data = data
|
|
||||||
self._name = data.name
|
|
||||||
self._state = None
|
self._state = None
|
||||||
self._current_activity = ACTIVITY_POWER_OFF
|
self._current_activity = ACTIVITY_POWER_OFF
|
||||||
self.default_activity = activity
|
self.default_activity = activity
|
||||||
self._activity_starting = None
|
self._activity_starting = None
|
||||||
self._is_initial_update = True
|
self._is_initial_update = True
|
||||||
self.delay_secs = delay_secs
|
self.delay_secs = delay_secs
|
||||||
self._unique_id = data.unique_id
|
|
||||||
self._last_activity = None
|
self._last_activity = None
|
||||||
self._config_path = out_path
|
self._config_path = out_path
|
||||||
|
self._attr_unique_id = data.unique_id
|
||||||
@property
|
self._attr_device_info = self._data.device_info(DOMAIN)
|
||||||
def supported_features(self):
|
self._attr_name = data.name
|
||||||
"""Supported features for the remote."""
|
self._attr_supported_features = SUPPORT_ACTIVITY
|
||||||
return SUPPORT_ACTIVITY
|
|
||||||
|
|
||||||
async def _async_update_options(self, data):
|
async def _async_update_options(self, data):
|
||||||
"""Change options when the options flow does."""
|
"""Change options when the options flow does."""
|
||||||
@ -128,7 +124,7 @@ class HarmonyRemote(ConnectionStateMixin, remote.RemoteEntity, RestoreEntity):
|
|||||||
"""Complete the initialization."""
|
"""Complete the initialization."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
_LOGGER.debug("%s: Harmony Hub added", self._name)
|
_LOGGER.debug("%s: Harmony Hub added", self.name)
|
||||||
|
|
||||||
self.async_on_remove(self._clear_disconnection_delay)
|
self.async_on_remove(self._clear_disconnection_delay)
|
||||||
self._setup_callbacks()
|
self._setup_callbacks()
|
||||||
@ -158,26 +154,6 @@ class HarmonyRemote(ConnectionStateMixin, remote.RemoteEntity, RestoreEntity):
|
|||||||
|
|
||||||
self._last_activity = last_state.attributes[ATTR_LAST_ACTIVITY]
|
self._last_activity = last_state.attributes[ATTR_LAST_ACTIVITY]
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self):
|
|
||||||
"""Return device info."""
|
|
||||||
return self._data.device_info(DOMAIN)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the unique id."""
|
|
||||||
return self._unique_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the Harmony device's name."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""Return the fact that we should not be polled."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_activity(self):
|
def current_activity(self):
|
||||||
"""Return the current activity."""
|
"""Return the current activity."""
|
||||||
@ -202,16 +178,11 @@ class HarmonyRemote(ConnectionStateMixin, remote.RemoteEntity, RestoreEntity):
|
|||||||
"""Return False if PowerOff is the current activity, otherwise True."""
|
"""Return False if PowerOff is the current activity, otherwise True."""
|
||||||
return self._current_activity not in [None, "PowerOff"]
|
return self._current_activity not in [None, "PowerOff"]
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""Return True if connected to Hub, otherwise False."""
|
|
||||||
return self._data.available
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_new_activity(self, activity_info: tuple) -> None:
|
def async_new_activity(self, activity_info: tuple) -> None:
|
||||||
"""Call for updating the current activity."""
|
"""Call for updating the current activity."""
|
||||||
activity_id, activity_name = activity_info
|
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.name, activity_name)
|
||||||
self._current_activity = activity_name
|
self._current_activity = activity_name
|
||||||
if self._is_initial_update:
|
if self._is_initial_update:
|
||||||
self._is_initial_update = False
|
self._is_initial_update = False
|
||||||
@ -227,7 +198,7 @@ class HarmonyRemote(ConnectionStateMixin, remote.RemoteEntity, RestoreEntity):
|
|||||||
|
|
||||||
async def async_new_config(self, _=None):
|
async def async_new_config(self, _=None):
|
||||||
"""Call for updating the current activity."""
|
"""Call for updating the current activity."""
|
||||||
_LOGGER.debug("%s: configuration has been updated", self._name)
|
_LOGGER.debug("%s: configuration has been updated", self.name)
|
||||||
self.async_new_activity(self._data.current_activity)
|
self.async_new_activity(self._data.current_activity)
|
||||||
await self.hass.async_add_executor_job(self.write_config_file)
|
await self.hass.async_add_executor_job(self.write_config_file)
|
||||||
|
|
||||||
|
75
homeassistant/components/harmony/select.py
Normal file
75
homeassistant/components/harmony/select.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
"""Support for Harmony Hub select activities."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.select import SelectEntity
|
||||||
|
from homeassistant.const import CONF_NAME
|
||||||
|
from homeassistant.core import callback
|
||||||
|
|
||||||
|
from .const import ACTIVITY_POWER_OFF, DOMAIN, HARMONY_DATA
|
||||||
|
from .data import HarmonyData
|
||||||
|
from .entity import HarmonyEntity
|
||||||
|
from .subscriber import HarmonyCallback
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
|
"""Set up harmony activities select."""
|
||||||
|
data = 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)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HarmonyActivitySelect(HarmonyEntity, SelectEntity):
|
||||||
|
"""Select representation of a Harmony activities."""
|
||||||
|
|
||||||
|
def __init__(self, name: str, 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 icon(self):
|
||||||
|
"""Return a representative icon."""
|
||||||
|
if not self.available or self.current_option == ACTIVITY_POWER_OFF:
|
||||||
|
return "mdi:remote-tv-off"
|
||||||
|
return "mdi:remote-tv"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self) -> list[str]:
|
||||||
|
"""Return a set of selectable options."""
|
||||||
|
return [ACTIVITY_POWER_OFF] + sorted(self._data.activity_names)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_option(self):
|
||||||
|
"""Return the current activity."""
|
||||||
|
_, activity_name = self._data.current_activity
|
||||||
|
return activity_name
|
||||||
|
|
||||||
|
async def async_select_option(self, option: str) -> None:
|
||||||
|
"""Change the current activity."""
|
||||||
|
await self._data.async_start_activity(option)
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Call when entity is added to hass."""
|
||||||
|
|
||||||
|
callbacks = {
|
||||||
|
"connected": self.async_got_connected,
|
||||||
|
"disconnected": self.async_got_disconnected,
|
||||||
|
"activity_starting": self._async_activity_update,
|
||||||
|
"activity_started": self._async_activity_update,
|
||||||
|
"config_updated": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.async_on_remove(self._data.async_subscribe(HarmonyCallback(**callbacks)))
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_activity_update(self, activity_info: tuple):
|
||||||
|
self.async_write_ha_state()
|
@ -5,9 +5,9 @@ from homeassistant.components.switch import SwitchEntity
|
|||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
|
||||||
from .connection_state import ConnectionStateMixin
|
|
||||||
from .const import DOMAIN, HARMONY_DATA
|
from .const import DOMAIN, HARMONY_DATA
|
||||||
from .data import HarmonyData
|
from .data import HarmonyData
|
||||||
|
from .entity import HarmonyEntity
|
||||||
from .subscriber import HarmonyCallback
|
from .subscriber import HarmonyCallback
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -27,31 +27,18 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
async_add_entities(switches, True)
|
async_add_entities(switches, True)
|
||||||
|
|
||||||
|
|
||||||
class HarmonyActivitySwitch(ConnectionStateMixin, SwitchEntity):
|
class HarmonyActivitySwitch(HarmonyEntity, SwitchEntity):
|
||||||
"""Switch representation of a Harmony activity."""
|
"""Switch representation of a Harmony activity."""
|
||||||
|
|
||||||
def __init__(self, name: str, activity: dict, data: HarmonyData) -> None:
|
def __init__(self, name: str, activity: dict, data: HarmonyData) -> None:
|
||||||
"""Initialize HarmonyActivitySwitch class."""
|
"""Initialize HarmonyActivitySwitch class."""
|
||||||
super().__init__()
|
super().__init__(data=data)
|
||||||
self._name = name
|
|
||||||
self._activity_name = activity["label"]
|
self._activity_name = activity["label"]
|
||||||
self._activity_id = activity["id"]
|
self._activity_id = activity["id"]
|
||||||
self._data = data
|
self._attr_entity_registry_enabled_default = False
|
||||||
|
self._attr_unique_id = f"activity_{self._activity_id}"
|
||||||
@property
|
self._attr_name = name
|
||||||
def name(self):
|
self._attr_device_info = self._data.device_info(DOMAIN)
|
||||||
"""Return the Harmony activity's name."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the unique id."""
|
|
||||||
return f"activity_{self._activity_id}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self):
|
|
||||||
"""Return device info."""
|
|
||||||
return self._data.device_info(DOMAIN)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -59,16 +46,6 @@ class HarmonyActivitySwitch(ConnectionStateMixin, SwitchEntity):
|
|||||||
_, activity_name = self._data.current_activity
|
_, activity_name = self._data.current_activity
|
||||||
return activity_name == self._activity_name
|
return activity_name == self._activity_name
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""Return that we shouldn't be polled."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
"""Return True if we're connected to the Hub, otherwise False."""
|
|
||||||
return self._data.available
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Start this activity."""
|
"""Start this activity."""
|
||||||
await self._data.async_start_activity(self._activity_name)
|
await self._data.async_start_activity(self._activity_name)
|
||||||
|
@ -5,6 +5,7 @@ ENTITY_REMOTE = "remote.guest_room"
|
|||||||
ENTITY_WATCH_TV = "switch.guest_room_watch_tv"
|
ENTITY_WATCH_TV = "switch.guest_room_watch_tv"
|
||||||
ENTITY_PLAY_MUSIC = "switch.guest_room_play_music"
|
ENTITY_PLAY_MUSIC = "switch.guest_room_play_music"
|
||||||
ENTITY_NILE_TV = "switch.guest_room_nile_tv"
|
ENTITY_NILE_TV = "switch.guest_room_nile_tv"
|
||||||
|
ENTITY_SELECT = "select.guest_room_activities"
|
||||||
|
|
||||||
WATCH_TV_ACTIVITY_ID = 123
|
WATCH_TV_ACTIVITY_ID = 123
|
||||||
PLAY_MUSIC_ACTIVITY_ID = 456
|
PLAY_MUSIC_ACTIVITY_ID = 456
|
||||||
|
@ -7,6 +7,7 @@ from homeassistant.setup import async_setup_component
|
|||||||
from .const import (
|
from .const import (
|
||||||
ENTITY_NILE_TV,
|
ENTITY_NILE_TV,
|
||||||
ENTITY_PLAY_MUSIC,
|
ENTITY_PLAY_MUSIC,
|
||||||
|
ENTITY_SELECT,
|
||||||
ENTITY_WATCH_TV,
|
ENTITY_WATCH_TV,
|
||||||
HUB_NAME,
|
HUB_NAME,
|
||||||
NILE_TV_ACTIVITY_ID,
|
NILE_TV_ACTIVITY_ID,
|
||||||
@ -55,6 +56,13 @@ async def test_unique_id_migration(mock_hc, hass, mock_write_config):
|
|||||||
platform="harmony",
|
platform="harmony",
|
||||||
config_entry_id=entry.entry_id,
|
config_entry_id=entry.entry_id,
|
||||||
),
|
),
|
||||||
|
# select entity
|
||||||
|
ENTITY_SELECT: er.RegistryEntry(
|
||||||
|
entity_id=ENTITY_SELECT,
|
||||||
|
unique_id=f"{HUB_NAME}_activities",
|
||||||
|
platform="harmony",
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert await async_setup_component(hass, DOMAIN, {})
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
@ -70,3 +78,6 @@ async def test_unique_id_migration(mock_hc, hass, mock_write_config):
|
|||||||
|
|
||||||
switch_music = ent_reg.async_get(ENTITY_PLAY_MUSIC)
|
switch_music = ent_reg.async_get(ENTITY_PLAY_MUSIC)
|
||||||
assert switch_music.unique_id == f"activity_{PLAY_MUSIC_ACTIVITY_ID}"
|
assert switch_music.unique_id == f"activity_{PLAY_MUSIC_ACTIVITY_ID}"
|
||||||
|
|
||||||
|
select_activities = ent_reg.async_get(ENTITY_SELECT)
|
||||||
|
assert select_activities.unique_id == f"{HUB_NAME}_activities"
|
||||||
|
@ -33,7 +33,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.util import utcnow
|
from homeassistant.util import utcnow
|
||||||
|
|
||||||
from .conftest import ACTIVITIES_TO_IDS, TV_DEVICE_ID, TV_DEVICE_NAME
|
from .conftest import ACTIVITIES_TO_IDS, TV_DEVICE_ID, TV_DEVICE_NAME
|
||||||
from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME
|
from .const import ENTITY_REMOTE, HUB_NAME
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
@ -91,10 +91,10 @@ async def test_remote_toggles(mock_hc, hass, mock_write_config):
|
|||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# mocks start with current activity == Watch TV
|
# mocks start remote with Watch TV default activity
|
||||||
assert hass.states.is_state(ENTITY_REMOTE, STATE_ON)
|
state = hass.states.get(ENTITY_REMOTE)
|
||||||
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON)
|
assert state.state == STATE_ON
|
||||||
assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF)
|
assert state.attributes.get("current_activity") == "Watch TV"
|
||||||
|
|
||||||
# turn off remote
|
# turn off remote
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -105,9 +105,9 @@ async def test_remote_toggles(mock_hc, hass, mock_write_config):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.is_state(ENTITY_REMOTE, STATE_OFF)
|
state = hass.states.get(ENTITY_REMOTE)
|
||||||
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF)
|
assert state.state == STATE_OFF
|
||||||
assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF)
|
assert state.attributes.get("current_activity") == "PowerOff"
|
||||||
|
|
||||||
# turn on remote, restoring the last activity
|
# turn on remote, restoring the last activity
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -118,9 +118,9 @@ async def test_remote_toggles(mock_hc, hass, mock_write_config):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.is_state(ENTITY_REMOTE, STATE_ON)
|
state = hass.states.get(ENTITY_REMOTE)
|
||||||
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON)
|
assert state.state == STATE_ON
|
||||||
assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF)
|
assert state.attributes.get("current_activity") == "Watch TV"
|
||||||
|
|
||||||
# send new activity command, with activity name
|
# send new activity command, with activity name
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -131,9 +131,9 @@ async def test_remote_toggles(mock_hc, hass, mock_write_config):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.is_state(ENTITY_REMOTE, STATE_ON)
|
state = hass.states.get(ENTITY_REMOTE)
|
||||||
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF)
|
assert state.state == STATE_ON
|
||||||
assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_ON)
|
assert state.attributes.get("current_activity") == "Play Music"
|
||||||
|
|
||||||
# send new activity command, with activity id
|
# send new activity command, with activity id
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -144,9 +144,9 @@ async def test_remote_toggles(mock_hc, hass, mock_write_config):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.is_state(ENTITY_REMOTE, STATE_ON)
|
state = hass.states.get(ENTITY_REMOTE)
|
||||||
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON)
|
assert state.state == STATE_ON
|
||||||
assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF)
|
assert state.attributes.get("current_activity") == "Watch TV"
|
||||||
|
|
||||||
|
|
||||||
async def test_async_send_command(mock_hc, harmony_client, hass, mock_write_config):
|
async def test_async_send_command(mock_hc, harmony_client, hass, mock_write_config):
|
||||||
|
113
tests/components/harmony/test_select.py
Normal file
113
tests/components/harmony/test_select.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
"""Test the Logitech Harmony Hub activity select."""
|
||||||
|
|
||||||
|
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.util import utcnow
|
||||||
|
|
||||||
|
from .const import ENTITY_REMOTE, ENTITY_SELECT, HUB_NAME
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connection_state_changes(
|
||||||
|
harmony_client, mock_hc, hass, mock_write_config
|
||||||
|
):
|
||||||
|
"""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)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# mocks start with current activity == Watch TV
|
||||||
|
assert hass.states.is_state(ENTITY_SELECT, "Watch TV")
|
||||||
|
|
||||||
|
harmony_client.mock_disconnection()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Entities do not immediately show as unavailable
|
||||||
|
assert hass.states.is_state(ENTITY_SELECT, "Watch TV")
|
||||||
|
|
||||||
|
future_time = utcnow() + timedelta(seconds=10)
|
||||||
|
async_fire_time_changed(hass, future_time)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.is_state(ENTITY_SELECT, STATE_UNAVAILABLE)
|
||||||
|
|
||||||
|
harmony_client.mock_reconnection()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.is_state(ENTITY_SELECT, "Watch TV")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options(mock_hc, hass, mock_write_config):
|
||||||
|
"""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)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# assert we have all options
|
||||||
|
state = hass.states.get(ENTITY_SELECT)
|
||||||
|
assert state.attributes.get("options") == [
|
||||||
|
"PowerOff",
|
||||||
|
"Nile-TV",
|
||||||
|
"Play Music",
|
||||||
|
"Watch TV",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_select_option(mock_hc, hass, mock_write_config):
|
||||||
|
"""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)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# mocks start with current activity == Watch TV
|
||||||
|
assert hass.states.is_state(ENTITY_REMOTE, STATE_ON)
|
||||||
|
assert hass.states.is_state(ENTITY_SELECT, "Watch TV")
|
||||||
|
|
||||||
|
# launch Play Music activity
|
||||||
|
await _select_option_and_wait(hass, ENTITY_SELECT, "Play Music")
|
||||||
|
assert hass.states.is_state(ENTITY_REMOTE, STATE_ON)
|
||||||
|
assert hass.states.is_state(ENTITY_SELECT, "Play Music")
|
||||||
|
|
||||||
|
# turn off harmony by selecting PowerOff activity
|
||||||
|
await _select_option_and_wait(hass, ENTITY_SELECT, "PowerOff")
|
||||||
|
assert hass.states.is_state(ENTITY_REMOTE, STATE_OFF)
|
||||||
|
assert hass.states.is_state(ENTITY_SELECT, "PowerOff")
|
||||||
|
|
||||||
|
|
||||||
|
async def _select_option_and_wait(hass, entity, option):
|
||||||
|
await hass.services.async_call(
|
||||||
|
SELECT_DOMAIN,
|
||||||
|
SERVICE_SELECT_OPTION,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity,
|
||||||
|
ATTR_OPTION: option,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
@ -16,6 +16,7 @@ from homeassistant.const import (
|
|||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers import entity_registry
|
||||||
from homeassistant.util import utcnow
|
from homeassistant.util import utcnow
|
||||||
|
|
||||||
from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME
|
from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME
|
||||||
@ -35,6 +36,17 @@ async def test_connection_state_changes(
|
|||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# check if switch entities are disabled by default
|
||||||
|
assert not hass.states.get(ENTITY_WATCH_TV)
|
||||||
|
assert not hass.states.get(ENTITY_PLAY_MUSIC)
|
||||||
|
|
||||||
|
# enable switch entities
|
||||||
|
ent_reg = entity_registry.async_get(hass)
|
||||||
|
ent_reg.async_update_entity(ENTITY_WATCH_TV, disabled_by=None)
|
||||||
|
ent_reg.async_update_entity(ENTITY_PLAY_MUSIC, disabled_by=None)
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# mocks start with current activity == Watch TV
|
# mocks start with current activity == Watch TV
|
||||||
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON)
|
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON)
|
||||||
assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF)
|
assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF)
|
||||||
@ -78,6 +90,13 @@ async def test_switch_toggles(mock_hc, hass, mock_write_config):
|
|||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# enable switch entities
|
||||||
|
ent_reg = entity_registry.async_get(hass)
|
||||||
|
ent_reg.async_update_entity(ENTITY_WATCH_TV, disabled_by=None)
|
||||||
|
ent_reg.async_update_entity(ENTITY_PLAY_MUSIC, disabled_by=None)
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# mocks start with current activity == Watch TV
|
# mocks start with current activity == Watch TV
|
||||||
assert hass.states.is_state(ENTITY_REMOTE, STATE_ON)
|
assert hass.states.is_state(ENTITY_REMOTE, STATE_ON)
|
||||||
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON)
|
assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user