Reconnecting client: optional keep-alive & timeout
- lirc timeout, to reliably detect disconnect
This commit is contained in:
parent
feeefbe987
commit
349861ad7c
|
@ -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)
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue