diff --git a/custom_components/lirc_network/__init__.py b/custom_components/lirc_network/__init__.py index ca75216..65fa540 100644 --- a/custom_components/lirc_network/__init__.py +++ b/custom_components/lirc_network/__init__.py @@ -2,7 +2,9 @@ import logging import voluptuous as vol +from datetime import timedelta import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_time_interval from homeassistant.const import CONF_HOST, CONF_PORT from ..reconnecting_client import ReconnectingClient @@ -16,6 +18,8 @@ REPEAT_COUNTER = 'repeat_counter' LIRC_HOST = 'host' DATA_LIRC_NETWORK = 'data_lirc_network' +LIRC_TIMEOUT = 40 # in seconds + CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema(vol.All([{ vol.Required(CONF_HOST): cv.string, @@ -39,7 +43,17 @@ class LircConnection(ReconnectingClient): def __init__(self, hass, config): super().__init__(hass, config[CONF_HOST], config[CONF_PORT], "lirc_network", receive_line_callback=self._process_line, - connection_status_changed_callback=self._connection_state_changed) + connection_status_changed_callback=self._connection_state_changed, + timeout=LIRC_TIMEOUT) + self._multiline_response_in_progress = False + self._multiline_response = "" + + async def keep_alive_callback(*args, **kwargs): + if self._writer and self.connected: + self._writer.write("VERSION\n".encode()) + await self._writer.drain() + + async_track_time_interval(hass, keep_alive_callback, timedelta(seconds=LIRC_TIMEOUT / 2)) async def _connection_state_changed(self, _): pass @@ -48,17 +62,27 @@ class LircConnection(ReconnectingClient): # Example msg: # 0000000000001795 00 Down Hauppauge_350 # 0000000000001795 01 Down Hauppauge_350 - splitted_line = line.split() - if len(splitted_line) != 4: - _LOGGER.warning(f'Ignoring LIRC message from host {self._host}: "{line}"') - return + if line.startswith("Unknown LIRC Command received: VERSION"): + pass # response of irserver to VERSION query + elif line.strip() == "BEGIN": + self._multiline_response_in_progress = True + elif self._multiline_response_in_progress and line.strip() == "END": + self._multiline_response_in_progress = False + self._multiline_response = "" + elif self._multiline_response_in_progress: + self._multiline_response += line else: - code, repeat_counter, key_name, remote_name = splitted_line - repeat_counter = int(repeat_counter, 16) # repeat code is hexadecimal - key_name = key_name.lower() - data = {BUTTON_NAME: key_name, - REMOTE_NAME: remote_name, - REPEAT_COUNTER: repeat_counter, - LIRC_HOST: self._host} - _LOGGER.info(f"Got new LIRC network code {data}") - self.hass.bus.fire(EVENT_IR_COMMAND_RECEIVED, data) + splitted_line = line.split() + if len(splitted_line) != 4: + _LOGGER.warning(f'Ignoring LIRC message from host {self._host}: "{line}"') + return + else: + code, repeat_counter, key_name, remote_name = splitted_line + repeat_counter = int(repeat_counter, 16) # repeat code is hexadecimal + key_name = key_name.lower() + data = {BUTTON_NAME: key_name, + REMOTE_NAME: remote_name, + REPEAT_COUNTER: repeat_counter, + LIRC_HOST: self._host} + _LOGGER.info(f"Got new LIRC network code {data}") + self.hass.bus.fire(EVENT_IR_COMMAND_RECEIVED, data) diff --git a/custom_components/reconnecting_client.py b/custom_components/reconnecting_client.py index f5a1f3d..c61de2b 100644 --- a/custom_components/reconnecting_client.py +++ b/custom_components/reconnecting_client.py @@ -2,6 +2,7 @@ import asyncio import logging from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.components.binary_sensor import BinarySensorDevice +import socket class IsConnectedSensor(BinarySensorDevice): @@ -12,16 +13,16 @@ class IsConnectedSensor(BinarySensorDevice): async def set_value(self, value): self._on = value - await self.async_update_ha_state() + # this doesn't work sometimes - but polling is enabled anyway + try: + await asyncio.wait_for(self.async_update_ha_state(), 1) + except Exception: + pass @property def name(self): return self._name - @property - def should_poll(self): - return False - @property def available(self) -> bool: return True @@ -30,13 +31,20 @@ class IsConnectedSensor(BinarySensorDevice): def is_on(self): return self._on + def refresh(self): + pass + + def update(self): + pass + _LOGGER = logging.getLogger(__name__) class ReconnectingClient: - def __init__(self, hass, host, port, connection_name, receive_line_callback, connection_status_changed_callback): + def __init__(self, hass, host, port, connection_name, receive_line_callback, + connection_status_changed_callback, timeout=None): self.connected = False self.reconnect_time_start = 1 self.reconnect_time_max = 60 @@ -52,6 +60,7 @@ class ReconnectingClient: self._connection_name = connection_name self._connection_task = None self._connected_sensor = None + self._timeout = timeout @property def connected_sensor(self): @@ -100,23 +109,25 @@ class ReconnectingClient: self.reconnect_time = self.reconnect_time_start while self._run: - line = await reader.readline() + if self._timeout: + line = await asyncio.wait_for(reader.readline(), self._timeout) + else: + line = await reader.readline() if not line: raise OSError("Disconnect") line = line.decode() _LOGGER.debug(f"{self._connection_name} received line - passing along '{line}'") await self._receive_line_callback(line) - except OSError as e: + except (OSError, asyncio.TimeoutError): if self._connection_last_state != 'FAILED': - notification_text = "{} connection to {}:{} failed".format(self._connection_name, self._host, - self._port) + notification_text = f"{self._connection_name} connection to {self._host}:{self._port} failed" self.hass.components.persistent_notification.async_create(notification_text, title="No connection") - _LOGGER.error("Connection to {} failed {}:{}".format(self._connection_name, self._host, self._port)) + _LOGGER.error(f"Connection to {self._connection_name} failed {self._host}:{self._port}") await self._connection_status_changed_callback('disconnected') if self._connected_sensor: - await self._connected_sensor.set_value(False) - _LOGGER.error( - "After connection failed ({} {}:{})".format(self._connection_name, self._host, self._port)) + await asyncio.wait_for(self._connected_sensor.set_value(False), timeout=2.0) + _LOGGER.error(f"After setting sensor ({self._connection_name} {self._host}:{self._port})") + _LOGGER.error(f"After connection failed ({self._connection_name} {self._host}:{self._port})") else: _LOGGER.debug(f"{self._connection_name} retried connection, last state {self._connection_last_state}" f"reconnection time {self.reconnect_time}") diff --git a/custom_components/squeezebox_telnet/media_player.py b/custom_components/squeezebox_telnet/media_player.py index 3749dcb..712eabe 100644 --- a/custom_components/squeezebox_telnet/media_player.py +++ b/custom_components/squeezebox_telnet/media_player.py @@ -441,9 +441,6 @@ class SqueezeBoxDevice(MediaPlayerDevice): """Flag media player features that are supported.""" return SUPPORT_SQUEEZEBOX - def turn_off(self): - self.call_method('power', '0') - def volume_up(self): self.call_method('mixer', 'volume', '+5') @@ -479,6 +476,9 @@ class SqueezeBoxDevice(MediaPlayerDevice): def turn_on(self): self.call_method('power', '1') + def turn_off(self): + self.call_method('power', '0') + def play_media(self, media_type, media_id, **kwargs): """ Send the play_media command to the media player.