Remove lag from Harmony remote platform (#10218)

* Simplify kwargs handling

* Move Harmony remote to a persistent connection with push feedback

* Make default delay_secs configurable on the harmony platform

* Remove lint

* Fix delay_secs with discovery

* Temporary location for updated pyharmony

* Remove lint

* Update pyharmony to 1.0.17

* Remove lint

* Return an Optional marker

* Update pyharmony to 1.0.18
This commit is contained in:
Anders Melchiorsen 2017-11-09 17:57:41 +01:00 committed by Martin Hjelmare
parent 68986e9143
commit dd16b7cac3
3 changed files with 67 additions and 75 deletions

View file

@ -61,8 +61,7 @@ REMOTE_SERVICE_SEND_COMMAND_SCHEMA = REMOTE_SERVICE_SCHEMA.extend({
vol.Optional(ATTR_DEVICE): cv.string, vol.Optional(ATTR_DEVICE): cv.string,
vol.Optional( vol.Optional(
ATTR_NUM_REPEATS, default=DEFAULT_NUM_REPEATS): cv.positive_int, ATTR_NUM_REPEATS, default=DEFAULT_NUM_REPEATS): cv.positive_int,
vol.Optional( vol.Optional(ATTR_DELAY_SECS): vol.Coerce(float),
ATTR_DELAY_SECS, default=DEFAULT_DELAY_SECS): vol.Coerce(float)
}) })
@ -141,25 +140,18 @@ def async_setup(hass, config):
def async_handle_remote_service(service): def async_handle_remote_service(service):
"""Handle calls to the remote services.""" """Handle calls to the remote services."""
target_remotes = component.async_extract_from_service(service) target_remotes = component.async_extract_from_service(service)
kwargs = service.data.copy()
activity_id = service.data.get(ATTR_ACTIVITY)
device = service.data.get(ATTR_DEVICE)
command = service.data.get(ATTR_COMMAND)
num_repeats = service.data.get(ATTR_NUM_REPEATS)
delay_secs = service.data.get(ATTR_DELAY_SECS)
update_tasks = [] update_tasks = []
for remote in target_remotes: for remote in target_remotes:
if service.service == SERVICE_TURN_ON: if service.service == SERVICE_TURN_ON:
yield from remote.async_turn_on(activity=activity_id) yield from remote.async_turn_on(**kwargs)
elif service.service == SERVICE_TOGGLE: elif service.service == SERVICE_TOGGLE:
yield from remote.async_toggle(activity=activity_id) yield from remote.async_toggle(**kwargs)
elif service.service == SERVICE_SEND_COMMAND: elif service.service == SERVICE_SEND_COMMAND:
yield from remote.async_send_command( yield from remote.async_send_command(**kwargs)
device=device, command=command,
num_repeats=num_repeats, delay_secs=delay_secs)
else: else:
yield from remote.async_turn_off(activity=activity_id) yield from remote.async_turn_off(**kwargs)
if not remote.should_poll: if not remote.should_poll:
continue continue

View file

