Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
async def getClient(ip_address):
    client = HarmonyAPI(ip_address)

    if await client.connect():
        return client

    return None
Ejemplo n.º 3
0
    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"
            )
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
async def get_client():
    client = HarmonyAPI(HUB_IP, HUB_PROTOCOL)
    try:
        if await client.connect():
            return client
    except:
        pass
    return None
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
 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
Ejemplo n.º 8
0
 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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
Archivo: remote.py Proyecto: 1e1/core-1
 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
Ejemplo n.º 12
0
    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))
Ejemplo n.º 13
0
    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
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
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,
            )
Ejemplo n.º 17
0
    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))
Ejemplo n.º 18
0
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