Use asyncio in XiaomiAqara instead of threading (#74979)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
starkillerOG 2022-09-15 08:36:56 +02:00 committed by GitHub
parent 84a812ad05
commit ade4fcaebd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 53 additions and 37 deletions

View File

@ -1,12 +1,13 @@
"""Support for Xiaomi Gateways.""" """Support for Xiaomi Gateways."""
import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
import voluptuous as vol import voluptuous as vol
from xiaomi_gateway import XiaomiGateway, XiaomiGatewayDiscovery from xiaomi_gateway import AsyncXiaomiGatewayMulticast, XiaomiGateway
from homeassistant.components import persistent_notification from homeassistant.components import persistent_notification
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import ( from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_BATTERY_LEVEL,
ATTR_DEVICE_ID, ATTR_DEVICE_ID,
@ -34,6 +35,8 @@ from .const import (
DEFAULT_DISCOVERY_RETRY, DEFAULT_DISCOVERY_RETRY,
DOMAIN, DOMAIN,
GATEWAYS_KEY, GATEWAYS_KEY,
KEY_SETUP_LOCK,
KEY_UNSUB_STOP,
LISTENER_KEY, LISTENER_KEY,
) )
@ -143,6 +146,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the xiaomi aqara components from a config entry.""" """Set up the xiaomi aqara components from a config entry."""
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
setup_lock = hass.data[DOMAIN].setdefault(KEY_SETUP_LOCK, asyncio.Lock())
hass.data[DOMAIN].setdefault(GATEWAYS_KEY, {}) hass.data[DOMAIN].setdefault(GATEWAYS_KEY, {})
# Connect to Xiaomi Aqara Gateway # Connect to Xiaomi Aqara Gateway
@ -158,24 +162,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) )
hass.data[DOMAIN][GATEWAYS_KEY][entry.entry_id] = xiaomi_gateway hass.data[DOMAIN][GATEWAYS_KEY][entry.entry_id] = xiaomi_gateway
gateway_discovery = hass.data[DOMAIN].setdefault( async with setup_lock:
LISTENER_KEY, if LISTENER_KEY not in hass.data[DOMAIN]:
XiaomiGatewayDiscovery(hass.add_job, [], entry.data[CONF_INTERFACE]), multicast = AsyncXiaomiGatewayMulticast(
) interface=entry.data[CONF_INTERFACE]
)
hass.data[DOMAIN][LISTENER_KEY] = multicast
if len(hass.data[DOMAIN][GATEWAYS_KEY]) == 1: # start listining for local pushes (only once)
# start listining for local pushes (only once) await multicast.start_listen()
await hass.async_add_executor_job(gateway_discovery.listen)
# register stop callback to shutdown listining for local pushes # register stop callback to shutdown listining for local pushes
def stop_xiaomi(event): @callback
"""Stop Xiaomi Socket.""" def stop_xiaomi(event):
_LOGGER.debug("Shutting down Xiaomi Gateway Listener") """Stop Xiaomi Socket."""
gateway_discovery.stop_listen() _LOGGER.debug("Shutting down Xiaomi Gateway Listener")
multicast.stop_listen()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_xiaomi) unsub = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_xiaomi)
hass.data[DOMAIN][KEY_UNSUB_STOP] = unsub
gateway_discovery.gateways[entry.data[CONF_HOST]] = xiaomi_gateway multicast = hass.data[DOMAIN][LISTENER_KEY]
multicast.register_gateway(entry.data[CONF_HOST], xiaomi_gateway.multicast_callback)
_LOGGER.debug( _LOGGER.debug(
"Gateway with host '%s' connected, listening for broadcasts", "Gateway with host '%s' connected, listening for broadcasts",
entry.data[CONF_HOST], entry.data[CONF_HOST],
@ -201,23 +209,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if entry.data[CONF_KEY] is not None: if config_entry.data[CONF_KEY] is not None:
platforms = GATEWAY_PLATFORMS platforms = GATEWAY_PLATFORMS
else: else:
platforms = GATEWAY_PLATFORMS_NO_KEY platforms = GATEWAY_PLATFORMS_NO_KEY
unload_ok = await hass.config_entries.async_unload_platforms(entry, platforms) unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, platforms
)
if unload_ok: if unload_ok:
hass.data[DOMAIN][GATEWAYS_KEY].pop(entry.entry_id) hass.data[DOMAIN][GATEWAYS_KEY].pop(config_entry.entry_id)
if len(hass.data[DOMAIN][GATEWAYS_KEY]) == 0: loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
# No gateways left, stop Xiaomi socket # No gateways left, stop Xiaomi socket
unsub_stop = hass.data[DOMAIN].pop(KEY_UNSUB_STOP)
unsub_stop()
hass.data[DOMAIN].pop(GATEWAYS_KEY) hass.data[DOMAIN].pop(GATEWAYS_KEY)
_LOGGER.debug("Shutting down Xiaomi Gateway Listener") _LOGGER.debug("Shutting down Xiaomi Gateway Listener")
gateway_discovery = hass.data[DOMAIN].pop(LISTENER_KEY) multicast = hass.data[DOMAIN].pop(LISTENER_KEY)
await hass.async_add_executor_job(gateway_discovery.stop_listen) multicast.stop_listen()
return unload_ok return unload_ok
@ -262,12 +279,9 @@ class XiaomiDevice(Entity):
self._is_gateway = False self._is_gateway = False
self._device_id = self._sid self._device_id = self._sid
def _add_push_data_job(self, *args):
self.hass.add_job(self.push_data, *args)
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Start unavailability tracking.""" """Start unavailability tracking."""
self._xiaomi_hub.callbacks[self._sid].append(self._add_push_data_job) self._xiaomi_hub.callbacks[self._sid].append(self.push_data)
self._async_track_unavailable() self._async_track_unavailable()
@property @property

View File

@ -287,7 +287,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
) )
if self.entity_id is not None: if self.entity_id is not None:
self._hass.bus.fire( self._hass.bus.async_fire(
"xiaomi_aqara.motion", {"entity_id": self.entity_id} "xiaomi_aqara.motion", {"entity_id": self.entity_id}
) )
@ -473,7 +473,7 @@ class XiaomiVibration(XiaomiBinarySensor):
_LOGGER.warning("Unsupported movement_type detected: %s", value) _LOGGER.warning("Unsupported movement_type detected: %s", value)
return False return False
self.hass.bus.fire( self.hass.bus.async_fire(
"xiaomi_aqara.movement", "xiaomi_aqara.movement",
{"entity_id": self.entity_id, "movement_type": value}, {"entity_id": self.entity_id, "movement_type": value},
) )
@ -533,7 +533,7 @@ class XiaomiButton(XiaomiBinarySensor):
_LOGGER.warning("Unsupported click_type detected: %s", value) _LOGGER.warning("Unsupported click_type detected: %s", value)
return False return False
self._hass.bus.fire( self._hass.bus.async_fire(
"xiaomi_aqara.click", "xiaomi_aqara.click",
{"entity_id": self.entity_id, "click_type": click_type}, {"entity_id": self.entity_id, "click_type": click_type},
) )
@ -570,7 +570,7 @@ class XiaomiCube(XiaomiBinarySensor):
def parse_data(self, data, raw_data): def parse_data(self, data, raw_data):
"""Parse data sent by gateway.""" """Parse data sent by gateway."""
if self._data_key in data: if self._data_key in data:
self._hass.bus.fire( self._hass.bus.async_fire(
"xiaomi_aqara.cube_action", "xiaomi_aqara.cube_action",
{"entity_id": self.entity_id, "action_type": data[self._data_key]}, {"entity_id": self.entity_id, "action_type": data[self._data_key]},
) )
@ -582,7 +582,7 @@ class XiaomiCube(XiaomiBinarySensor):
if isinstance(data["rotate"], int) if isinstance(data["rotate"], int)
else data["rotate"].replace(",", ".") else data["rotate"].replace(",", ".")
) )
self._hass.bus.fire( self._hass.bus.async_fire(
"xiaomi_aqara.cube_action", "xiaomi_aqara.cube_action",
{ {
"entity_id": self.entity_id, "entity_id": self.entity_id,
@ -598,7 +598,7 @@ class XiaomiCube(XiaomiBinarySensor):
if isinstance(data["rotate_degree"], int) if isinstance(data["rotate_degree"], int)
else data["rotate_degree"].replace(",", ".") else data["rotate_degree"].replace(",", ".")
) )
self._hass.bus.fire( self._hass.bus.async_fire(
"xiaomi_aqara.cube_action", "xiaomi_aqara.cube_action",
{ {
"entity_id": self.entity_id, "entity_id": self.entity_id,

View File

@ -106,7 +106,7 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self.async_step_settings() return await self.async_step_settings()
# Discover Xiaomi Aqara Gateways in the netwerk to get required SIDs. # Discover Xiaomi Aqara Gateways in the netwerk to get required SIDs.
xiaomi = XiaomiGatewayDiscovery(self.hass.add_job, [], self.interface) xiaomi = XiaomiGatewayDiscovery(self.interface)
try: try:
await self.hass.async_add_executor_job(xiaomi.discover_gateways) await self.hass.async_add_executor_job(xiaomi.discover_gateways)
except gaierror: except gaierror:

View File

@ -4,6 +4,8 @@ DOMAIN = "xiaomi_aqara"
GATEWAYS_KEY = "gateways" GATEWAYS_KEY = "gateways"
LISTENER_KEY = "listener" LISTENER_KEY = "listener"
KEY_UNSUB_STOP = "unsub_stop"
KEY_SETUP_LOCK = "setup_lock"
ZEROCONF_GATEWAY = "lumi-gateway" ZEROCONF_GATEWAY = "lumi-gateway"
ZEROCONF_ACPARTNER = "lumi-acpartner" ZEROCONF_ACPARTNER = "lumi-acpartner"

View File

@ -3,7 +3,7 @@
"name": "Xiaomi Gateway (Aqara)", "name": "Xiaomi Gateway (Aqara)",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/xiaomi_aqara", "documentation": "https://www.home-assistant.io/integrations/xiaomi_aqara",
"requirements": ["PyXiaomiGateway==0.13.4"], "requirements": ["PyXiaomiGateway==0.14.1"],
"after_dependencies": ["discovery"], "after_dependencies": ["discovery"],
"codeowners": ["@danielhiversen", "@syssi"], "codeowners": ["@danielhiversen", "@syssi"],
"zeroconf": ["_miio._udp.local."], "zeroconf": ["_miio._udp.local."],

View File

@ -50,7 +50,7 @@ PyTurboJPEG==1.6.7
PyViCare==2.17.0 PyViCare==2.17.0
# homeassistant.components.xiaomi_aqara # homeassistant.components.xiaomi_aqara
PyXiaomiGateway==0.13.4 PyXiaomiGateway==0.14.1
# homeassistant.components.remember_the_milk # homeassistant.components.remember_the_milk
RtmAPI==0.7.2 RtmAPI==0.7.2

View File

@ -46,7 +46,7 @@ PyTurboJPEG==1.6.7
PyViCare==2.17.0 PyViCare==2.17.0
# homeassistant.components.xiaomi_aqara # homeassistant.components.xiaomi_aqara
PyXiaomiGateway==0.13.4 PyXiaomiGateway==0.14.1
# homeassistant.components.remember_the_milk # homeassistant.components.remember_the_milk
RtmAPI==0.7.2 RtmAPI==0.7.2