Use call_soon_threadsafe instead of event fd for ipc

This commit is contained in:
Feng Wang 2024-12-21 21:39:59 +08:00
parent faa13c53e8
commit 794505f082
2 changed files with 222 additions and 269 deletions

View File

@ -1022,7 +1022,7 @@ class MIoTClient:
handler=self.__on_lan_device_state_changed) handler=self.__on_lan_device_state_changed)
for did, info in ( for did, info in (
await self._miot_lan.get_dev_list_async()).items(): await self._miot_lan.get_dev_list_async()).items():
self.__on_lan_device_state_changed( await self.__on_lan_device_state_changed(
did=did, state=info, ctx=None) did=did, state=info, ctx=None)
_LOGGER.info('lan device list, %s', self._device_list_lan) _LOGGER.info('lan device list, %s', self._device_list_lan)
self._miot_lan.update_devices(devices={ self._miot_lan.update_devices(devices={

View File

@ -53,8 +53,6 @@ import asyncio
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum, auto from enum import Enum, auto
import logging import logging
import os
import queue
import random import random
import secrets import secrets
import socket import socket
@ -77,29 +75,6 @@ from .common import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class MIoTLanCmdType(Enum):
"""MIoT lan command."""
DEINIT = 0
CALL_API = auto()
SUB_DEVICE_STATE = auto()
UNSUB_DEVICE_STATE = auto()
REG_BROADCAST = auto()
UNREG_BROADCAST = auto()
GET_DEV_LIST = auto()
DEVICE_UPDATE = auto()
DEVICE_DELETE = auto()
NET_INFO_UPDATE = auto()
NET_IFS_UPDATE = auto()
OPTIONS_UPDATE = auto()
@dataclass
class MIoTLanCmd:
"""MIoT lan command."""
type_: MIoTLanCmdType
data: Any
@dataclass @dataclass
class MIoTLanCmdData: class MIoTLanCmdData:
handler: Callable[[dict, Any], None] handler: Callable[[dict, Any], None]
@ -142,7 +117,7 @@ class MIoTLanUnsubDeviceState:
@dataclass @dataclass
class MIoTLanSubDeviceState: class MIoTLanSubDeviceState:
key: str key: str
handler: Callable[[str, dict, Any], None] handler: Callable[[str, dict, Any], Coroutine]
handler_ctx: Any handler_ctx: Any
@ -157,7 +132,7 @@ class MIoTLanRequestData:
msg_id: int msg_id: int
handler: Optional[Callable[[dict, Any], None]] handler: Optional[Callable[[dict, Any], None]]
handler_ctx: Any handler_ctx: Any
timeout: asyncio.TimerHandle timeout: Optional[asyncio.TimerHandle]
class MIoTLanDeviceState(Enum): class MIoTLanDeviceState(Enum):
@ -202,6 +177,8 @@ class MIoTLanDevice:
_ka_timer: Optional[asyncio.TimerHandle] _ka_timer: Optional[asyncio.TimerHandle]
_ka_internal: float _ka_internal: float
# All functions SHOULD be called from the internal loop
def __init__( def __init__(
self, manager: 'MIoTLan', did: str, token: str, ip: Optional[str] = None self, manager: 'MIoTLan', did: str, token: str, ip: Optional[str] = None
) -> None: ) -> None:
@ -511,6 +488,8 @@ class MIoTLan:
_init_done: bool _init_done: bool
# The following should be called from the main loop
def __init__( def __init__(
self, self,
net_ifs: list[str], net_ifs: list[str],
@ -528,7 +507,7 @@ class MIoTLan:
self._net_ifs = set(net_ifs) self._net_ifs = set(net_ifs)
self._network = network self._network = network
self._network.sub_network_info( self._network.sub_network_info(
key='miot_lan', handler=self.__on_network_info_change) key='miot_lan', handler=self.__on_network_info_change_external_async)
self._mips_service = mips_service self._mips_service = mips_service
self._mips_service.sub_service_change( self._mips_service.sub_service_change(
key='miot_lan', group_id='*', key='miot_lan', group_id='*',
@ -611,8 +590,6 @@ class MIoTLan:
except Exception as err: # pylint: disable=broad-exception-caught except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.error('load profile models error, %s', err) _LOGGER.error('load profile models error, %s', err)
self._profile_models = {} self._profile_models = {}
self._queue = queue.Queue()
self._cmd_event_fd = os.eventfd(0, os.O_NONBLOCK)
# All tasks meant for the internal loop should happen in this thread # All tasks meant for the internal loop should happen in this thread
self._thread = threading.Thread(target=self.__internal_loop_thread) self._thread = threading.Thread(target=self.__internal_loop_thread)
self._thread.name = 'miot_lan' self._thread.name = 'miot_lan'
@ -629,8 +606,6 @@ class MIoTLan:
self._internal_loop = asyncio.new_event_loop() self._internal_loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._internal_loop) asyncio.set_event_loop(self._internal_loop)
self.__init_socket() self.__init_socket()
self._internal_loop.add_reader(
self._cmd_event_fd, self.__cmd_read_handler)
self._scan_timer = self._internal_loop.call_later( self._scan_timer = self._internal_loop.call_later(
int(3*random.random()), self.__scan_devices) int(3*random.random()), self.__scan_devices)
self._internal_loop.run_forever() self._internal_loop.run_forever()
@ -642,7 +617,7 @@ class MIoTLan:
_LOGGER.info('miot lan not init') _LOGGER.info('miot lan not init')
return return
self._init_done = False self._init_done = False
self.__lan_send_cmd(MIoTLanCmdType.DEINIT, None) self._internal_loop.call_soon_threadsafe(self.__deinit)
self._thread.join() self._thread.join()
self._profile_models = {} self._profile_models = {}
@ -683,9 +658,9 @@ class MIoTLan:
self._net_ifs = set(net_ifs) self._net_ifs = set(net_ifs)
await self.init_async() await self.init_async()
return return
self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.NET_IFS_UPDATE, self.__update_net_ifs,
data=net_ifs) net_ifs)
async def vote_for_lan_ctrl_async(self, key: str, vote: bool) -> None: async def vote_for_lan_ctrl_async(self, key: str, vote: bool) -> None:
_LOGGER.info('vote for lan ctrl, %s, %s', key, vote) _LOGGER.info('vote for lan ctrl, %s, %s', key, vote)
@ -700,22 +675,21 @@ class MIoTLan:
if not self._init_done: if not self._init_done:
self._enable_subscribe = enable_subscribe self._enable_subscribe = enable_subscribe
return return
self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.OPTIONS_UPDATE, self.__update_subscribe_option,
data={ {'enable_subscribe': enable_subscribe})
'enable_subscribe': enable_subscribe, })
def update_devices(self, devices: dict[str, dict]) -> bool: def update_devices(self, devices: dict[str, dict]) -> bool:
_LOGGER.info('update devices, %s', devices) _LOGGER.info('update devices, %s', devices)
return self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.DEVICE_UPDATE, self.__update_devices, devices)
data=devices) return True
def delete_devices(self, devices: list[str]) -> bool: def delete_devices(self, devices: list[str]) -> bool:
_LOGGER.info('delete devices, %s', devices) _LOGGER.info('delete devices, %s', devices)
return self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.DEVICE_DELETE, self.__delete_devices, devices)
data=devices) return True
def sub_lan_state( def sub_lan_state(
self, key: str, handler: Callable[[bool], Coroutine] self, key: str, handler: Callable[[bool], Coroutine]
@ -727,19 +701,20 @@ class MIoTLan:
@final @final
def sub_device_state( def sub_device_state(
self, key: str, handler: Callable[[str, dict, Any], None], self, key: str, handler: Callable[[str, dict, Any], Coroutine],
handler_ctx: Any = None handler_ctx: Any = None
) -> bool: ) -> bool:
return self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.SUB_DEVICE_STATE, self.__sub_device_state,
data=MIoTLanSubDeviceState( MIoTLanSubDeviceState(
key=key, handler=handler, handler_ctx=handler_ctx)) key=key, handler=handler, handler_ctx=handler_ctx))
return True
@final @final
def unsub_device_state(self, key: str) -> bool: def unsub_device_state(self, key: str) -> bool:
return self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.UNSUB_DEVICE_STATE, self.__unsub_device_state, MIoTLanUnsubDeviceState(key=key))
data=MIoTLanUnsubDeviceState(key=key)) return True
@final @final
def sub_prop( def sub_prop(
@ -751,10 +726,11 @@ class MIoTLan:
key = ( key = (
f'{did}/p/' f'{did}/p/'
f'{"#" if siid is None or piid is None else f"{siid}/{piid}"}') f'{"#" if siid is None or piid is None else f"{siid}/{piid}"}')
return self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.REG_BROADCAST, self.__sub_broadcast,
data=MIoTLanRegisterBroadcastData( MIoTLanRegisterBroadcastData(
key=key, handler=handler, handler_ctx=handler_ctx)) key=key, handler=handler, handler_ctx=handler_ctx))
return True
@final @final
def unsub_prop(self, did: str, siid: Optional[int] = None, piid: Optional[int] = None) -> bool: def unsub_prop(self, did: str, siid: Optional[int] = None, piid: Optional[int] = None) -> bool:
@ -763,9 +739,10 @@ class MIoTLan:
key = ( key = (
f'{did}/p/' f'{did}/p/'
f'{"#" if siid is None or piid is None else f"{siid}/{piid}"}') f'{"#" if siid is None or piid is None else f"{siid}/{piid}"}')
return self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.UNREG_BROADCAST, self.__unsub_broadcast,
data=MIoTLanUnregisterBroadcastData(key=key)) MIoTLanUnregisterBroadcastData(key=key))
return True
@final @final
def sub_event( def sub_event(
@ -777,10 +754,11 @@ class MIoTLan:
key = ( key = (
f'{did}/e/' f'{did}/e/'
f'{"#" if siid is None or eiid is None else f"{siid}/{eiid}"}') f'{"#" if siid is None or eiid is None else f"{siid}/{eiid}"}')
return self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.REG_BROADCAST, self.__sub_broadcast,
data=MIoTLanRegisterBroadcastData( MIoTLanRegisterBroadcastData(
key=key, handler=handler, handler_ctx=handler_ctx)) key=key, handler=handler, handler_ctx=handler_ctx))
return True
@final @final
def unsub_event(self, did: str, siid: Optional[int] = None, eiid: Optional[int] = None) -> bool: def unsub_event(self, did: str, siid: Optional[int] = None, eiid: Optional[int] = None) -> bool:
@ -789,9 +767,10 @@ class MIoTLan:
key = ( key = (
f'{did}/e/' f'{did}/e/'
f'{"#" if siid is None or eiid is None else f"{siid}/{eiid}"}') f'{"#" if siid is None or eiid is None else f"{siid}/{eiid}"}')
return self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
cmd=MIoTLanCmdType.UNREG_BROADCAST, self.__unsub_broadcast,
data=MIoTLanUnregisterBroadcastData(key=key)) MIoTLanUnregisterBroadcastData(key=key))
return True
@final @final
async def get_prop_async( async def get_prop_async(
@ -870,17 +849,67 @@ class MIoTLan:
fut.set_result, msg) fut.set_result, msg)
fut: asyncio.Future = self._main_loop.create_future() fut: asyncio.Future = self._main_loop.create_future()
if self.__lan_send_cmd( self._internal_loop.call_soon_threadsafe(
MIoTLanCmdType.GET_DEV_LIST, self.__get_dev_list,
MIoTLanGetDevListData( MIoTLanGetDevListData(
handler=get_device_list_handler, handler=get_device_list_handler,
handler_ctx=fut, handler_ctx=fut,
timeout_ms=timeout_ms)): timeout_ms=timeout_ms))
return await fut return await fut
_LOGGER.error('get_dev_list_async error, send cmd failed')
fut.set_result({}) async def __call_api_async(
self, did: str, msg: dict, timeout_ms: int = 10000
) -> dict:
def call_api_handler(msg: dict, fut: asyncio.Future):
self._main_loop.call_soon_threadsafe(
fut.set_result, msg)
fut: asyncio.Future = self._main_loop.create_future()
self._internal_loop.call_soon_threadsafe(
self.__call_api, did, msg, call_api_handler, fut, timeout_ms)
return await fut return await fut
async def __on_network_info_change_external_async(
self,
status: InterfaceStatus,
info: NetworkInfo
) -> None:
_LOGGER.info(
'on network info change, status: %s, info: %s', status, info)
available_net_ifs = set()
for if_name in list(self._network.network_info.keys()):
available_net_ifs.add(if_name)
if len(available_net_ifs) == 0:
await self.deinit_async()
self._available_net_ifs = available_net_ifs
return
if self._net_ifs.isdisjoint(available_net_ifs):
_LOGGER.info('no valid net_ifs')
await self.deinit_async()
self._available_net_ifs = available_net_ifs
return
if not self._init_done:
self._available_net_ifs = available_net_ifs
await self.init_async()
return
self._internal_loop.call_soon_threadsafe(
self.__on_network_info_chnage,
MIoTLanNetworkUpdateData(status=status, if_name=info.name))
async def __on_mips_service_change(
self, group_id: str, state: MipsServiceState, data: dict
) -> None:
_LOGGER.info(
'on mips service change, %s, %s, %s', group_id, state, data)
if len(self._mips_service.get_services()) > 0:
_LOGGER.info('find central service, deinit miot lan')
await self.deinit_async()
else:
_LOGGER.info('no central service, init miot lan')
await self.init_async()
# The folowing methods SHOULD ONLY be called in the internal loop
def ping(self, if_name: str | None, target_ip: str) -> None: def ping(self, if_name: str | None, target_ip: str) -> None:
if not target_ip: if not target_ip:
return return
@ -956,8 +985,8 @@ class MIoTLan:
def broadcast_device_state(self, did: str, state: dict) -> None: def broadcast_device_state(self, did: str, state: dict) -> None:
for handler in self._device_state_sub_map.values(): for handler in self._device_state_sub_map.values():
self._main_loop.call_soon_threadsafe( self._main_loop.call_soon_threadsafe(
self._main_loop.create_task, lambda: self._main_loop.create_task(
handler.handler(did, state, handler.handler_ctx)) handler.handler(did, state, handler.handler_ctx)))
def __gen_msg_id(self) -> int: def __gen_msg_id(self) -> int:
if not self._msg_id_counter: if not self._msg_id_counter:
@ -967,76 +996,37 @@ class MIoTLan:
self._msg_id_counter = 1 self._msg_id_counter = 1
return self._msg_id_counter return self._msg_id_counter
def __lan_send_cmd(self, cmd: MIoTLanCmdType, data: Any) -> bool: def __call_api(self, did: str, msg: dict, handler: Callable, handler_ctx: Any, timeout_ms: int = 10000) -> None:
try:
self._queue.put(MIoTLanCmd(type_=cmd, data=data))
os.eventfd_write(self._cmd_event_fd, 1)
return True
except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.error('send cmd error, %s, %s', cmd, err)
return False
async def __call_api_async(
self, did: str, msg: dict, timeout_ms: int = 10000
) -> dict:
def call_api_handler(msg: dict, fut: asyncio.Future):
self._main_loop.call_soon_threadsafe(
fut.set_result, msg)
fut: asyncio.Future = self._main_loop.create_future()
if self.__lan_send_cmd(
cmd=MIoTLanCmdType.CALL_API,
data=MIoTLanCallApiData(
did=did,
msg=msg,
handler=call_api_handler,
handler_ctx=fut,
timeout_ms=timeout_ms)):
return await fut
fut.set_result({
'code': MIoTErrorCode.CODE_UNAVAILABLE.value,
'error': 'send cmd error'})
return await fut
def __cmd_read_handler(self) -> None:
fd_value = os.eventfd_read(self._cmd_event_fd)
if fd_value == 0:
return
while not self._queue.empty():
mips_cmd: MIoTLanCmd = self._queue.get(block=False)
if mips_cmd.type_ == MIoTLanCmdType.CALL_API:
call_api_data: MIoTLanCallApiData = mips_cmd.data
try: try:
self.send2device( self.send2device(
did=call_api_data.did, did=did,
msg={'from': 'ha.xiaomi_home', **call_api_data.msg}, msg={'from': 'ha.xiaomi_home', **msg},
handler=call_api_data.handler, handler=handler,
handler_ctx=call_api_data.handler_ctx, handler_ctx=handler_ctx,
timeout_ms=call_api_data.timeout_ms) timeout_ms=timeout_ms)
except Exception as err: # pylint: disable=broad-exception-caught except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.error('send2device error, %s', err) _LOGGER.error('send2device error, %s', err)
call_api_data.handler({ handler({
'code': MIoTErrorCode.CODE_INTERNAL_ERROR.value, 'code': MIoTErrorCode.CODE_INTERNAL_ERROR.value,
'error': str(err)}, 'error': str(err)},
call_api_data.handler_ctx) handler_ctx)
elif mips_cmd.type_ == MIoTLanCmdType.SUB_DEVICE_STATE:
sub_data: MIoTLanSubDeviceState = mips_cmd.data def __sub_device_state(self, data: MIoTLanSubDeviceState) -> None:
self._device_state_sub_map[sub_data.key] = sub_data self._device_state_sub_map[data.key] = data
elif mips_cmd.type_ == MIoTLanCmdType.UNSUB_DEVICE_STATE:
sub_data: MIoTLanUnsubDeviceState = mips_cmd.data def __unsub_device_state(self, data: MIoTLanUnsubDeviceState) -> None:
self._device_state_sub_map.pop(sub_data.key, None) self._device_state_sub_map.pop(data.key, None)
elif mips_cmd.type_ == MIoTLanCmdType.REG_BROADCAST:
reg_data: MIoTLanRegisterBroadcastData = mips_cmd.data def __sub_broadcast(self, data: MIoTLanRegisterBroadcastData) -> None:
self._device_msg_matcher[reg_data.key] = reg_data self._device_msg_matcher[data.key] = data
_LOGGER.debug('lan register broadcast, %s', reg_data.key) _LOGGER.debug('lan register broadcast, %s', data.key)
elif mips_cmd.type_ == MIoTLanCmdType.UNREG_BROADCAST:
unreg_data: MIoTLanUnregisterBroadcastData = mips_cmd.data def __unsub_broadcast(self, data: MIoTLanUnregisterBroadcastData) -> None:
if self._device_msg_matcher.get(topic=unreg_data.key): if self._device_msg_matcher.get(topic=data.key):
del self._device_msg_matcher[unreg_data.key] del self._device_msg_matcher[data.key]
_LOGGER.debug('lan unregister broadcast, %s', unreg_data.key) _LOGGER.debug('lan unregister broadcast, %s', data.key)
elif mips_cmd.type_ == MIoTLanCmdType.GET_DEV_LIST:
get_dev_list_data: MIoTLanGetDevListData = mips_cmd.data def __get_dev_list(self, data: MIoTLanGetDevListData) -> None:
dev_list = { dev_list = {
device.did: { device.did: {
'online': device.online, 'online': device.online,
@ -1044,10 +1034,10 @@ class MIoTLan:
} }
for device in self._lan_devices.values() for device in self._lan_devices.values()
if device.online} if device.online}
get_dev_list_data.handler( data.handler(
dev_list, get_dev_list_data.handler_ctx) dev_list, data.handler_ctx)
elif mips_cmd.type_ == MIoTLanCmdType.DEVICE_UPDATE:
devices: dict[str, dict] = mips_cmd.data def __update_devices(self, devices: dict[str, dict]) -> None:
for did, info in devices.items(): for did, info in devices.items():
# did MUST be digit(UINT64) # did MUST be digit(UINT64)
if not did.isdigit(): if not did.isdigit():
@ -1076,24 +1066,24 @@ class MIoTLan:
ip=info.get('ip', None)) ip=info.get('ip', None))
else: else:
self._lan_devices[did].update_info(info) self._lan_devices[did].update_info(info)
elif mips_cmd.type_ == MIoTLanCmdType.DEVICE_DELETE:
device_dids: list[str] = mips_cmd.data def __delete_devices(self, devices: list[str]) -> None:
for did in device_dids: for did in devices:
lan_device = self._lan_devices.pop(did, None) lan_device = self._lan_devices.pop(did, None)
if not lan_device: if not lan_device:
continue continue
lan_device.on_delete() lan_device.on_delete()
elif mips_cmd.type_ == MIoTLanCmdType.NET_INFO_UPDATE:
net_data: MIoTLanNetworkUpdateData = mips_cmd.data def __on_network_info_chnage(self, data: MIoTLanNetworkUpdateData) -> None:
if net_data.status == InterfaceStatus.ADD: if data.status == InterfaceStatus.ADD:
self._available_net_ifs.add(net_data.if_name) self._available_net_ifs.add(data.if_name)
if net_data.if_name in self._net_ifs: if data.if_name in self._net_ifs:
self.__create_socket(if_name=net_data.if_name) self.__create_socket(if_name=data.if_name)
elif net_data.status == InterfaceStatus.REMOVE: elif data.status == InterfaceStatus.REMOVE:
self._available_net_ifs.remove(net_data.if_name) self._available_net_ifs.remove(data.if_name)
self.__destroy_socket(if_name=net_data.if_name) self.__destroy_socket(if_name=data.if_name)
elif mips_cmd.type_ == MIoTLanCmdType.NET_IFS_UPDATE:
net_ifs: list[str] = mips_cmd.data def __update_net_ifs(self, net_ifs: list[str]) -> None:
if self._net_ifs != set(net_ifs): if self._net_ifs != set(net_ifs):
self._net_ifs = set(net_ifs) self._net_ifs = set(net_ifs)
for if_name in self._net_ifs: for if_name in self._net_ifs:
@ -1101,8 +1091,8 @@ class MIoTLan:
for if_name in list(self._broadcast_socks.keys()): for if_name in list(self._broadcast_socks.keys()):
if if_name not in self._net_ifs: if if_name not in self._net_ifs:
self.__destroy_socket(if_name=if_name) self.__destroy_socket(if_name=if_name)
elif mips_cmd.type_ == MIoTLanCmdType.OPTIONS_UPDATE:
options: dict = mips_cmd.data def __update_subscribe_option(self, options: dict) -> None:
if 'enable_subscribe' in options: if 'enable_subscribe' in options:
if options['enable_subscribe'] != self._enable_subscribe: if options['enable_subscribe'] != self._enable_subscribe:
self._enable_subscribe = options['enable_subscribe'] self._enable_subscribe = options['enable_subscribe']
@ -1110,8 +1100,9 @@ class MIoTLan:
# Unsubscribe all # Unsubscribe all
for device in self._lan_devices.values(): for device in self._lan_devices.values():
device.unsubscribe() device.unsubscribe()
elif mips_cmd.type_ == MIoTLanCmdType.DEINIT:
# stop the thread def __deinit(self) -> None:
# Release all resources
if self._scan_timer: if self._scan_timer:
self._scan_timer.cancel() self._scan_timer.cancel()
self._scan_timer = None self._scan_timer = None
@ -1119,6 +1110,7 @@ class MIoTLan:
device.on_delete() device.on_delete()
self._lan_devices.clear() self._lan_devices.clear()
for req_data in self._pending_requests.values(): for req_data in self._pending_requests.values():
if req_data.timeout:
req_data.timeout.cancel() req_data.timeout.cancel()
self._pending_requests.clear() self._pending_requests.clear()
for timer in self._reply_msg_buffer.values(): for timer in self._reply_msg_buffer.values():
@ -1126,8 +1118,7 @@ class MIoTLan:
self._reply_msg_buffer.clear() self._reply_msg_buffer.clear()
self._device_msg_matcher = MIoTMatcher() self._device_msg_matcher = MIoTMatcher()
self.__deinit_socket() self.__deinit_socket()
# DO NOT force a event loop to stop. self._internal_loop.stop()
# It will stop when you release all handles properly.
def __init_socket(self) -> None: def __init_socket(self) -> None:
self.__deinit_socket() self.__deinit_socket()
@ -1237,6 +1228,7 @@ class MIoTLan:
# Reply # Reply
req: MIoTLanRequestData | None = self._pending_requests.pop(msg['id'], None) req: MIoTLanRequestData | None = self._pending_requests.pop(msg['id'], None)
if req: if req:
if req.timeout:
req.timeout.cancel() req.timeout.cancel()
if req.handler is not None: if req.handler is not None:
self._main_loop.call_soon_threadsafe( self._main_loop.call_soon_threadsafe(
@ -1329,42 +1321,3 @@ class MIoTLan:
self._last_scan_interval = min( self._last_scan_interval = min(
self._last_scan_interval*2, self.OT_PROBE_INTERVAL_MAX) self._last_scan_interval*2, self.OT_PROBE_INTERVAL_MAX)
return self._last_scan_interval return self._last_scan_interval
async def __on_network_info_change(
self,
status: InterfaceStatus,
info: NetworkInfo
) -> None:
_LOGGER.info(
'on network info change, status: %s, info: %s', status, info)
available_net_ifs = set()
for if_name in list(self._network.network_info.keys()):
available_net_ifs.add(if_name)
if len(available_net_ifs) == 0:
await self.deinit_async()
self._available_net_ifs = available_net_ifs
return
if self._net_ifs.isdisjoint(available_net_ifs):
_LOGGER.info('no valid net_ifs')
await self.deinit_async()
self._available_net_ifs = available_net_ifs
return
if not self._init_done:
self._available_net_ifs = available_net_ifs
await self.init_async()
return
self.__lan_send_cmd(
MIoTLanCmdType.NET_INFO_UPDATE, MIoTLanNetworkUpdateData(
status=status, if_name=info.name))
async def __on_mips_service_change(
self, group_id: str, state: MipsServiceState, data: dict
) -> None:
_LOGGER.info(
'on mips service change, %s, %s, %s', group_id, state, data)
if len(self._mips_service.get_services()) > 0:
_LOGGER.info('find central service, deinit miot lan')
await self.deinit_async()
else:
_LOGGER.info('no central service, init miot lan')
await self.init_async()