Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use aioharmony for remote.harmony platform #19595

Merged

Conversation

ehendrix23
Copy link
Contributor

@ehendrix23 ehendrix23 commented Dec 26, 2018

Description:

Use new aioharmony instead of pyharmony. This provides the following benefits:

  • Full async support
  • No more polling for updates, when an activity is changed from another source (i.e. remote) then HASS will be updated through a notification
  • Set available state if for some reason disconnected from HUB.
  • Automatic reconnect to HUB if disconnected
  • When sending a command with repeat it will be send done as 1 "transaction" preventing anything else from HASS to send a command in the middle.
  • Automatic config update. If anything is changed on the HUB (i.e. device added, rename, ...) then it is detected and configuration is updated. The Harmony files with the configuration are automatically updates as well then.
  • Unique message identifications ensuring that HUB responses are correctly identified and processed

Related issue (if applicable):
fixes #19465, fixes #19466

Pull request in home-assistant.io with documentation (if applicable): home-assistant/home-assistant.io#7982

Checklist:

  • The code change is tested and works locally.
  • Local tests pass with tox. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.

If the code communicates with devices, web services, or third-party tools:

  • New dependencies have been added to the REQUIREMENTS variable ([example][ex-requir]).
  • New dependencies are only imported inside functions that use them ([example][ex-import]).
  • New or updated dependencies have been added to requirements_all.txt by running script/gen_requirements_all.py.

Use aioharmony to interact with Harmony hub. Due to this following improvements:
-) Setting of available state for entity
-) Automatic config update if configuration changes (including updating file containing config)
-) Allow using of device name instead of number
-) When sending command with repeat, nothing else will be able to put a IR command in between
self.new_config()
self._available = True

async def got_disconnected(self, _ = None):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unexpected spaces around keyword / parameter equals

self.schedule_update_ha_state()
self.write_config_file()

def got_connected(self, _ = None):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unexpected spaces around keyword / parameter equals

_LOGGER.debug("%s activity reported as: %s", self._name, activity_name)
self._current_activity = activity_name
self._state = bool(self._current_activity != 'PowerOff')
self.schedule_update_ha_state()

def new_config(self, _ = None):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unexpected spaces around keyword / parameter equals

Copy link
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service handler also uses the sync api in async context (old code).


_, self._current_activity = self._client.current_activity
self._state = bool(self._current_activity != 'PowerOff')
self.schedule_update_ha_state()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is called in async context we should use the async api, async_schedule_update_ha_state.

"""Call for updating the current activity."""
activity_name = self._client.get_activity_name(activity_id)
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(self._current_activity != 'PowerOff')
self.schedule_update_ha_state()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this called in async context?


if not self._available:
# Still disconnected. Let the state engine know.
self.schedule_update_ha_state()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

_, self._current_activity = self._client.current_activity
self._state = bool(self._current_activity != 'PowerOff')
self.schedule_update_ha_state()
self.write_config_file()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be run in the executor. It seems we're mixing things that possibly should be run in the event loop with things that should run in the thread pool.


self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown)

_LOGGER.debug("Connecting.")
await self._client.connect()
await self._client.get_config()
if not Path(self._config_path).is_file():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does I/O, so we have to run this in the executor.

@ehendrix23 ehendrix23 changed the title WIP: Use aioharmony for remote.harmony platform Use aioharmony for remote.harmony platform Dec 29, 2018
Small version bump increase for aioharmony
@@ -142,17 +137,17 @@ class HarmonyRemote(remote.RemoteDevice):
def __init__(self, name, host, port, activity, out_path, delay_secs):
"""Initialize HarmonyRemote class."""
from aioharmony.harmonyapi import (
HarmonyAPI as harmony_client, ClientCallbackType)

HarmonyAPI as Harmony_Client, ClientCallbackType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class names should use CapWords convention.

homeassistant/components/remote/harmony.py Show resolved Hide resolved
await self._client.sync()
except aioexc.TimeOut:
_LOGGER.error("%s: Syncing hub with Harmony cloud timed-out",
self.name)
await self.hass.async_add_executor_job(self.write_config_file)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we write the config even after error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, fixed. :-)

Copy link
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Should we merge?

@ehendrix23
Copy link
Contributor Author

@MartinHjelmare

That would be great, I have a few more PRs open for this platform. Each one dependent on the other one. Been busy over the holidays :-)
So if we merge this one then I can rebase the next one to get it ready for review/merge. :-)

Hoping to get all of them in for 0.85 release . :-)

@MartinHjelmare
Copy link
Member

Good! Can be merged when build passes.

@ehendrix23
Copy link
Contributor Author

@MartinHjelmare, build passed. :-)

@MartinHjelmare MartinHjelmare merged commit faeee4f into home-assistant:dev Dec 30, 2018
@ghost ghost removed the in progress label Dec 30, 2018
@ehendrix23 ehendrix23 deleted the Use_aioharmony_for_harmony branch December 30, 2018 02:54
@balloob balloob mentioned this pull request Jan 10, 2019
@steve28
Copy link

steve28 commented Jan 10, 2019

So I just updated last night, and I'm getting errors in the logs that reference pyharmony...

2019-01-09 21:37:53 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:37:58 WARNING (MainThread) [homeassistant.helpers.entity] Update of remote.living_room is taking over 10 seconds
2019-01-09 21:37:59 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:05 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:11 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:17 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:23 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:29 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:35 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:41 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:47 WARNING (MainThread) [homeassistant.components.remote] Updating harmony remote took longer than the scheduled update interval 0:00:05
2019-01-09 21:38:48 ERROR (MainThread) [homeassistant.helpers.entity] Update for remote.living_room fails
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/websockets/protocol.py", line 528, in transfer_data
    msg = yield from self.read_message()
  File "/usr/local/lib/python3.6/site-packages/websockets/protocol.py", line 580, in read_message
    frame = yield from self.read_data_frame(max_size=self.max_size)
  File "/usr/local/lib/python3.6/site-packages/websockets/protocol.py", line 645, in read_data_frame
    frame = yield from self.read_frame(max_size)
  File "/usr/local/lib/python3.6/site-packages/websockets/protocol.py", line 710, in read_frame
    extensions=self.extensions,
  File "/usr/local/lib/python3.6/site-packages/websockets/framing.py", line 100, in read
    data = yield from reader(2)
  File "/usr/local/lib/python3.6/asyncio/streams.py", line 672, in readexactly
    raise IncompleteReadError(incomplete, n)
asyncio.streams.IncompleteReadError: 0 bytes read on a total of 2 expected bytes

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/src/app/homeassistant/helpers/entity.py", line 221, in async_update_ha_state
    await self.async_device_update()
  File "/usr/src/app/homeassistant/helpers/entity.py", line 347, in async_device_update
    await self.async_update()
  File "/usr/src/app/homeassistant/components/remote/harmony.py", line 205, in async_update
    activity_id = await self._client.get_current_activity()
  File "/usr/local/lib/python3.6/site-packages/pyharmony/client.py", line 234, in get_current_activity
    'vnd.logitech.harmony/vnd.logitech.harmony.engine'
  File "/usr/local/lib/python3.6/site-packages/pyharmony/client.py", line 184, in _send_request
    return await self._wait_response(msgid)
  File "/usr/local/lib/python3.6/site-packages/pyharmony/client.py", line 190, in _wait_response
    response_json = await self._websocket.recv()
  File "/usr/local/lib/python3.6/site-packages/websockets/protocol.py", line 350, in recv
    yield from self.ensure_open()
  File "/usr/local/lib/python3.6/site-packages/websockets/protocol.py", line 501, in ensure_open
    self.close_code, self.close_reason) from self.transfer_data_exc
websockets.exceptions.ConnectionClosed: WebSocket connection is closed: code = 1006 (connection closed abnormally [internal]), no reason

Is this expected? Also, I still have issues where i get the above everytime I do anything with my remote...

I'm using HA in Docker on my Ubuntu server, if that matters.

@ehendrix23
Copy link
Contributor Author

@steve28, that does not look right. Like 205 in remote/harmony.py is:
return self._current_activity not in [None, 'PowerOff']
and not
activity_id = await self._client.get_current_activity()
as what you have there.

Assuming you are on 0.85 now?

@marchingphoenix
Copy link
Contributor

Please do not use merged PRs for bug reports. Please open a new issue and discuss it there.

@steve28
Copy link

steve28 commented Jan 10, 2019

@ehendrix23 yes, I am on 0.85 - anything you want me to check? I simply pulled the latest docker image

@ehendrix23
Copy link
Contributor Author

@steve28, as mentioned by @marchingphoenix, please open an issue first so we can discuss there.

@steve28
Copy link

steve28 commented Jan 11, 2019

Opened #19947

alandtse pushed a commit to alandtse/home-assistant that referenced this pull request Feb 12, 2019
* Use aioharmony for async

Use aioharmony to interact with Harmony hub. Due to this following improvements:
-) Setting of available state for entity
-) Automatic config update if configuration changes (including updating file containing config)
-) Allow using of device name instead of number
-) When sending command with repeat, nothing else will be able to put a IR command in between

* Requirements updated

* Version update for fix

* Mainly cleanup

* Update requirements

Updated requirements

* Fixed lint issue

* Small bump for aioharmony

Small version bump increase for aioharmony

* Updated based on review
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants