This commit is contained in:
Paul Shawn 2024-12-29 04:47:49 +00:00 committed by GitHub
commit 5ed7be0ad5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 332 additions and 165 deletions

View File

@ -122,10 +122,10 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_auth_info: dict
_nick_name: str
_home_selected: dict
_devices_filter: dict
_home_info_buffer: dict
_home_list_show: dict
_device_list_sorted: dict
_device_list_filter: dict
_cloud_server: str
_oauth_redirect_url_full: str
@ -630,7 +630,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
room_list_in: list = user_input.get('room_list', [])
if room_list_in:
if user_input.get(
'room_filter_mode', 'include') == 'include':
'room_filter_mode', 'exclude') == 'include':
include_items['room_id'] = room_list_in
else:
exclude_items['room_id'] = room_list_in
@ -638,7 +638,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
type_list_in: list = user_input.get('type_list', [])
if type_list_in:
if user_input.get(
'type_filter_mode', 'include') == 'include':
'type_filter_mode', 'exclude') == 'include':
include_items['connect_type'] = type_list_in
else:
exclude_items['connect_type'] = type_list_in
@ -646,7 +646,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
model_list_in: list = user_input.get('model_list', [])
if model_list_in:
if user_input.get(
'model_filter_mode', 'include') == 'include':
'model_filter_mode', 'exclude') == 'include':
include_items['model'] = model_list_in
else:
exclude_items['model'] = model_list_in
@ -654,7 +654,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
device_list_in: list = user_input.get('device_list', [])
if device_list_in:
if user_input.get(
'devices_filter_mode', 'include') == 'include':
'devices_filter_mode', 'exclude') == 'include':
include_items['did'] = device_list_in
else:
exclude_items['did'] = device_list_in
@ -663,10 +663,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
logic_or=(user_input.get('statistics_logic', 'or') == 'or'),
item_in=include_items, item_ex=exclude_items)
if not device_filter_list:
raise AbortFlow(
reason='config_flow_error',
description_placeholders={
'error': 'invalid devices_filter'})
return await self.__display_devices_filter_form(
reason='no_filter_devices')
self._device_list_sorted = dict(sorted(
device_filter_list.items(), key=lambda item:
item[1].get('home_id', '')+item[1].get('room_id', '')))
@ -675,13 +673,31 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
domain='miot_devices',
name=f'{self._uid}_{self._cloud_server}',
data=self._device_list_sorted):
return await self.__display_devices_filter_form(
reason='no_devices_selected')
_LOGGER.error(
'save devices async failed, %s, %s',
self._uid, self._cloud_server)
raise AbortFlow(
reason='storage_error', description_placeholders={
'error': 'save user devices error'})
self._devices_filter = {
'room_list': {
'items': room_list_in,
'mode': user_input.get('room_filter_mode', 'exclude')},
'type_list': {
'items': type_list_in,
'mode': user_input.get('type_filter_mode', 'exclude')},
'model_list': {
'items': model_list_in,
'mode': user_input.get('model_filter_mode', 'exclude')},
'device_list': {
'items': device_list_in,
'mode': user_input.get('devices_filter_mode', 'exclude')},
'statistics_logic': user_input.get('statistics_logic', 'or'),
}
return await self.config_flow_done()
return await self.__display_devices_filter_form(reason='')
async def __display_devices_filter_form(self, reason: str):
tip_devices: str = self._miot_i18n.translate(
key='config.other.devices') # type: ignore
tip_without_room: str = self._miot_i18n.translate(
@ -771,6 +787,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
'oauth_redirect_url': self._oauth_redirect_url_full,
'ctrl_mode': self._ctrl_mode,
'home_selected': self._home_selected,
'devices_filter': self._devices_filter,
'area_name_rule': self._area_name_rule,
'action_debug': self._action_debug,
'hide_non_standard_entities':
@ -813,6 +830,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
_ctrl_mode: str
_nick_name: str
_home_selected_list: list
_devices_filter: dict
_action_debug: bool
_hide_non_standard_entities: bool
_display_devs_notify: list[str]
@ -865,6 +883,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
'display_devices_changed_notify', ['add', 'del', 'offline'])
self._home_selected_list = list(
self._entry_data['home_selected'].keys())
self._devices_filter = self._entry_data.get('devices_filter', {})
self._oauth_redirect_url_full = ''
self._auth_info = {}
@ -1307,48 +1326,61 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
include_items: dict = {}
exclude_items: dict = {}
room_list_in: list = user_input.get('room_list', [])
room_filter_mode: str = user_input.get(
'room_filter_mode', 'exclude')
if room_list_in:
if user_input.get(
'room_filter_mode', 'include') == 'include':
if room_filter_mode == 'include':
include_items['room_id'] = room_list_in
else:
exclude_items['room_id'] = room_list_in
# Connect Type filter
type_list_in: list = user_input.get('type_list', [])
type_filter_mode: str = user_input.get(
'type_filter_mode', 'exclude')
if type_list_in:
if user_input.get(
'type_filter_mode', 'include') == 'include':
if type_filter_mode == 'include':
include_items['connect_type'] = type_list_in
else:
exclude_items['connect_type'] = type_list_in
# Model filter
model_list_in: list = user_input.get('model_list', [])
model_filter_mode: str = user_input.get(
'model_filter_mode', 'exclude')
if model_list_in:
if user_input.get(
'model_filter_mode', 'include') == 'include':
if model_filter_mode == 'include':
include_items['model'] = model_list_in
else:
exclude_items['model'] = model_list_in
# Device filter
device_list_in: list = user_input.get('device_list', [])
device_filter_mode: str = user_input.get(
'devices_filter_mode', 'exclude')
if device_list_in:
if user_input.get(
'devices_filter_mode', 'include') == 'include':
if device_filter_mode == 'include':
include_items['did'] = device_list_in
else:
exclude_items['did'] = device_list_in
statistics_logic: str = user_input.get('statistics_logic', 'or')
device_filter_list = _handle_devices_filter(
devices=self._device_list_sorted,
logic_or=(user_input.get('statistics_logic', 'or') == 'or'),
logic_or=(statistics_logic == 'or'),
item_in=include_items, item_ex=exclude_items)
if not device_filter_list:
raise AbortFlow(
reason='config_flow_error',
description_placeholders={
'error': 'invalid devices_filter'})
return await self.__display_devices_filter_form(
reason='no_filter_devices')
self._device_list_sorted = dict(sorted(
device_filter_list.items(), key=lambda item:
item[1].get('home_id', '')+item[1].get('room_id', '')))
self._devices_filter = {
'room_list': {
'items': room_list_in, 'mode': room_filter_mode},
'type_list': {
'items': type_list_in, 'mode': type_filter_mode},
'model_list': {
'items': model_list_in, 'mode': model_filter_mode},
'device_list': {
'items': device_list_in, 'mode': device_filter_mode},
'statistics_logic': statistics_logic}
return await self.update_devices_done_async()
return await self.__display_devices_filter_form(reason='')
@ -1401,25 +1433,48 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
step_id='devices_filter',
data_schema=vol.Schema({
vol.Required(
'room_filter_mode', default='exclude' # type: ignore
'room_filter_mode', default=self._devices_filter.get(
'room_list', {}).get('mode', 'exclude') # type: ignore
): vol.In(trans_filter_mode),
vol.Optional('room_list'): cv.multi_select(room_list),
vol.Optional('room_list', default=[
room_id for room_id in self._devices_filter.get(
'room_list', {}).get('items', [])
if room_id in room_list] # type: ignore
): cv.multi_select(room_list),
vol.Required(
'type_filter_mode', default='exclude' # type: ignore
'type_filter_mode', default=self._devices_filter.get(
'type_list', {}).get('mode', 'exclude') # type: ignore
): vol.In(trans_filter_mode),
vol.Optional('type_list'): cv.multi_select(type_list),
vol.Optional('type_list', default=[
type_ for type_ in self._devices_filter.get(
'type_list', {}).get('items', [])
if type_ in type_list] # type: ignore
): cv.multi_select(type_list),
vol.Required(
'model_filter_mode', default='exclude' # type: ignore
'model_filter_mode',
default=self._devices_filter.get('model_list', {}).get(
'mode', 'exclude') # type: ignore
): vol.In(trans_filter_mode),
vol.Optional('model_list'): cv.multi_select(dict(sorted(
vol.Optional('model_list', default=[
model for model in self._devices_filter.get(
'model_list', {}).get('items', [])
if model in model_list] # type: ignore
): cv.multi_select(dict(sorted(
model_list.items(), key=lambda item: item[0]))),
vol.Required(
'devices_filter_mode', default='exclude' # type: ignore
'devices_filter_mode', default=self._devices_filter.get(
'device_list', {}).get(
'mode', 'exclude') # type: ignore
): vol.In(trans_filter_mode),
vol.Optional('device_list'): cv.multi_select(dict(sorted(
vol.Optional('device_list', default=[
did for did in self._devices_filter.get(
'device_list', {}).get('items', [])
if did in device_list] # type: ignore
): cv.multi_select(dict(sorted(
device_list.items(), key=lambda device: device[1]))),
vol.Required(
'statistics_logic', default='or' # type: ignore
'statistics_logic', default=self._devices_filter.get(
'statistics_logic', 'or')
): vol.In(trans_statistics_logic),
}),
errors={'base': reason},
@ -1590,6 +1645,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
if self._update_devices:
self._entry_data['ctrl_mode'] = self._ctrl_mode
self._entry_data['home_selected'] = self._home_selected
self._entry_data['devices_filter'] = self._devices_filter
if not await self._miot_storage.save_async(
domain='miot_devices',
name=f'{self._uid}_{self._cloud_server}',

View File

@ -1851,15 +1851,6 @@ async def get_miot_instance_async(
loop: asyncio.AbstractEventLoop = asyncio.get_running_loop()
if loop is None:
raise MIoTClientError('loop is None')
# MIoT network
network: Optional[MIoTNetwork] = hass.data[DOMAIN].get(
'miot_network', None)
if not network:
network = MIoTNetwork(loop=loop)
hass.data[DOMAIN]['miot_network'] = network
await network.init_async(
refresh_interval=NETWORK_REFRESH_INTERVAL)
_LOGGER.info('create miot_network instance')
# MIoT storage
storage: Optional[MIoTStorage] = hass.data[DOMAIN].get(
'miot_storage', None)
@ -1868,12 +1859,29 @@ async def get_miot_instance_async(
root_path=entry_data['storage_path'], loop=loop)
hass.data[DOMAIN]['miot_storage'] = storage
_LOGGER.info('create miot_storage instance')
global_config: dict = await storage.load_user_config_async(
uid='global_config', cloud_server='all',
keys=['network_detect_addr', 'net_interfaces', 'enable_subscribe'])
# MIoT network
network_detect_addr: dict = global_config.get(
'network_detect_addr', {})
network: Optional[MIoTNetwork] = hass.data[DOMAIN].get(
'miot_network', None)
if not network:
network = MIoTNetwork(
ip_addr_list=network_detect_addr.get('ip', []),
http_addr_list=network_detect_addr.get('http', []),
refresh_interval=NETWORK_REFRESH_INTERVAL,
loop=loop)
hass.data[DOMAIN]['miot_network'] = network
await network.init_async()
_LOGGER.info('create miot_network instance')
# MIoT service
mips_service: Optional[MipsService] = hass.data[DOMAIN].get(
'mips_service', None)
if not mips_service:
aiozc = await zeroconf.async_get_async_instance(hass)
mips_service: MipsService = MipsService(aiozc=aiozc, loop=loop)
mips_service = MipsService(aiozc=aiozc, loop=loop)
hass.data[DOMAIN]['mips_service'] = mips_service
await mips_service.init_async()
_LOGGER.info('create mips_service instance')
@ -1881,15 +1889,11 @@ async def get_miot_instance_async(
miot_lan: Optional[MIoTLan] = hass.data[DOMAIN].get(
'miot_lan', None)
if not miot_lan:
lan_config = (await storage.load_user_config_async(
uid='global_config',
cloud_server='all',
keys=['net_interfaces', 'enable_subscribe'])) or {}
miot_lan = MIoTLan(
net_ifs=lan_config.get('net_interfaces', []),
net_ifs=global_config.get('net_interfaces', []),
network=network,
mips_service=mips_service,
enable_subscribe=lan_config.get('enable_subscribe', False),
enable_subscribe=global_config.get('enable_subscribe', False),
loop=loop)
hass.data[DOMAIN]['miot_lan'] = miot_lan
_LOGGER.info('create miot_lan instance')

View File

@ -224,20 +224,20 @@ class MIoTHttpClient:
_client_id: str
_access_token: str
_get_prop_timer: asyncio.TimerHandle
_get_prop_list: dict[str, dict[str, asyncio.Future | str | bool]]
_get_prop_timer: Optional[asyncio.TimerHandle]
_get_prop_list: dict[str, dict]
def __init__(
self, cloud_server: str, client_id: str, access_token: str,
loop: Optional[asyncio.AbstractEventLoop] = None
) -> None:
self._main_loop = loop or asyncio.get_running_loop()
self._host = None
self._base_url = None
self._client_id = None
self._access_token = None
self._host = DEFAULT_OAUTH2_API_HOST
self._base_url = ''
self._client_id = ''
self._access_token = ''
self._get_prop_timer: asyncio.TimerHandle = None
self._get_prop_timer = None
self._get_prop_list = {}
if (
@ -258,8 +258,9 @@ class MIoTHttpClient:
self._get_prop_timer.cancel()
self._get_prop_timer = None
for item in self._get_prop_list.values():
fut: asyncio.Future = item.get('fut')
fut.cancel()
fut: Optional[asyncio.Future] = item.get('fut', None)
if fut:
fut.cancel()
self._get_prop_list.clear()
if self._session and not self._session.closed:
await self._session.close()
@ -270,9 +271,7 @@ class MIoTHttpClient:
access_token: Optional[str] = None
) -> None:
if isinstance(cloud_server, str):
if cloud_server == 'cn':
self._host = DEFAULT_OAUTH2_API_HOST
else:
if cloud_server != 'cn':
self._host = f'{cloud_server}.{DEFAULT_OAUTH2_API_HOST}'
self._base_url = f'https://{self._host}'
if isinstance(client_id, str):
@ -350,8 +349,8 @@ class MIoTHttpClient:
async def get_user_info_async(self) -> dict:
http_res = await self._session.get(
url='https://open.account.xiaomi.com/user/profile',
params={'clientId': self._client_id,
'token': self._access_token},
params={
'clientId': self._client_id, 'token': self._access_token},
headers={'content-type': 'application/x-www-form-urlencoded'},
timeout=MIHOME_HTTP_API_TIMEOUT
)
@ -386,7 +385,9 @@ class MIoTHttpClient:
return cert
async def __get_dev_room_page_async(self, max_id: str = None) -> dict:
async def __get_dev_room_page_async(
self, max_id: Optional[str] = None
) -> dict:
res_obj = await self.__mihome_api_post_async(
url_path='/app/v2/homeroom/get_dev_room_page',
data={
@ -442,7 +443,7 @@ class MIoTHttpClient:
if 'result' not in res_obj:
raise MIoTHttpError('invalid response result')
uid: str = None
uid: Optional[str] = None
home_infos: dict = {}
for device_source in ['homelist', 'share_home_list']:
home_infos.setdefault(device_source, {})
@ -507,7 +508,7 @@ class MIoTHttpClient:
return (await self.get_homeinfos_async()).get('uid', None)
async def __get_device_list_page_async(
self, dids: list[str], start_did: str = None
self, dids: list[str], start_did: Optional[str] = None
) -> dict[str, dict]:
req_data: dict = {
'limit': 200,
@ -575,9 +576,9 @@ class MIoTHttpClient:
async def get_devices_with_dids_async(
self, dids: list[str]
) -> dict[str, dict]:
) -> Optional[dict[str, dict]]:
results: list[dict[str, dict]] = await asyncio.gather(
*[self.__get_device_list_page_async(dids[index:index+150])
*[self.__get_device_list_page_async(dids=dids[index:index+150])
for index in range(0, len(dids), 150)])
devices = {}
for result in results:
@ -587,7 +588,7 @@ class MIoTHttpClient:
return devices
async def get_devices_async(
self, home_ids: list[str] = None
self, home_ids: Optional[list[str]] = None
) -> dict[str, dict]:
homeinfos = await self.get_homeinfos_async()
homes: dict[str, dict[str, Any]] = {}
@ -627,8 +628,9 @@ class MIoTHttpClient:
'group_id': group_id
} for did in room_info.get('dids', [])})
dids = sorted(list(devices.keys()))
results: dict[str, dict] = await self.get_devices_with_dids_async(
dids=dids)
results = await self.get_devices_with_dids_async(dids=dids)
if results is None:
raise MIoTHttpError('get devices failed')
for did in dids:
if did not in results:
devices.pop(did, None)
@ -706,7 +708,7 @@ class MIoTHttpClient:
key = f'{result["did"]}.{result["siid"]}.{result["piid"]}'
prop_obj = self._get_prop_list.pop(key, None)
if prop_obj is None:
_LOGGER.error('get prop error, key not exists, %s', result)
_LOGGER.info('get prop error, key not exists, %s', result)
continue
prop_obj['fut'].set_result(result['value'])
props_req.remove(key)
@ -717,7 +719,7 @@ class MIoTHttpClient:
continue
prop_obj['fut'].set_result(None)
if props_req:
_LOGGER.error(
_LOGGER.info(
'get prop from cloud failed, %s, %s', len(key), props_req)
if self._get_prop_list:

View File

@ -52,7 +52,8 @@ import socket
from dataclasses import dataclass
from enum import Enum, auto
import subprocess
from typing import Callable, Optional
from typing import Callable, Coroutine, Optional
import aiohttp
import psutil
import ipaddress
@ -77,38 +78,55 @@ class NetworkInfo:
class MIoTNetwork:
"""MIoT network utilities."""
PING_ADDRESS_LIST = [
_IP_ADDRESS_LIST: list[str] = [
'1.2.4.8', # CNNIC sDNS
'8.8.8.8', # Google Public DNS
'233.5.5.5', # AliDNS
'1.1.1.1', # Cloudflare DNS
'114.114.114.114', # 114 DNS
'208.67.222.222', # OpenDNS
'9.9.9.9', # Quad9 DNS
'9.9.9.9' # Quad9
]
_HTTP_ADDRESS_LIST: list[str] = [
'https://www.bing.com',
'https://www.google.com',
'https://www.baidu.com'
]
_REFRESH_INTERVAL = 30
_DETECT_TIMEOUT = 6
_main_loop: asyncio.AbstractEventLoop
_ip_addr_map: dict[str, float]
_http_addr_list: dict[str, float]
_http_session: aiohttp.ClientSession
_refresh_interval: int
_refresh_task: asyncio.Task
_refresh_timer: asyncio.TimerHandle
_refresh_task: Optional[asyncio.Task]
_refresh_timer: Optional[asyncio.TimerHandle]
_network_status: bool
_network_info: dict[str, NetworkInfo]
_sub_list_network_status: dict[str, Callable[[bool], asyncio.Future]]
_sub_list_network_status: dict[str, Callable[[bool], Coroutine]]
_sub_list_network_info: dict[str, Callable[[
InterfaceStatus, NetworkInfo], asyncio.Future]]
_ping_address_priority: int
InterfaceStatus, NetworkInfo], Coroutine]]
_done_event: asyncio.Event
def __init__(
self, loop: Optional[asyncio.AbstractEventLoop] = None
self,
ip_addr_list: Optional[list[str]] = None,
http_addr_list: Optional[list[str]] = None,
refresh_interval: Optional[int] = None,
loop: Optional[asyncio.AbstractEventLoop] = None
) -> None:
self._main_loop = loop or asyncio.get_running_loop()
self._ip_addr_map = {
ip: self._DETECT_TIMEOUT for ip in
ip_addr_list or self._IP_ADDRESS_LIST}
self._http_addr_map = {
http: self._DETECT_TIMEOUT for http in
http_addr_list or self._HTTP_ADDRESS_LIST}
self._http_session = aiohttp.ClientSession()
self._refresh_interval = refresh_interval or self._REFRESH_INTERVAL
self._refresh_interval = None
self._refresh_task = None
self._refresh_timer = None
@ -122,13 +140,10 @@ class MIoTNetwork:
self._done_event = asyncio.Event()
@property
def network_status(self) -> bool:
return self._network_status
@property
def network_info(self) -> dict[str, NetworkInfo]:
return self._network_info
async def init_async(self) -> bool:
self.__refresh_timer_handler()
# MUST get network info before starting
return await self._done_event.wait()
async def deinit_async(self) -> None:
if self._refresh_task:
@ -137,16 +152,36 @@ class MIoTNetwork:
if self._refresh_timer:
self._refresh_timer.cancel()
self._refresh_timer = None
await self._http_session.close()
self._refresh_interval = None
self._network_status = False
self._network_info.clear()
self._sub_list_network_status.clear()
self._sub_list_network_info.clear()
self._done_event.clear()
@property
def network_status(self) -> bool:
return self._network_status
@property
def network_info(self) -> dict[str, NetworkInfo]:
return self._network_info
async def update_addr_list_async(
self,
ip_addr_list: Optional[list[str]] = None,
http_addr_list: Optional[list[str]] = None,
) -> None:
if ip_addr_list:
self._ip_addr_map = {
ip: self._DETECT_TIMEOUT for ip in ip_addr_list}
if http_addr_list:
self._http_addr_map = {
http: self._DETECT_TIMEOUT for http in http_addr_list}
def sub_network_status(
self, key: str, handler: Callable[[bool], asyncio.Future]
self, key: str, handler: Callable[[bool], Coroutine]
) -> None:
self._sub_list_network_status[key] = handler
@ -155,65 +190,115 @@ class MIoTNetwork:
def sub_network_info(
self, key: str,
handler: Callable[[InterfaceStatus, NetworkInfo], asyncio.Future]
handler: Callable[[InterfaceStatus, NetworkInfo], Coroutine]
) -> None:
self._sub_list_network_info[key] = handler
def unsub_network_info(self, key: str) -> None:
self._sub_list_network_info.pop(key, None)
async def init_async(self, refresh_interval: int = 30) -> bool:
self._refresh_interval = refresh_interval
self.__refresh_timer_handler()
# MUST get network info before starting
return await self._done_event.wait()
async def refresh_async(self) -> None:
self.__refresh_timer_handler()
async def get_network_status_async(self, timeout: int = 6) -> bool:
return await self._main_loop.run_in_executor(
None, self.__get_network_status, False, timeout)
async def get_network_status_async(self) -> bool:
try:
ip_addr: str = ''
ip_ts: float = self._DETECT_TIMEOUT
for ip, ts in self._ip_addr_map.items():
if ts < ip_ts:
ip_addr = ip
ip_ts = ts
if (
ip_ts < self._DETECT_TIMEOUT
and self.ping_multi_async(ip_list=[ip_addr])
):
return True
http_addr: str = ''
http_ts: float = self._DETECT_TIMEOUT
for http, ts in self._http_addr_map.items():
if ts < http_ts:
http_addr = http
http_ts = ts
if (
http_ts < self._DETECT_TIMEOUT
and await self.http_multi_async(url_list=[http_addr])
):
return True
# Detect all addresses
results = await asyncio.gather(
*[self.ping_multi_async(), self.http_multi_async()])
return any(results)
except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.error('get network status error, %s', err)
return False
async def get_network_info_async(self) -> dict[str, NetworkInfo]:
return await self._main_loop.run_in_executor(
None, self.__get_network_info)
async def ping_multi_async(
self, ip_list: Optional[list[str]] = None
) -> bool:
addr_list = ip_list or list(self._ip_addr_map.keys())
tasks = []
for addr in addr_list:
tasks.append(self.__ping_async(addr))
results = await asyncio.gather(*tasks)
for addr, ts in zip(addr_list, results):
if addr in self._ip_addr_map:
self._ip_addr_map[addr] = ts
return any([ts < self._DETECT_TIMEOUT for ts in results])
async def http_multi_async(
self, url_list: Optional[list[str]] = None
) -> bool:
addr_list = url_list or list(self._http_addr_map.keys())
tasks = []
for addr in addr_list:
tasks.append(self.__http_async(url=addr))
results = await asyncio.gather(*tasks)
for addr, ts in zip(addr_list, results):
if addr in self._http_addr_map:
self._http_addr_map[addr] = ts
return any([ts < self._DETECT_TIMEOUT for ts in results])
def __calc_network_address(self, ip: str, netmask: str) -> str:
return str(ipaddress.IPv4Network(
f'{ip}/{netmask}', strict=False).network_address)
def __ping(
self, address: Optional[str] = None, timeout: int = 6
) -> bool:
param = '-n' if platform.system().lower() == 'windows' else '-c'
command = ['ping', param, '1', address]
async def __ping_async(self, address: Optional[str] = None) -> float:
start_ts: float = self._main_loop.time()
try:
output = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True, timeout=timeout)
return output.returncode == 0
process = await asyncio.create_subprocess_exec(
*(
[
'ping', '-n', '1', '-w',
str(self._DETECT_TIMEOUT*1000), address]
if platform.system().lower() == 'windows' else
[
'ping', '-c', '1', '-w',
str(self._DETECT_TIMEOUT), address]),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
await process.communicate()
if process.returncode == 0:
return self._main_loop.time() - start_ts
return self._DETECT_TIMEOUT
except Exception as err: # pylint: disable=broad-exception-caught
print(err)
return self._DETECT_TIMEOUT
async def __http_async(self, url: str) -> float:
start_ts: float = self._main_loop.time()
try:
async with self._http_session.get(
url, timeout=self._DETECT_TIMEOUT) as response:
if response.status == 200:
return self._main_loop.time() - start_ts
except Exception: # pylint: disable=broad-exception-caught
return False
def __get_network_status(
self, with_retry: bool = True, timeout: int = 6
) -> bool:
if self._ping_address_priority >= len(self.PING_ADDRESS_LIST):
self._ping_address_priority = 0
if self.__ping(
self.PING_ADDRESS_LIST[self._ping_address_priority], timeout):
return True
if not with_retry:
return False
for index in range(len(self.PING_ADDRESS_LIST)):
if index == self._ping_address_priority:
continue
if self.__ping(self.PING_ADDRESS_LIST[index], timeout):
self._ping_address_priority = index
return True
return False
pass
return self._DETECT_TIMEOUT
def __get_network_info(self) -> dict[str, NetworkInfo]:
interfaces = psutil.net_if_addrs()
@ -246,12 +331,10 @@ class MIoTNetwork:
for handler in self._sub_list_network_info.values():
self._main_loop.create_task(handler(status, info))
async def __update_status_and_info_async(self, timeout: int = 6) -> None:
async def __update_status_and_info_async(self) -> None:
try:
status: bool = await self._main_loop.run_in_executor(
None, self.__get_network_status, timeout)
infos = await self._main_loop.run_in_executor(
None, self.__get_network_info)
status: bool = await self.get_network_status_async()
infos = await self.get_network_info_async()
if self._network_status != status:
for handler in self._sub_list_network_status.values():
@ -273,7 +356,7 @@ class MIoTNetwork:
# Remove
self.__call_network_info_change(
InterfaceStatus.REMOVE,
self._network_info.pop(name, None))
self._network_info.pop(name))
# Add
for name, info in infos.items():
self._network_info[name] = info

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "Lokaler Geräteerkennungsdienst ist nicht verfügbar.",
"get_cert_error": "Fehler beim Abrufen des Gateway-Zertifikats.",
"no_family_selected": "Keine Familie ausgewählt.",
"no_devices": "In der ausgewählten Familie sind keine Geräte enthalten. Bitte wählen Sie eine Familie mit Geräten aus und fahren Sie fort.",
"no_devices": "Im ausgewählten Haushalt sind keine Geräte vorhanden. Bitte wählen Sie einen Haushalt mit Geräten aus und fahren Sie fort.",
"no_filter_devices": "Gefilterte Geräte sind leer. Bitte wählen Sie gültige Filterkriterien aus und fahren Sie fort.",
"no_central_device": "Im Modus \"Xiaomi Central Hub Gateway\" muss ein verfügbares Xiaomi Central Hub Gateway im lokalen Netzwerk von Home Assistant vorhanden sein. Stellen Sie sicher, dass die ausgewählte Familie diese Anforderungen erfüllt."
},
"abort": {
@ -168,7 +169,8 @@
"get_homeinfo_error": "Fehler beim Abrufen von Home-Informationen.",
"get_cert_error": "Fehler beim Abrufen des Zentralzertifikats.",
"no_family_selected": "Keine Familie ausgewählt.",
"no_devices": "In der ausgewählten Familie sind keine Geräte vorhanden. Bitte wählen Sie eine Familie mit Geräten und fahren Sie dann fort.",
"no_devices": "Im ausgewählten Haushalt sind keine Geräte vorhanden. Bitte wählen Sie einen Haushalt mit Geräten aus und fahren Sie fort.",
"no_filter_devices": "Gefilterte Geräte sind leer. Bitte wählen Sie gültige Filterkriterien aus und fahren Sie fort.",
"no_central_device": "Der Modus \"Zentral Gateway\" erfordert ein verfügbares Xiaomi-Zentral-Gateway im lokalen Netzwerk, in dem Home Assistant installiert ist. Überprüfen Sie, ob die ausgewählte Familie diese Anforderung erfüllt.",
"mdns_discovery_error": "Lokaler Geräteerkennungsdienstfehler.",
"update_config_error": "Fehler beim Aktualisieren der Konfigurationsinformationen.",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "Local device discovery service exception.",
"get_cert_error": "Failed to retrieve the central hub gateway certificate.",
"no_family_selected": "No home selected.",
"no_devices": "The selected home does not have any devices. Please choose a home containing devices and continue.",
"no_devices": "There are no devices in the selected home. Please select a home with devices and continue.",
"no_filter_devices": "Filtered devices are empty. Please select valid filter criteria and continue.",
"no_central_device": "[Central Hub Gateway Mode] requires a Xiaomi central hub gateway available in the local network where Home Assistant exists. Please check if the selected home meets the requirement."
},
"abort": {
@ -167,7 +168,8 @@
"get_token_error": "Failed to retrieve login authorization information (OAuth token).",
"get_homeinfo_error": "Failed to retrieve home information.",
"get_cert_error": "Failed to retrieve the central hub gateway certificate.",
"no_devices": "The selected home does not have any devices. Please choose a home containing devices and continue.",
"no_devices": "There are no devices in the selected home. Please select a home with devices and continue.",
"no_filter_devices": "Filtered devices are empty. Please select valid filter criteria and continue.",
"no_family_selected": "No home selected.",
"no_central_device": "[Central Hub Gateway Mode] requires a Xiaomi central hub gateway available in the local network where Home Assistant exists. Please check if the selected home meets the requirement.",
"mdns_discovery_error": "Local device discovery service exception.",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "Error en el servicio de descubrimiento de dispositivos locales.",
"get_cert_error": "Error al obtener el certificado de la puerta de enlace.",
"no_family_selected": "No se ha seleccionado ningún hogar.",
"no_devices": "No hay dispositivos en el hogar seleccionado. Seleccione un hogar con dispositivos y continúe.",
"no_devices": "No hay dispositivos en el hogar seleccionado. Por favor, seleccione un hogar con dispositivos y continúe.",
"no_filter_devices": "Los dispositivos filtrados están vacíos. Por favor, seleccione criterios de filtro válidos y continúe.",
"no_central_device": "【Modo de puerta de enlace central】Se requiere una puerta de enlace Xiaomi disponible en la red local donde se encuentra Home Assistant. Verifique si el hogar seleccionado cumple con este requisito."
},
"abort": {
@ -168,7 +169,8 @@
"get_homeinfo_error": "Error al obtener la información del hogar.",
"get_cert_error": "Error al obtener el certificado de la puerta de enlace.",
"no_family_selected": "No se ha seleccionado ningún hogar.",
"no_devices": "No hay dispositivos en el hogar seleccionado. Seleccione un hogar con dispositivos y continúe.",
"no_devices": "No hay dispositivos en el hogar seleccionado. Por favor, seleccione un hogar con dispositivos y continúe.",
"no_filter_devices": "Los dispositivos filtrados están vacíos. Por favor, seleccione criterios de filtro válidos y continúe.",
"no_central_device": "【Modo de puerta de enlace central】Se requiere una puerta de enlace Xiaomi disponible en la red local donde se encuentra Home Assistant. Verifique si el hogar seleccionado cumple con este requisito.",
"mdns_discovery_error": "Error en el servicio de descubrimiento de dispositivos locales.",
"update_config_error": "Error al actualizar la información de configuración.",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "Le service de découverte de périphériques locaux est anormal.",
"get_cert_error": "Échec de l'obtention du certificat de la passerelle.",
"no_family_selected": "Aucune maison sélectionnée.",
"no_devices": "Il n'y a pas d'appareil dans la maison sélectionnée. Veuillez sélectionner une maison avec des appareils avant de continuer.",
"no_devices": "Il n'y a pas d'appareils dans la maison sélectionnée. Veuillez sélectionner une maison avec des appareils et continuer.",
"no_filter_devices": "Les appareils filtrés sont vides. Veuillez sélectionner des critères de filtre valides et continuer.",
"no_central_device": "Le mode gateway central a besoin d'un Xiaomi Gateway disponible dans le réseau local où se trouve Home Assistant. Veuillez vérifier si la maison sélectionnée répond à cette exigence."
},
"abort": {
@ -168,7 +169,8 @@
"get_homeinfo_error": "Impossible d'obtenir les informations de la maison.",
"get_cert_error": "Impossible d'obtenir le certificat central.",
"no_family_selected": "Aucune maison sélectionnée.",
"no_devices": "Aucun périphérique dans la maison sélectionnée. Veuillez sélectionner une maison avec des périphériques et continuer.",
"no_devices": "Il n'y a pas d'appareils dans la maison sélectionnée. Veuillez sélectionner une maison avec des appareils et continuer.",
"no_filter_devices": "Les appareils filtrés sont vides. Veuillez sélectionner des critères de filtre valides et continuer.",
"no_central_device": "Le mode passerelle centrale nécessite une passerelle Xiaomi disponible dans le réseau local où est installé Home Assistant. Veuillez vérifier que la maison sélectionnée répond à cette exigence.",
"mdns_discovery_error": "Service de découverte de périphérique local en panne.",
"update_config_error": "Échec de la mise à jour des informations de configuration.",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "ローカルデバイス検出サービスに異常があります。",
"get_cert_error": "ゲートウェイ証明書を取得できませんでした。",
"no_family_selected": "家庭が選択されていません。",
"no_devices": "選択された家庭にデバイスがありません。デバイスがある家庭を選択して続行してください。",
"no_devices": "選択した家庭にはデバイスがありません。デバイスがある家庭を選択して続行してください。",
"no_filter_devices": "フィルタリングされたデバイスが空です。有効なフィルター条件を選択して続行してください。",
"no_central_device": "【中央ゲートウェイモード】Home Assistant が存在する LAN 内に使用可能な Xiaomi 中央ゲートウェイがある必要があります。選択された家庭がこの要件を満たしているかどうかを確認してください。"
},
"abort": {
@ -168,7 +169,8 @@
"get_homeinfo_error": "家庭情報の取得に失敗しました。",
"get_cert_error": "中枢証明書の取得に失敗しました。",
"no_family_selected": "家族が選択されていません。",
"no_devices": "選択された家庭にはデバイスがありません。デバイスがある家庭を選択してから続行してください。",
"no_devices": "選択した家庭にはデバイスがありません。デバイスがある家庭を選択して続行してください。",
"no_filter_devices": "フィルタリングされたデバイスが空です。有効なフィルター条件を選択して続行してください。",
"no_central_device": "【中枢ゲートウェイモード】には、Home Assistantが存在するローカルネットワークに使用可能なXiaomi Central Hub Gatewayが存在する必要があります。選択された家庭がこの要件を満たしているかどうかを確認してください。",
"mdns_discovery_error": "ローカルデバイス発見サービスが異常です。",
"update_config_error": "構成情報の更新に失敗しました。",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "Lokaal apparaatsontdekkingsservice-exceptie.",
"get_cert_error": "Mislukt bij het ophalen van het certificaat van de centrale hubgateway.",
"no_family_selected": "Geen huis geselecteerd.",
"no_devices": "Het geselecteerde huis heeft geen apparaten. Kies a.u.b. een huis met apparaten en ga verder.",
"no_devices": "Er zijn geen apparaten in het geselecteerde huis. Selecteer een huis met apparaten en ga verder.",
"no_filter_devices": "Gefilterde apparaten zijn leeg. Selecteer geldige filtercriteria en ga verder.",
"no_central_device": "[Centrale Hub Gateway Modus] vereist een beschikbare Xiaomi centrale hubgateway in het lokale netwerk waar Home Assistant zich bevindt. Controleer of het geselecteerde huis aan deze vereiste voldoet."
},
"abort": {
@ -167,7 +168,8 @@
"get_token_error": "Mislukt bij het ophalen van inlogautorisatie-informatie (OAuth-token).",
"get_homeinfo_error": "Mislukt bij het ophalen van huisinformatie.",
"get_cert_error": "Mislukt bij het ophalen van het certificaat van de centrale hubgateway.",
"no_devices": "Het geselecteerde huis heeft geen apparaten. Kies a.u.b. een huis met apparaten en ga verder.",
"no_devices": "Er zijn geen apparaten in het geselecteerde huis. Selecteer een huis met apparaten en ga verder.",
"no_filter_devices": "Gefilterde apparaten zijn leeg. Selecteer geldige filtercriteria en ga verder.",
"no_family_selected": "Geen huis geselecteerd.",
"no_central_device": "[Centrale Hub Gateway Modus] vereist een beschikbare Xiaomi centrale hubgateway in het lokale netwerk waar Home Assistant zich bevindt. Controleer of het geselecteerde huis aan deze vereiste voldoet.",
"mdns_discovery_error": "Lokaal apparaatsontdekkingsservice-exceptie.",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "Exceção no serviço de descoberta de dispositivos locais.",
"get_cert_error": "Falha ao obter o certificado do gateway central.",
"no_family_selected": "Nenhuma casa selecionada.",
"no_devices": "A casa selecionada não possui nenhum dispositivo. Por favor, escolha uma casa que contenha dispositivos e continue.",
"no_devices": "Não há dispositivos na casa selecionada. Por favor, selecione uma casa com dispositivos e continue.",
"no_filter_devices": "Os dispositivos filtrados estão vazios. Por favor, selecione critérios de filtro válidos e continue.",
"no_central_device": "[Modo Gateway Central] requer um gateway central Xiaomi disponível na rede local onde o Home Assistant está. Verifique se a casa selecionada atende a esse requisito."
},
"abort": {
@ -167,7 +168,8 @@
"get_token_error": "Falha ao obter as informações de autorização de login (token OAuth).",
"get_homeinfo_error": "Falha ao obter as informações da casa.",
"get_cert_error": "Falha ao obter o certificado do gateway central.",
"no_devices": "A casa selecionada não possui nenhum dispositivo. Por favor, escolha uma casa com dispositivos e continue.",
"no_devices": "Não há dispositivos na casa selecionada. Por favor, selecione uma casa com dispositivos e continue.",
"no_filter_devices": "Os dispositivos filtrados estão vazios. Por favor, selecione critérios de filtro válidos e continue.",
"no_family_selected": "Nenhuma casa selecionada.",
"no_central_device": "[Modo Gateway Central] requer um gateway central Xiaomi disponível na rede local onde o Home Assistant está. Verifique se a casa selecionada atende a esse requisito.",
"mdns_discovery_error": "Exceção no serviço de descoberta de dispositivos locais.",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "Exceção no serviço de descoberta de dispositivos locais.",
"get_cert_error": "Não foi possível obter o certificado do gateway central.",
"no_family_selected": "Nenhuma casa selecionada.",
"no_devices": "A casa selecionada não possui quaisquer dispositivos. Por favor, selecione uma casa que contenha dispositivos e continue.",
"no_devices": "Não há dispositivos na casa selecionada. Por favor, selecione uma casa com dispositivos e continue.",
"no_filter_devices": "Os dispositivos filtrados estão vazios. Por favor, selecione critérios de filtro válidos e continue.",
"no_central_device": "O [Modo Gateway Central] requer um gateway central Xiaomi disponível na rede local onde o Home Assistant está. Verifique se a casa selecionada cumpre este requisito."
},
"abort": {
@ -167,7 +168,8 @@
"get_token_error": "Não foi possível obter a informação de autorização de login (token OAuth).",
"get_homeinfo_error": "Não foi possível obter a informação da casa.",
"get_cert_error": "Não foi possível obter o certificado do gateway central.",
"no_devices": "A casa selecionada não possui quaisquer dispositivos. Por favor, selecione uma casa com dispositivos e continue.",
"no_devices": "Não há dispositivos na casa selecionada. Por favor, selecione uma casa com dispositivos e continue.",
"no_filter_devices": "Os dispositivos filtrados estão vazios. Por favor, selecione critérios de filtro válidos e continue.",
"no_family_selected": "Nenhuma casa selecionada.",
"no_central_device": "O [Modo Gateway Central] requer um gateway central Xiaomi disponível na rede local onde o Home Assistant está. Verifique se a casa selecionada cumpre este requisito.",
"mdns_discovery_error": "Exceção no serviço de descoberta de dispositivos locais.",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "Сервис обнаружения локальных устройств недоступен.",
"get_cert_error": "Не удалось получить сертификат центрального шлюза.",
"no_family_selected": "Не выбрана домашняя сеть.",
"no_devices": "В выбранной домашней сети нет устройств. Пожалуйста, выберите домашнюю сеть с устройствами и продолжайте.",
"no_devices": "В выбранном доме нет устройств. Пожалуйста, выберите дом с устройствами и продолжите.",
"no_filter_devices": "Список устройств после фильтрации пуст. Пожалуйста, выберите действительные условия фильтрации и продолжите.",
"no_central_device": "Для режима центрального шлюза Xiaomi необходимо наличие доступного центрального шлюза Xiaomi в локальной сети Home Assistant. Проверьте, соответствует ли выбранная домашняя сеть этому требованию."
},
"abort": {
@ -168,7 +169,8 @@
"get_homeinfo_error": "Не удалось получить информацию о домашней сети.",
"get_cert_error": "Не удалось получить центральный сертификат.",
"no_family_selected": "Не выбрана семья.",
"no_devices": "В выбранной семье нет устройств. Пожалуйста, выберите семью с устройствами и продолжайте.",
"no_devices": "В выбранном доме нет устройств. Пожалуйста, выберите дом с устройствами и продолжите.",
"no_filter_devices": "Список устройств после фильтрации пуст. Пожалуйста, выберите действительные условия фильтрации и продолжите.",
"no_central_device": "Для режима центрального шлюза необходим существующий в локальной сети Home Assistant с доступным Xiaomi-шлюзом. Пожалуйста, проверьте, соответствует ли выбранная семья этому требованию.",
"mdns_discovery_error": "Ошибка сервиса поиска локальных устройств.",
"update_config_error": "Не удалось обновить информацию о конфигурации.",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "本地设备发现服务异常。",
"get_cert_error": "获取中枢证书失败。",
"no_family_selected": "未选择家庭。",
"no_devices": "选择的家庭中没有设备。请选择有设备的家庭,而后继续。",
"no_devices": "选择的家庭中没有设备。请选择有设备的家庭,然后继续。",
"no_filter_devices": "筛选设备为空。请选择有效的筛选条件,然后继续。",
"no_central_device": "【中枢网关模式】需要 Home Assistant 所在的局域网中存在可用的小米中枢网关。请检查选择的家庭是否符合该要求。"
},
"abort": {
@ -168,7 +169,8 @@
"get_homeinfo_error": "获取家庭信息失败。",
"get_cert_error": "获取中枢证书失败。",
"no_family_selected": "未选择家庭。",
"no_devices": "选择的家庭中没有设备,请选择有设备的家庭,而后继续。",
"no_devices": "选择的家庭中没有设备,请选择有设备的家庭,然后继续。",
"no_filter_devices": "筛选设备为空。请选择有效的筛选条件,然后继续。",
"no_central_device": "【中枢网关模式】需要 Home Assistant 所在的局域网中存在可用的小米中枢网关。请检查选择的家庭是否符合该要求。",
"mdns_discovery_error": "本地设备发现服务异常。",
"update_config_error": "配置信息更新失败。",

View File

@ -68,7 +68,8 @@
"mdns_discovery_error": "本地設備發現服務異常。",
"get_cert_error": "獲取中樞證書失敗。",
"no_family_selected": "未選擇家庭。",
"no_devices": "選擇的家庭中沒有設備。請選擇有設備的家庭,而後繼續。",
"no_devices": "選擇的家庭中沒有設備。請選擇有設備的家庭,然後繼續。",
"no_filter_devices": "篩選設備為空。請選擇有效的篩選條件,然後繼續。",
"no_central_device": "【中樞網關模式】需要 Home Assistant 所在的局域網中存在可用的小米中樞網關。請檢查選擇的家庭是否符合該要求。"
},
"abort": {
@ -168,7 +169,8 @@
"get_homeinfo_error": "獲取家庭信息失敗。",
"get_cert_error": "獲取中樞證書失敗。",
"no_family_selected": "未選擇家庭。",
"no_devices": "選擇的家庭中沒有設備,請選擇有設備的家庭,而後繼續。",
"no_devices": "選擇的家庭中沒有設備。請選擇有設備的家庭,然後繼續。",
"no_filter_devices": "篩選設備為空。請選擇有效的篩選條件,然後繼續。",
"no_central_device": "【中樞網關模式】需要 Home Assistant 所在的局域網中存在可用的小米中樞網關。請檢查選擇的家庭是否符合該要求。",
"mdns_discovery_error": "本地設備發現服務異常。",
"update_config_error": "配置信息更新失敗。",