async def get_client(ip_address, protocol, show_responses) -> Optional[HarmonyAPI]: client = HarmonyAPI(ip_address=ip_address, protocol=protocol) def output_response(message): print(f"{client.name}: {message}") if show_responses: listen_callback = Handler(handler_obj=output_response, handler_name='output_response', once=False) client.register_handler(handler=listen_callback) print(f"Trying to connect to HUB with IP {ip_address}.") try: if await client.connect(): print( "Connected to HUB {} ({}) with firmware version {} and HUB ID {} using protocol {}" .format(client.name, ip_address, client.fw_version, client.hub_id, client.protocol)) return client except ConnectionRefusedError: print(f"Failed to connect to HUB {ip_address}.") print("An issue occurred trying to connect") return None
async def getClient(ip_address): client = HarmonyAPI(ip_address) if await client.connect(): return client return None
async def connect(self) -> bool: """Connect to the Harmony Hub.""" _LOGGER.debug("%s: Connecting", self._name) callbacks = { "config_updated": self._config_updated, "connect": self._connected, "disconnect": self._disconnected, "new_activity_starting": self._activity_starting, "new_activity": self._activity_started, } self._client = HarmonyClient( ip_address=self._address, callbacks=ClientCallbackType(**callbacks) ) connected = False try: connected = await self._client.connect() except (asyncio.TimeoutError, aioexc.TimeOut) as err: await self._client.close() raise ConfigEntryNotReady( 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: {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" )
def __init__(self, name, host, port, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" from aioharmony.harmonyapi import ( HarmonyAPI as HarmonyClient, ClientCallbackType ) _LOGGER.debug("%s: Device init started", name) self._name = name self.host = host self.port = port self._state = None self._current_activity = None self._default_activity = activity self._client = HarmonyClient( ip_address=host, callbacks=ClientCallbackType( new_activity=self.new_activity, config_updated=self.new_config, connect=self.got_connected, disconnect=self.got_disconnected ) ) self._config_path = out_path self._delay_secs = delay_secs self._available = False
async def get_client(): client = HarmonyAPI(HUB_IP, HUB_PROTOCOL) try: if await client.connect(): return client except: pass return None
async def get_client(ip_address, show_responses) -> Optional[HarmonyAPI]: client = HarmonyAPI(ip_address) def output_response(message): print(message) listen_callback = Handler(handler_obj=output_response, handler_name='output_response', once=False) if show_responses: client.register_handler(handler=listen_callback) if await client.connect(): print("Connected to HUB {} with firmware version {}".format( client.name, client.fw_version)) return client print("An issue occured trying to connect") return None
def __init__(self, name, host, port, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" self._name = name self.host = host self.port = port self._state = None self._current_activity = None self._default_activity = activity self._client = HarmonyClient(ip_address=host) self._config_path = out_path self._delay_secs = delay_secs self._available = False
def __init__(self, name, unique_id, host, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" self._name = name self.host = host self._state = None self._current_activity = None self.default_activity = activity self._client = HarmonyClient(ip_address=host) self._config_path = out_path self.delay_secs = delay_secs self._available = False self._unique_id = unique_id self._undo_dispatch_subscription = None
async def get_harmony_client_if_available(ip_address: str): """Connect to a harmony hub and fetch info.""" harmony = HarmonyAPI(ip_address=ip_address) try: if not await harmony.connect(): await harmony.close() return None except harmony_exceptions.TimeOut: return None await harmony.close() return harmony
def __init__(self, name, host, port, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" from aioharmony.harmonyapi import HarmonyAPI as HarmonyClient self._name = name self.host = host self.port = port self._state = None self._current_activity = None self._default_activity = activity self._client = HarmonyClient(ip_address=host) self._config_path = out_path self._delay_secs = delay_secs self._available = False
def __init__(self, name, unique_id, host, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" self._name = name self.host = host self._state = None self._current_activity = ACTIVITY_POWER_OFF self.default_activity = activity self._activity_starting = None self._is_initial_update = True self._client = HarmonyClient(ip_address=host) self._config_path = out_path self.delay_secs = delay_secs self._available = False self._unique_id = unique_id self._last_activity = None
def __init__(self, hass, address: str, name: str, unique_id: str): """Initialize a data object.""" super().__init__(hass) self._name = name self._unique_id = unique_id self._available = False callbacks = { "config_updated": self._config_updated, "connect": self._connected, "disconnect": self._disconnected, "new_activity_starting": self._activity_starting, "new_activity": self._activity_started, } self._client = HarmonyClient(ip_address=address, callbacks=ClientCallbackType(**callbacks))
async def connect(self) -> bool: """Connect to the Harmony Hub.""" _LOGGER.debug("%s: Connecting", self._name) callbacks = { "config_updated": self._config_updated, "connect": self._connected, "disconnect": self._disconnected, "new_activity_starting": self._activity_starting, "new_activity": self._activity_started, } self._client = HarmonyClient(ip_address=self._address, callbacks=ClientCallbackType(**callbacks)) try: if not await self._client.connect(): _LOGGER.warning("%s: Unable to connect to HUB", self._name) await self._client.close() return False except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False return True
class HarmonyRemote(remote.RemoteDevice): """Remote representation used to control a Harmony device.""" def __init__(self, name, host, port, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" from aioharmony.harmonyapi import HarmonyAPI as HarmonyClient self._name = name self.host = host self.port = port self._state = None self._current_activity = None self._default_activity = activity self._client = HarmonyClient(ip_address=host) self._config_path = out_path self._delay_secs = delay_secs self._available = False async def async_added_to_hass(self): """Complete the initialization.""" from aioharmony.harmonyapi import ClientCallbackType _LOGGER.debug("%s: Harmony Hub added", self._name) # Register the callbacks self._client.callbacks = ClientCallbackType( new_activity=self.new_activity, config_updated=self.new_config, connect=self.got_connected, disconnect=self.got_disconnected ) # Store Harmony HUB config, this will also update our current # activity await self.new_config() import aioharmony.exceptions as aioexc async def shutdown(_): """Close connection on shutdown.""" _LOGGER.debug("%s: Closing Harmony Hub", self._name) try: await self._client.close() except aioexc.TimeOut: _LOGGER.warning("%s: Disconnect timed-out", self._name) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) @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 def device_state_attributes(self): """Add platform specific attributes.""" return {ATTR_CURRENT_ACTIVITY: self._current_activity} @property def is_on(self): """Return False if PowerOff is the current activity, otherwise True.""" return self._current_activity not in [None, 'PowerOff'] @property def available(self): """Return True if connected to Hub, otherwise False.""" return self._available async def connect(self): """Connect to the Harmony HUB.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Connecting", self._name) try: if not await self._client.connect(): _LOGGER.warning("%s: Unable to connect to HUB.", self._name) await self._client.close() return False except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False return True def 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) self._current_activity = activity_name self._state = bool(activity_id != -1) self._available = True self.async_schedule_update_ha_state() async def new_config(self, _=None): """Call for updating the current activity.""" _LOGGER.debug("%s: configuration has been updated", self._name) self.new_activity(self._client.current_activity) await self.hass.async_add_executor_job(self.write_config_file) async def got_connected(self, _=None): """Notification that we're connected to the HUB.""" _LOGGER.debug("%s: connected to the HUB.", self._name) if not self._available: # We were disconnected before. await self.new_config() async def got_disconnected(self, _=None): """Notification that we're disconnected from the HUB.""" _LOGGER.debug("%s: disconnected from the HUB.", self._name) self._available = False # We're going to wait for 10 seconds before announcing we're # unavailable, this to allow a reconnection to happen. await asyncio.sleep(10) if not self._available: # Still disconnected. Let the state engine know. self.async_schedule_update_ha_state() async def async_turn_on(self, **kwargs): """Start an activity from the Harmony device.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Turn On", self.name) activity = kwargs.get(ATTR_ACTIVITY, self._default_activity) if activity: activity_id = None if activity.isdigit() or activity == '-1': _LOGGER.debug("%s: Activity is numeric", self.name) if self._client.get_activity_name(int(activity)): activity_id = activity if activity_id is None: _LOGGER.debug("%s: Find activity ID based on name", self.name) activity_id = self._client.get_activity_id( str(activity).strip()) if activity_id is None: _LOGGER.error("%s: Activity %s is invalid", self.name, activity) return try: await self._client.start_activity(activity_id) except aioexc.TimeOut: _LOGGER.error("%s: Starting activity %s timed-out", self.name, activity) else: _LOGGER.error("%s: No activity specified with turn_on service", self.name) async def async_turn_off(self, **kwargs): """Start the PowerOff activity.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Turn Off", self.name) try: await self._client.power_off() except aioexc.TimeOut: _LOGGER.error("%s: Powering off timed-out", self.name) # pylint: disable=arguments-differ async def async_send_command(self, command, **kwargs): """Send a list of commands to one device.""" from aioharmony.harmonyapi import SendCommandDevice import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Send Command", self.name) device = kwargs.get(ATTR_DEVICE) if device is None: _LOGGER.error("%s: Missing required argument: device", self.name) return device_id = None if device.isdigit(): _LOGGER.debug("%s: Device %s is numeric", self.name, device) if self._client.get_device_name(int(device)): device_id = device if device_id is None: _LOGGER.debug("%s: Find device ID %s based on device name", self.name, device) device_id = self._client.get_device_id(str(device).strip()) if device_id is None: _LOGGER.error("%s: Device %s is invalid", self.name, device) return num_repeats = kwargs[ATTR_NUM_REPEATS] delay_secs = kwargs.get(ATTR_DELAY_SECS, self._delay_secs) hold_secs = kwargs[ATTR_HOLD_SECS] _LOGGER.debug("Sending commands to device %s holding for %s seconds " "with a delay of %s seconds", device, hold_secs, delay_secs) # Creating list of commands to send. snd_cmnd_list = [] for _ in range(num_repeats): for single_command in command: send_command = SendCommandDevice( device=device_id, command=single_command, delay=hold_secs ) snd_cmnd_list.append(send_command) if delay_secs > 0: snd_cmnd_list.append(float(delay_secs)) _LOGGER.debug("%s: Sending commands", self.name) try: result_list = await self._client.send_commands(snd_cmnd_list) except aioexc.TimeOut: _LOGGER.error("%s: Sending commands timed-out", self.name) return for result in result_list: _LOGGER.error("Sending command %s to device %s failed with code " "%s: %s", result.command.command, result.command.device, result.code, result.msg ) async def change_channel(self, channel): """Change the channel using Harmony remote.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Changing channel to %s", self.name, channel) try: await self._client.change_channel(channel) except aioexc.TimeOut: _LOGGER.error("%s: Changing channel to %s timed-out", self.name, channel) async def sync(self): """Sync the Harmony device with the web service.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Syncing hub with Harmony cloud", self.name) try: await self._client.sync() except aioexc.TimeOut: _LOGGER.error("%s: Syncing hub with Harmony cloud timed-out", self.name) else: await self.hass.async_add_executor_job(self.write_config_file) def write_config_file(self): """Write Harmony configuration file.""" _LOGGER.debug("%s: Writing hub config to file: %s", self.name, self._config_path) if self._client.config is None: _LOGGER.warning("%s: No configuration received from hub", self.name) return try: with open(self._config_path, 'w+', encoding='utf-8') as file_out: json.dump(self._client.json_config, file_out, sort_keys=True, indent=4) except IOError as exc: _LOGGER.error("%s: Unable to write HUB configuration to %s: %s", self.name, self._config_path, exc)
class HarmonyRemote(remote.RemoteDevice): """Remote representation used to control a Harmony device.""" def __init__(self, name, host, port, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" from aioharmony.harmonyapi import HarmonyAPI as HarmonyClient self._name = name self.host = host self.port = port self._state = None self._current_activity = None self._default_activity = activity self._client = HarmonyClient(ip_address=host) self._config_path = out_path self._delay_secs = delay_secs self._available = False async def async_added_to_hass(self): """Complete the initialization.""" from aioharmony.harmonyapi import ClientCallbackType _LOGGER.debug("%s: Harmony Hub added", self._name) # Register the callbacks self._client.callbacks = ClientCallbackType( new_activity=self.new_activity, config_updated=self.new_config, connect=self.got_connected, disconnect=self.got_disconnected) # Store Harmony HUB config, this will also update our current # activity await self.new_config() import aioharmony.exceptions as aioexc async def shutdown(_): """Close connection on shutdown.""" _LOGGER.debug("%s: Closing Harmony Hub", self._name) try: await self._client.close() except aioexc.TimeOut: _LOGGER.warning("%s: Disconnect timed-out", self._name) self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) @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 def device_state_attributes(self): """Add platform specific attributes.""" return {ATTR_CURRENT_ACTIVITY: self._current_activity} @property def is_on(self): """Return False if PowerOff is the current activity, otherwise True.""" return self._current_activity not in [None, 'PowerOff'] @property def available(self): """Return True if connected to Hub, otherwise False.""" return self._available async def connect(self): """Connect to the Harmony HUB.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Connecting", self._name) try: if not await self._client.connect(): _LOGGER.warning("%s: Unable to connect to HUB.", self._name) await self._client.close() return False except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False return True def 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) self._current_activity = activity_name self._state = bool(activity_id != -1) self._available = True self.async_schedule_update_ha_state() async def new_config(self, _=None): """Call for updating the current activity.""" _LOGGER.debug("%s: configuration has been updated", self._name) self.new_activity(self._client.current_activity) await self.hass.async_add_executor_job(self.write_config_file) async def got_connected(self, _=None): """Notification that we're connected to the HUB.""" _LOGGER.debug("%s: connected to the HUB.", self._name) if not self._available: # We were disconnected before. await self.new_config() async def got_disconnected(self, _=None): """Notification that we're disconnected from the HUB.""" _LOGGER.debug("%s: disconnected from the HUB.", self._name) self._available = False # We're going to wait for 10 seconds before announcing we're # unavailable, this to allow a reconnection to happen. await asyncio.sleep(10) if not self._available: # Still disconnected. Let the state engine know. self.async_schedule_update_ha_state() async def async_turn_on(self, **kwargs): """Start an activity from the Harmony device.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Turn On", self.name) activity = kwargs.get(ATTR_ACTIVITY, self._default_activity) if activity: activity_id = None if activity.isdigit() or activity == '-1': _LOGGER.debug("%s: Activity is numeric", self.name) if self._client.get_activity_name(int(activity)): activity_id = activity if activity_id is None: _LOGGER.debug("%s: Find activity ID based on name", self.name) activity_id = self._client.get_activity_id( str(activity).strip()) if activity_id is None: _LOGGER.error("%s: Activity %s is invalid", self.name, activity) return try: await self._client.start_activity(activity_id) except aioexc.TimeOut: _LOGGER.error("%s: Starting activity %s timed-out", self.name, activity) else: _LOGGER.error("%s: No activity specified with turn_on service", self.name) async def async_turn_off(self, **kwargs): """Start the PowerOff activity.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Turn Off", self.name) try: await self._client.power_off() except aioexc.TimeOut: _LOGGER.error("%s: Powering off timed-out", self.name) # pylint: disable=arguments-differ async def async_send_command(self, command, **kwargs): """Send a list of commands to one device.""" from aioharmony.harmonyapi import SendCommandDevice import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Send Command", self.name) device = kwargs.get(ATTR_DEVICE) if device is None: _LOGGER.error("%s: Missing required argument: device", self.name) return device_id = None if device.isdigit(): _LOGGER.debug("%s: Device %s is numeric", self.name, device) if self._client.get_device_name(int(device)): device_id = device if device_id is None: _LOGGER.debug("%s: Find device ID %s based on device name", self.name, device) device_id = self._client.get_device_id(str(device).strip()) if device_id is None: _LOGGER.error("%s: Device %s is invalid", self.name, device) return num_repeats = kwargs.get(ATTR_NUM_REPEATS) delay_secs = kwargs.get(ATTR_DELAY_SECS, self._delay_secs) # Creating list of commands to send. snd_cmnd_list = [] for _ in range(num_repeats): for single_command in command: send_command = SendCommandDevice(device=device_id, command=single_command, delay=0) snd_cmnd_list.append(send_command) if delay_secs > 0: snd_cmnd_list.append(float(delay_secs)) _LOGGER.debug("%s: Sending commands", self.name) try: result_list = await self._client.send_commands(snd_cmnd_list) except aioexc.TimeOut: _LOGGER.error("%s: Sending commands timed-out", self.name) return for result in result_list: _LOGGER.error( "Sending command %s to device %s failed with code " "%s: %s", result.command.command, result.command.device, result.code, result.msg) async def change_channel(self, channel): """Change the channel using Harmony remote.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Changing channel to %s", self.name, channel) try: await self._client.change_channel(channel) except aioexc.TimeOut: _LOGGER.error("%s: Changing channel to %s timed-out", self.name, channel) async def sync(self): """Sync the Harmony device with the web service.""" import aioharmony.exceptions as aioexc _LOGGER.debug("%s: Syncing hub with Harmony cloud", self.name) try: await self._client.sync() except aioexc.TimeOut: _LOGGER.error("%s: Syncing hub with Harmony cloud timed-out", self.name) else: await self.hass.async_add_executor_job(self.write_config_file) def write_config_file(self): """Write Harmony configuration file.""" _LOGGER.debug("%s: Writing hub config to file: %s", self.name, self._config_path) if self._client.config is None: _LOGGER.warning("%s: No configuration received from hub", self.name) return try: with open(self._config_path, 'w+', encoding='utf-8') as file_out: json.dump(self._client.json_config, file_out, sort_keys=True, indent=4) except IOError as exc: _LOGGER.error("%s: Unable to write HUB configuration to %s: %s", self.name, self._config_path, exc)
class HarmonyRemote(remote.RemoteDevice): """Remote representation used to control a Harmony device.""" def __init__(self, name, unique_id, host, activity, out_path, delay_secs): """Initialize HarmonyRemote class.""" self._name = name self.host = host self._state = None self._current_activity = None self.default_activity = activity self._client = HarmonyClient(ip_address=host) self._config_path = out_path self.delay_secs = delay_secs self._available = False self._unique_id = unique_id self._undo_dispatch_subscription = None @property def activity_names(self): """Names of all the remotes activities.""" activities = [ activity["label"] for activity in self._client.config["activity"] ] # Remove both ways of representing PowerOff if None in activities: activities.remove(None) if ACTIVITY_POWER_OFF in activities: activities.remove(ACTIVITY_POWER_OFF) return activities async def async_will_remove_from_hass(self): """Undo subscription.""" if self._undo_dispatch_subscription: self._undo_dispatch_subscription() async def _async_update_options(self, data): """Change options when the options flow does.""" if ATTR_DELAY_SECS in data: self.delay_secs = data[ATTR_DELAY_SECS] if ATTR_ACTIVITY in data: self.default_activity = data[ATTR_ACTIVITY] async def async_added_to_hass(self): """Complete the initialization.""" _LOGGER.debug("%s: Harmony Hub added", self._name) # Register the callbacks self._client.callbacks = ClientCallbackType( new_activity=self.new_activity, config_updated=self.new_config, connect=self.got_connected, disconnect=self.got_disconnected, ) self._undo_dispatch_subscription = async_dispatcher_connect( self.hass, f"{HARMONY_OPTIONS_UPDATE}-{self.unique_id}", self._async_update_options, ) # Store Harmony HUB config, this will also update our current # activity await self.new_config() async def shutdown(self): """Close connection on shutdown.""" _LOGGER.debug("%s: Closing Harmony Hub", self._name) try: await self._client.close() except aioexc.TimeOut: _LOGGER.warning("%s: Disconnect timed-out", self._name) @property def device_info(self): """Return device info.""" model = "Harmony Hub" if "ethernetStatus" in self._client.hub_config.info: model = "Harmony Hub Pro 2400" return { "identifiers": {(DOMAIN, self.unique_id)}, "manufacturer": "Logitech", "sw_version": self._client.hub_config.info.get("hubSwVersion", self._client.fw_version), "name": self.name, "model": model, } @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 def device_state_attributes(self): """Add platform specific attributes.""" return {ATTR_CURRENT_ACTIVITY: self._current_activity} @property def is_on(self): """Return False if PowerOff is the current activity, otherwise True.""" return self._current_activity not in [None, "PowerOff"] @property def available(self): """Return True if connected to Hub, otherwise False.""" return self._available async def connect(self): """Connect to the Harmony HUB.""" _LOGGER.debug("%s: Connecting", self._name) try: if not await self._client.connect(): _LOGGER.warning("%s: Unable to connect to HUB.", self._name) await self._client.close() return False except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False return True def 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) self._current_activity = activity_name self._state = bool(activity_id != -1) self._available = True self.async_write_ha_state() async def new_config(self, _=None): """Call for updating the current activity.""" _LOGGER.debug("%s: configuration has been updated", self._name) self.new_activity(self._client.current_activity) await self.hass.async_add_executor_job(self.write_config_file) async def got_connected(self, _=None): """Notification that we're connected to the HUB.""" _LOGGER.debug("%s: connected to the HUB.", self._name) if not self._available: # We were disconnected before. await self.new_config() async def got_disconnected(self, _=None): """Notification that we're disconnected from the HUB.""" _LOGGER.debug("%s: disconnected from the HUB.", self._name) self._available = False # We're going to wait for 10 seconds before announcing we're # unavailable, this to allow a reconnection to happen. await asyncio.sleep(10) if not self._available: # Still disconnected. Let the state engine know. self.async_write_ha_state() async def async_turn_on(self, **kwargs): """Start an activity from the Harmony device.""" _LOGGER.debug("%s: Turn On", self.name) activity = kwargs.get(ATTR_ACTIVITY, self.default_activity) if activity: activity_id = None if activity.isdigit() or activity == "-1": _LOGGER.debug("%s: Activity is numeric", self.name) if self._client.get_activity_name(int(activity)): activity_id = activity if activity_id is None: _LOGGER.debug("%s: Find activity ID based on name", self.name) activity_id = self._client.get_activity_id( str(activity).strip()) if activity_id is None: _LOGGER.error("%s: Activity %s is invalid", self.name, activity) return try: await self._client.start_activity(activity_id) except aioexc.TimeOut: _LOGGER.error("%s: Starting activity %s timed-out", self.name, activity) else: _LOGGER.error("%s: No activity specified with turn_on service", self.name) async def async_turn_off(self, **kwargs): """Start the PowerOff activity.""" _LOGGER.debug("%s: Turn Off", self.name) try: await self._client.power_off() except aioexc.TimeOut: _LOGGER.error("%s: Powering off timed-out", self.name) async def async_send_command(self, command, **kwargs): """Send a list of commands to one device.""" _LOGGER.debug("%s: Send Command", self.name) device = kwargs.get(ATTR_DEVICE) if device is None: _LOGGER.error("%s: Missing required argument: device", self.name) return device_id = None if device.isdigit(): _LOGGER.debug("%s: Device %s is numeric", self.name, device) if self._client.get_device_name(int(device)): device_id = device if device_id is None: _LOGGER.debug("%s: Find device ID %s based on device name", self.name, device) device_id = self._client.get_device_id(str(device).strip()) if device_id is None: _LOGGER.error("%s: Device %s is invalid", self.name, device) return num_repeats = kwargs[ATTR_NUM_REPEATS] delay_secs = kwargs.get(ATTR_DELAY_SECS, self.delay_secs) hold_secs = kwargs[ATTR_HOLD_SECS] _LOGGER.debug( "Sending commands to device %s holding for %s seconds " "with a delay of %s seconds", device, hold_secs, delay_secs, ) # Creating list of commands to send. snd_cmnd_list = [] for _ in range(num_repeats): for single_command in command: send_command = SendCommandDevice(device=device_id, command=single_command, delay=hold_secs) snd_cmnd_list.append(send_command) if delay_secs > 0: snd_cmnd_list.append(float(delay_secs)) _LOGGER.debug("%s: Sending commands", self.name) try: result_list = await self._client.send_commands(snd_cmnd_list) except aioexc.TimeOut: _LOGGER.error("%s: Sending commands timed-out", self.name) return for result in result_list: _LOGGER.error( "Sending command %s to device %s failed with code %s: %s", result.command.command, result.command.device, result.code, result.msg, ) async def change_channel(self, channel): """Change the channel using Harmony remote.""" _LOGGER.debug("%s: Changing channel to %s", self.name, channel) try: await self._client.change_channel(channel) except aioexc.TimeOut: _LOGGER.error("%s: Changing channel to %s timed-out", self.name, channel) async def sync(self): """Sync the Harmony device with the web service.""" _LOGGER.debug("%s: Syncing hub with Harmony cloud", self.name) try: await self._client.sync() except aioexc.TimeOut: _LOGGER.error("%s: Syncing hub with Harmony cloud timed-out", self.name) else: await self.hass.async_add_executor_job(self.write_config_file) def write_config_file(self): """Write Harmony configuration file.""" _LOGGER.debug("%s: Writing hub configuration to file: %s", self.name, self._config_path) if self._client.config is None: _LOGGER.warning("%s: No configuration received from hub", self.name) return try: with open(self._config_path, "w+", encoding="utf-8") as file_out: json.dump(self._client.json_config, file_out, sort_keys=True, indent=4) except IOError as exc: _LOGGER.error( "%s: Unable to write HUB configuration to %s: %s", self.name, self._config_path, exc, )
async def connect(self): # Client self.client = HarmonyAPI(self.config.get('host'), loop=self.context.loop) # Connect try: if not await self.client.connect(): logger.warning( 'Unable to connect to Harmony HUB, name=%s, fw=%s' % (self.client.name, self.client.fw_version)) await self.client.close() except aioexc.TimeOut: logger.warning('Harmony HUB connection timed out') logger.info('Connected to Harmony HUB, name=%s, fw=%s' % (self.client.name, self.client.fw_version)) self.state.hub = { 'name': self.client.name, 'fw': self.client.fw_version, } # Register callbacks self.client.callbacks = ClientCallbackType( new_activity=self.callbacks.new_activity, config_updated=self.callbacks.new_config, connect=self.callbacks.connected, disconnect=self.callbacks.disconnected) # Config with open('harmony.txt', 'w') as outfile: json.dump(self.client.config, outfile) # print(json.dumps(self.client.config)) self.activities = { str(a['id']): a['label'] for a in self.client.config.get('activity', []) } self.state.activities = self.activities logger.debug('> Activities:') for aid, name in self.activities.items(): logger.debug(' %s:%s' % (str(aid), name)) self.devices = { str(a['id']): a['label'] for a in self.client.config.get('device', []) } self.state.devices = self.devices logger.debug('> Devices:') for did, name in self.devices.items(): logger.debug(' %s:%s' % (str(did), name)) # Update current activity activity_id, activity_name = self.client.current_activity self.state.connected = True self.state.current_activity = activity_name logger.info('> Current activity, id=%s, name=%s' % (str(activity_id), activity_name))
class HarmonyData(HarmonySubscriberMixin): """HarmonyData registers for Harmony hub updates.""" def __init__(self, hass, address: str, name: str, unique_id: str): """Initialize a data object.""" super().__init__(hass) self._name = name self._unique_id = unique_id self._available = False self._client = None self._address = address @property def activities(self): """List of all non-poweroff activity objects.""" activity_infos = self._client.config.get("activity", []) return [ info for info in activity_infos if info["label"] is not None and info["label"] != ACTIVITY_POWER_OFF ] @property def activity_names(self): """Names of all the remotes activities.""" activity_infos = self.activities activities = [activity["label"] for activity in activity_infos] return activities @property def device_names(self): """Names of all of the devices connected to the hub.""" device_infos = self._client.config.get("device", []) devices = [device["label"] for device in device_infos] return devices @property def name(self): """Return the Harmony device's name.""" return self._name @property def unique_id(self): """Return the Harmony device's unique_id.""" return self._unique_id @property def json_config(self): """Return the hub config as json.""" if self._client.config is None: return None return self._client.json_config @property def available(self) -> bool: """Return if connected to the hub.""" return self._available @property def current_activity(self) -> tuple: """Return the current activity tuple.""" return self._client.current_activity def device_info(self, domain: str): """Return hub device info.""" model = "Harmony Hub" if "ethernetStatus" in self._client.hub_config.info: model = "Harmony Hub Pro 2400" return { "identifiers": {(domain, self.unique_id)}, "manufacturer": "Logitech", "sw_version": self._client.hub_config.info.get("hubSwVersion", self._client.fw_version), "name": self.name, "model": model, } async def connect(self) -> bool: """Connect to the Harmony Hub.""" _LOGGER.debug("%s: Connecting", self._name) callbacks = { "config_updated": self._config_updated, "connect": self._connected, "disconnect": self._disconnected, "new_activity_starting": self._activity_starting, "new_activity": self._activity_started, } self._client = HarmonyClient(ip_address=self._address, callbacks=ClientCallbackType(**callbacks)) try: if not await self._client.connect(): _LOGGER.warning("%s: Unable to connect to HUB", self._name) await self._client.close() return False except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False return True async def shutdown(self): """Close connection on shutdown.""" _LOGGER.debug("%s: Closing Harmony Hub", self._name) try: await self._client.close() except aioexc.TimeOut: _LOGGER.warning("%s: Disconnect timed-out", self._name) async def async_start_activity(self, activity: str): """Start an activity from the Harmony device.""" if not activity: _LOGGER.error("%s: No activity specified with turn_on service", self.name) return activity_id = None activity_name = None if activity.isdigit() or activity == "-1": _LOGGER.debug("%s: Activity is numeric", self.name) activity_name = self._client.get_activity_name(int(activity)) if activity_name: activity_id = activity if activity_id is None: _LOGGER.debug("%s: Find activity ID based on name", self.name) activity_name = str(activity) activity_id = self._client.get_activity_id(activity_name) if activity_id is None: _LOGGER.error("%s: Activity %s is invalid", self.name, activity) return _, current_activity_name = self.current_activity if current_activity_name == activity_name: # Automations or HomeKit may turn the device on multiple times # when the current activity is already active which will cause # harmony to loose state. This behavior is unexpected as turning # the device on when its already on isn't expected to reset state. _LOGGER.debug("%s: Current activity is already %s", self.name, activity_name) return await self.async_lock_start_activity() try: await self._client.start_activity(activity_id) except aioexc.TimeOut: _LOGGER.error("%s: Starting activity %s timed-out", self.name, activity) self.async_unlock_start_activity() async def async_power_off(self): """Start the PowerOff activity.""" _LOGGER.debug("%s: Turn Off", self.name) try: await self._client.power_off() except aioexc.TimeOut: _LOGGER.error("%s: Powering off timed-out", self.name) async def async_send_command( self, commands: Iterable[str], device: str, num_repeats: int, delay_secs: float, hold_secs: float, ): """Send a list of commands to one device.""" device_id = None if device.isdigit(): _LOGGER.debug("%s: Device %s is numeric", self.name, device) if self._client.get_device_name(int(device)): device_id = device if device_id is None: _LOGGER.debug("%s: Find device ID %s based on device name", self.name, device) device_id = self._client.get_device_id(str(device).strip()) if device_id is None: _LOGGER.error("%s: Device %s is invalid", self.name, device) return _LOGGER.debug( "Sending commands to device %s holding for %s seconds " "with a delay of %s seconds", device, hold_secs, delay_secs, ) # Creating list of commands to send. snd_cmnd_list = [] for _ in range(num_repeats): for single_command in commands: send_command = SendCommandDevice(device=device_id, command=single_command, delay=hold_secs) snd_cmnd_list.append(send_command) if delay_secs > 0: snd_cmnd_list.append(float(delay_secs)) _LOGGER.debug("%s: Sending commands", self.name) try: result_list = await self._client.send_commands(snd_cmnd_list) except aioexc.TimeOut: _LOGGER.error("%s: Sending commands timed-out", self.name) return for result in result_list: _LOGGER.error( "Sending command %s to device %s failed with code %s: %s", result.command.command, result.command.device, result.code, result.msg, ) async def change_channel(self, channel: int): """Change the channel using Harmony remote.""" _LOGGER.debug("%s: Changing channel to %s", self.name, channel) try: await self._client.change_channel(channel) except aioexc.TimeOut: _LOGGER.error("%s: Changing channel to %s timed-out", self.name, channel) async def sync(self) -> bool: """Sync the Harmony device with the web service. Returns True if the sync was successful. """ _LOGGER.debug("%s: Syncing hub with Harmony cloud", self.name) try: await self._client.sync() except aioexc.TimeOut: _LOGGER.error("%s: Syncing hub with Harmony cloud timed-out", self.name) return False else: return True