1 """Support for Rflink devices."""
3 from __future__
import annotations
6 from collections
import defaultdict
9 from rflink.protocol
import create_rflink_connection
10 from serial
import SerialException
11 import voluptuous
as vol
18 EVENT_HOMEASSISTANT_STOP,
20 from homeassistant.core import CoreState, HassJob, HomeAssistant, ServiceCall, callback
23 async_dispatcher_connect,
24 async_dispatcher_send,
31 DATA_ENTITY_GROUP_LOOKUP,
40 from .entity
import RflinkCommand
41 from .utils
import identify_event_type
43 _LOGGER = logging.getLogger(__name__)
45 CONF_IGNORE_DEVICES =
"ignore_devices"
46 CONF_RECONNECT_INTERVAL =
"reconnect_interval"
47 CONF_WAIT_FOR_ACK =
"wait_for_ack"
48 CONF_KEEPALIVE_IDLE =
"tcp_keepalive_idle_timer"
50 DEFAULT_RECONNECT_INTERVAL = 10
51 DEFAULT_TCP_KEEPALIVE_IDLE_TIMER = 3600
52 CONNECTION_TIMEOUT = 10
54 RFLINK_GROUP_COMMANDS = [
"allon",
"alloff"]
58 SERVICE_SEND_COMMAND =
"send_command"
60 SIGNAL_EVENT =
"rflink_event"
63 CONFIG_SCHEMA = vol.Schema(
67 vol.Required(CONF_PORT): vol.Any(cv.port, cv.string),
68 vol.Optional(CONF_HOST): cv.string,
69 vol.Optional(CONF_WAIT_FOR_ACK, default=
True): cv.boolean,
71 CONF_KEEPALIVE_IDLE, default=DEFAULT_TCP_KEEPALIVE_IDLE_TIMER
74 CONF_RECONNECT_INTERVAL, default=DEFAULT_RECONNECT_INTERVAL
76 vol.Optional(CONF_IGNORE_DEVICES, default=[]): vol.All(
77 cv.ensure_list, [cv.string]
82 extra=vol.ALLOW_EXTRA,
85 SEND_COMMAND_SCHEMA = vol.Schema(
86 {vol.Required(CONF_DEVICE_ID): cv.string, vol.Required(CONF_COMMAND): cv.string}
90 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
91 """Set up the Rflink component."""
94 hass.data[DATA_ENTITY_LOOKUP] = {
95 EVENT_KEY_COMMAND: defaultdict(list),
96 EVENT_KEY_SENSOR: defaultdict(list),
98 hass.data[DATA_ENTITY_GROUP_LOOKUP] = {EVENT_KEY_COMMAND: defaultdict(list)}
101 hass.data[DATA_DEVICE_REGISTER] = {}
104 """Send Rflink command."""
105 _LOGGER.debug(
"Rflink command for %s",
str(call.data))
107 await RflinkCommand.send_command(
108 call.data.get(CONF_DEVICE_ID), call.data.get(CONF_COMMAND)
111 _LOGGER.error(
"Failed Rflink command for %s",
str(call.data))
117 EVENT_KEY_ID: call.data.get(CONF_DEVICE_ID),
118 EVENT_KEY_COMMAND: call.data.get(CONF_COMMAND),
122 hass.services.async_register(
123 DOMAIN, SERVICE_SEND_COMMAND, async_send_command, schema=SEND_COMMAND_SCHEMA
127 def event_callback(event):
128 """Handle incoming Rflink events.
130 Rflink events arrive as dictionaries of varying content
131 depending on their type. Identify the events and distribute
135 _LOGGER.debug(
"event of type %s: %s", event_type, event)
138 if event_type
not in hass.data[DATA_ENTITY_LOOKUP]:
139 _LOGGER.debug(
"unhandled event of type: %s", event_type)
143 event_id = event.get(EVENT_KEY_ID)
146 event_type == EVENT_KEY_COMMAND
147 and event[EVENT_KEY_COMMAND]
in RFLINK_GROUP_COMMANDS
150 entity_ids = hass.data[DATA_ENTITY_GROUP_LOOKUP][event_type].
get(
154 entity_ids = hass.data[DATA_ENTITY_LOOKUP][event_type][event_id]
156 _LOGGER.debug(
"entity_ids: %s", entity_ids)
159 for entity
in entity_ids:
160 _LOGGER.debug(
"passing event to %s", entity)
162 elif not is_group_event:
164 if event_type
in hass.data[DATA_DEVICE_REGISTER]:
165 _LOGGER.debug(
"device_id not known, adding new device")
170 hass.data[DATA_ENTITY_LOOKUP][event_type][event_id].append(
171 TMP_ENTITY.format(event_id)
173 hass.async_create_task(
174 hass.data[DATA_DEVICE_REGISTER][event_type](event),
178 _LOGGER.debug(
"device_id not known and automatic add disabled")
181 host = config[DOMAIN].
get(CONF_HOST)
183 port = config[DOMAIN][CONF_PORT]
185 keepalive_idle_timer =
None
189 keepalive_idle_timer = config[DOMAIN][CONF_KEEPALIVE_IDLE]
190 if keepalive_idle_timer < 0:
193 "A bogus TCP Keepalive IDLE timer was provided (%d secs), "
194 "it will be disabled. "
195 "Recommended values: 60-3600 (seconds)"
197 keepalive_idle_timer,
199 keepalive_idle_timer =
None
200 elif keepalive_idle_timer == 0:
201 keepalive_idle_timer =
None
202 elif keepalive_idle_timer <= 30:
205 "A very short TCP Keepalive IDLE timer was provided (%d secs) "
206 "and may produce unexpected disconnections from RFlink device."
207 " Recommended values: 60-3600 (seconds)"
209 keepalive_idle_timer,
213 def reconnect(_: Exception |
None =
None) ->
None:
214 """Schedule reconnect after connection has been unexpectedly lost."""
216 RflinkCommand.set_rflink_protocol(
None)
221 if hass.state
is not CoreState.stopping:
222 _LOGGER.warning(
"Disconnected from Rflink, reconnecting")
223 hass.async_create_task(connect(), eager_start=
False)
225 _reconnect_job =
HassJob(reconnect,
"Rflink reconnect", cancel_on_shutdown=
True)
228 """Set up connection and hook it into HA for reconnect/shutdown."""
229 _LOGGER.debug(
"Initiating Rflink connection")
235 connection = create_rflink_connection(
238 keepalive=keepalive_idle_timer,
239 event_callback=event_callback,
240 disconnect_callback=reconnect,
242 ignore=config[DOMAIN][CONF_IGNORE_DEVICES],
246 async
with asyncio.timeout(CONNECTION_TIMEOUT):
247 transport, protocol = await connection
254 reconnect_interval = config[DOMAIN][CONF_RECONNECT_INTERVAL]
256 "Error connecting to Rflink, reconnecting in %s", reconnect_interval
269 RflinkCommand.set_rflink_protocol(protocol, config[DOMAIN][CONF_WAIT_FOR_ACK])
272 hass.bus.async_listen_once(
273 EVENT_HOMEASSISTANT_STOP,
lambda x: transport.close()
276 _LOGGER.debug(
"Connected to Rflink")
278 hass.async_create_task(connect(), eager_start=
False)
web.Response get(self, web.Request request, str config_key)
None async_send_command(HomeAssistant hass, Mapping[str, Any] data)
def identify_event_type(event)
bool async_setup(HomeAssistant hass, ConfigType config)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)