@ -5,22 +5,23 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/remote.harmony/ https://home-assistant.io/components/remote.harmony/
""" """
import logging import logging
import asyncio
from os import path from os import path
import urllib.parse import time
import voluptuous as vol import voluptuous as vol
import homeassistant.components.remote as remote import homeassistant.components.remote as remote
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_PORT, ATTR_ENTITY_ID) CONF_NAME, CONF_HOST, CONF_PORT, ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.remote import ( from homeassistant.components.remote import (
PLATFORM_SCHEMA, DOMAIN, ATTR_DEVICE, ATTR_ACTIVITY, ATTR_NUM_REPEATS, PLATFORM_SCHEMA, DOMAIN, ATTR_DEVICE, ATTR_ACTIVITY, ATTR_NUM_REPEATS,
ATTR_DELAY_SECS) ATTR_DELAY_SECS, DEFAULT_DELAY_SECS)
from homeassistant.util import slugify from homeassistant.util import slugify
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
REQUIREMENTS = ['pyharmony==1.0.16'] REQUIREMENTS = ['pyharmony==1.0.18']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -35,6 +36,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string, vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(ATTR_ACTIVITY, default=None): cv.string, vol.Required(ATTR_ACTIVITY, default=None): cv.string,
vol.Optional(ATTR_DELAY_SECS, default=DEFAULT_DELAY_SECS):
vol.Coerce(float),
}) })
HARMONY_SYNC_SCHEMA = vol.Schema({ HARMONY_SYNC_SCHEMA = vol.Schema({
@ -44,8 +47,6 @@ HARMONY_SYNC_SCHEMA = vol.Schema({
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Harmony platform.""" """Set up the Harmony platform."""
import pyharmony
host = None host = None
activity = None activity = None
@ -61,6 +62,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
port = DEFAULT_PORT port = DEFAULT_PORT
if override: if override:
activity = override.get(ATTR_ACTIVITY) activity = override.get(ATTR_ACTIVITY)
delay_secs = override.get(ATTR_DELAY_SECS)
port = override.get(CONF_PORT, DEFAULT_PORT) port = override.get(CONF_PORT, DEFAULT_PORT)
host = ( host = (
@ -79,6 +81,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_PORT), config.get(CONF_PORT),
) )
activity = config.get(ATTR_ACTIVITY) activity = config.get(ATTR_ACTIVITY)
delay_secs = config.get(ATTR_DELAY_SECS)
else: else:
hass.data[CONF_DEVICE_CACHE].append(config) hass.data[CONF_DEVICE_CACHE].append(config)
return return
@ -86,26 +89,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
name, address, port = host name, address, port = host
_LOGGER.info("Loading Harmony Platform: %s at %s:%s, startup activity: %s", _LOGGER.info("Loading Harmony Platform: %s at %s:%s, startup activity: %s",
name, address, port, activity) name, address, port, activity)
try:
_LOGGER.debug("Calling pyharmony.ha_get_token for remote at: %s:%s",
address, port)
token = urllib.parse.quote_plus(pyharmony.ha_get_token(address, port))
_LOGGER.debug("Received token: %s", token)
except ValueError as err:
_LOGGER.warning("%s for remote: %s", err.args[0], name)
return False
harmony_conf_file = hass.config.path( harmony_conf_file = hass.config.path(
'{}{}{}'.format('harmony_', slugify(name), '.conf')) '{}{}{}'.format('harmony_', slugify(name), '.conf'))
try:
device = HarmonyRemote( device = HarmonyRemote(
name, address, port, name, address, port, activity, harmony_conf_file, delay_secs)
activity, harmony_conf_file, token)
DEVICES.append(device) DEVICES.append(device)
add_devices([device]) add_devices([device])
register_services(hass) register_services(hass)
return True except ValueError:
_LOGGER.warning("Failed to initialize remote: %s", name)
def register_services(hass): def register_services(hass):
@ -140,7 +134,7 @@ def _sync_service(service):
class HarmonyRemote(remote.RemoteDevice): class HarmonyRemote(remote.RemoteDevice):
"""Remote representation used to control a Harmony device.""" """Remote representation used to control a Harmony device."""
def __init__(self, name, host, port, activity, out_path, token): def __init__(self, name, host, port, activity, out_path, delay_secs):
"""Initialize HarmonyRemote class.""" """Initialize HarmonyRemote class."""
import pyharmony import pyharmony
from pathlib import Path from pathlib import Path
@ -152,20 +146,35 @@ class HarmonyRemote(remote.RemoteDevice):
self._state = None self._state = None
self._current_activity = None self._current_activity = None
self._default_activity = activity self._default_activity = activity
self._token = token self._client = pyharmony.get_client(host, port, self.new_activity)
self._config_path = out_path self._config_path = out_path
_LOGGER.debug("Retrieving harmony config using token: %s", token) self._config = self._client.get_config()
self._config = pyharmony.ha_get_config(self._token, host, port)
if not Path(self._config_path).is_file(): if not Path(self._config_path).is_file():
_LOGGER.debug("Writing harmony configuration to file: %s", _LOGGER.debug("Writing harmony configuration to file: %s",
out_path) out_path)
pyharmony.ha_write_config_file(self._config, self._config_path) pyharmony.ha_write_config_file(self._config, self._config_path)
self._delay_secs = delay_secs
@asyncio.coroutine
def async_added_to_hass(self):
"""Complete the initialization."""
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP,
lambda event: self._client.disconnect(wait=True))
# Poll for initial state
self.new_activity(self._client.get_current_activity())
@property @property
def name(self): def name(self):
"""Return the Harmony device's name.""" """Return the Harmony device's name."""
return self._name return self._name
@property
def should_poll(self):
"""Return the fact that we should not be polled."""
return False
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Add platform specific attributes.""" """Add platform specific attributes."""
@ -176,60 +185,51 @@ class HarmonyRemote(remote.RemoteDevice):
"""Return False if PowerOff is the current activity, otherwise True.""" """Return False if PowerOff is the current activity, otherwise True."""
return self._current_activity not in [None, 'PowerOff'] return self._current_activity not in [None, 'PowerOff']
def update(self): def new_activity(self, activity_id):
"""Return current activity.""" """Callback for updating the current activity."""
import pyharmony import pyharmony
name = self._name activity_name = pyharmony.activity_name(self._config, activity_id)
_LOGGER.debug("Polling %s for current activity", name) _LOGGER.debug("%s activity reported as: %s", self._name, activity_name)
state = pyharmony.ha_get_current_activity( self._current_activity = activity_name
self._token, self._config, self.host, self._port) self._state = bool(self._current_activity != 'PowerOff')
_LOGGER.debug("%s current activity reported as: %s", name, state) self.schedule_update_ha_state()
self._current_activity = state
self._state = bool(state != 'PowerOff')
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Start an activity from the Harmony device.""" """Start an activity from the Harmony device."""
import pyharmony import pyharmony
if kwargs[ATTR_ACTIVITY]: activity = kwargs.get(ATTR_ACTIVITY, self._default_activity)
activity = kwargs[ATTR_ACTIVITY]
else:
activity = self._default_activity
if activity: if activity:
pyharmony.ha_start_activity( activity_id = pyharmony.activity_id(self._config, activity)
self._token, self.host, self._port, self._config, activity) self._client.start_activity(activity_id)
self._state = True self._state = True
else: else:
_LOGGER.error("No activity specified with turn_on service") _LOGGER.error("No activity specified with turn_on service")
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Start the PowerOff activity.""" """Start the PowerOff activity."""
import pyharmony self._client.power_off()
pyharmony.ha_power_off(self._token, self.host, self._port)
def send_command(self, command, **kwargs): def send_command(self, commands, **kwargs):
"""Send a set of commands to one device.""" """Send a list of commands to one device."""
import pyharmony device = kwargs.get(ATTR_DEVICE)
device = kwargs.pop(ATTR_DEVICE, None)
if device is None: if device is None:
_LOGGER.error("Missing required argument: device") _LOGGER.error("Missing required argument: device")
return return
params = {}
num_repeats = kwargs.pop(ATTR_NUM_REPEATS, None) num_repeats = kwargs.get(ATTR_NUM_REPEATS)
if num_repeats is not None: delay_secs = kwargs.get(ATTR_DELAY_SECS, self._delay_secs)
params['repeat_num'] = num_repeats
delay_secs = kwargs.pop(ATTR_DELAY_SECS, None) for _ in range(num_repeats):
if delay_secs is not None: for command in commands:
params['delay_secs'] = delay_secs self._client.send_command(device, command)
pyharmony.ha_send_commands( time.sleep(delay_secs)
self._token, self.host, self._port, device, command, **params)
def sync(self): def sync(self):
"""Sync the Harmony device with the web service.""" """Sync the Harmony device with the web service."""
import pyharmony import pyharmony
_LOGGER.debug("Syncing hub with Harmony servers") _LOGGER.debug("Syncing hub with Harmony servers")
pyharmony.ha_sync(self._token, self.host, self._port) self._client.sync()
self._config = pyharmony.ha_get_config( self._config = self._client.get_config()
self._token, self.host, self._port)
_LOGGER.debug("Writing hub config to file: %s", self._config_path) _LOGGER.debug("Writing hub config to file: %s", self._config_path)
pyharmony.ha_write_config_file(self._config, self._config_path) pyharmony.ha_write_config_file(self._config, self._config_path)

View file

@ -666,7 +666,7 @@ pyflexit==0.3
pyfttt==0.3 pyfttt==0.3
# homeassistant.components.remote.harmony # homeassistant.components.remote.harmony
pyharmony==1.0.16 pyharmony==1.0.18
# homeassistant.components.binary_sensor.hikvision # homeassistant.components.binary_sensor.hikvision
pyhik==0.1.4 pyhik==0.1.4