diff --git a/custom_components/xiaomi_home/config_flow.py b/custom_components/xiaomi_home/config_flow.py index 46bab14..79517a4 100644 --- a/custom_components/xiaomi_home/config_flow.py +++ b/custom_components/xiaomi_home/config_flow.py @@ -101,37 +101,39 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # pylint: disable=unused-argument, inconsistent-quotes VERSION = 1 MINOR_VERSION = 1 + DEFAULT_AREA_NAME_RULE = 'room' _main_loop: asyncio.AbstractEventLoop - _mips_service: Optional[MipsService] - _miot_storage: Optional[MIoTStorage] - _miot_network: Optional[MIoTNetwork] - _miot_i18n: Optional[MIoTI18n] + _miot_network: MIoTNetwork + _mips_service: MipsService + _miot_storage: MIoTStorage + _miot_i18n: MIoTI18n - _integration_language: Optional[str] - _storage_path: Optional[str] - _virtual_did: Optional[str] - _uid: Optional[str] - _uuid: Optional[str] - _ctrl_mode: Optional[str] - _area_name_rule: Optional[str] + _integration_language: str + _storage_path: str + _virtual_did: str + _uid: str + _uuid: str + _ctrl_mode: str + _area_name_rule: str _action_debug: bool _hide_non_standard_entities: bool _display_devices_changed_notify: bool - _auth_info: Optional[dict] - _nick_name: Optional[str] + + _auth_info: dict + _nick_name: str _home_selected: dict - _home_info_buffer: Optional[dict[str, str | dict[str, dict]]] - _home_list: Optional[dict] + _home_info_buffer: dict + _home_list_show: dict _device_list_sorted: dict _device_list_filter: dict - _cloud_server: Optional[str] - _oauth_redirect_url: Optional[str] + _cloud_server: str + _oauth_redirect_url_full: str _miot_oauth: Optional[MIoTOauthClient] _miot_http: Optional[MIoTHttpClient] _user_cert_state: bool - _oauth_auth_url: Optional[str] + _oauth_auth_url: str _task_oauth: Optional[asyncio.Task[None]] _config_error_reason: Optional[str] @@ -139,70 +141,65 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: self._main_loop = asyncio.get_running_loop() - self._mips_service = None - self._miot_storage = None - self._miot_network = None - self._miot_i18n = None - - self._integration_language = None - self._storage_path = None - self._virtual_did = None - self._uid = None - self._uuid = None # MQTT client id - self._ctrl_mode = None - self._area_name_rule = None + self._integration_language = DEFAULT_INTEGRATION_LANGUAGE + self._storage_path = '' + self._virtual_did = '' + self._uid = '' + self._uuid = '' # MQTT client id + self._ctrl_mode = DEFAULT_CTRL_MODE + self._area_name_rule = self.DEFAULT_AREA_NAME_RULE self._action_debug = False self._hide_non_standard_entities = False self._display_devices_changed_notify = True - self._auth_info = None - self._nick_name = None + self._auth_info = {} + self._nick_name = DEFAULT_NICK_NAME self._home_selected = {} - self._home_info_buffer = None - self._home_list = None - self._device_list_sorted = None + self._home_info_buffer = {} + self._home_list_show = {} + self._device_list_sorted = {} - self._cloud_server = None - self._oauth_redirect_url = None + self._cloud_server = DEFAULT_CLOUD_SERVER + self._oauth_redirect_url_full = '' self._miot_oauth = None self._miot_http = None - self._user_cert_state = False - self._oauth_auth_url = None + self._user_cert_state = False + self._oauth_auth_url = '' self._task_oauth = None self._config_error_reason = None self._fut_oauth_code = None - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: Optional[dict] = None + ): self.hass.data.setdefault(DOMAIN, {}) - loop: asyncio.AbstractEventLoop = asyncio.get_running_loop() - - if self._virtual_did is None: + if not self._virtual_did: self._virtual_did = str(secrets.randbits(64)) self.hass.data[DOMAIN].setdefault(self._virtual_did, {}) - if self._storage_path is None: + if not self._storage_path: self._storage_path = self.hass.config.path('.storage', DOMAIN) # MIoT network self._miot_network = self.hass.data[DOMAIN].get('miot_network', None) - if self._miot_network is None: - self._miot_network = MIoTNetwork(loop=loop) + if not self._miot_network: + self._miot_network = MIoTNetwork(loop=self._main_loop) self.hass.data[DOMAIN]['miot_network'] = self._miot_network await self._miot_network.init_async( refresh_interval=NETWORK_REFRESH_INTERVAL) _LOGGER.info('async_step_user, create miot network') # Mips server self._mips_service = self.hass.data[DOMAIN].get('mips_service', None) - if self._mips_service is None: + if not self._mips_service: aiozc: HaAsyncZeroconf = await zeroconf.async_get_async_instance( self.hass) - self._mips_service = MipsService(aiozc=aiozc, loop=loop) + self._mips_service = MipsService(aiozc=aiozc, loop=self._main_loop) self.hass.data[DOMAIN]['mips_service'] = self._mips_service await self._mips_service.init_async() _LOGGER.info('async_step_user, create mips service') # MIoT storage self._miot_storage = self.hass.data[DOMAIN].get('miot_storage', None) - if self._miot_storage is None: + if not self._miot_storage: self._miot_storage = MIoTStorage( - root_path=self._storage_path, loop=loop) + root_path=self._storage_path, loop=self._main_loop) self.hass.data[DOMAIN]['miot_storage'] = self._miot_storage _LOGGER.info( 'async_step_user, create miot storage, %s', self._storage_path) @@ -214,7 +211,9 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_eula(user_input) - async def async_step_eula(self, user_input=None): + async def async_step_eula( + self, user_input: Optional[dict] = None + ): if user_input: if user_input.get('eula', None) is True: return await self.async_step_auth_config() @@ -225,16 +224,18 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id='eula', data_schema=vol.Schema({ - vol.Required('eula', default=False): bool, + vol.Required('eula', default=False): bool, # type: ignore }), last_step=False, errors={'base': reason}, ) - async def async_step_auth_config(self, user_input=None): + async def async_step_auth_config( + self, user_input: Optional[dict] = None + ): if user_input: self._cloud_server = user_input.get( - 'cloud_server', DEFAULT_CLOUD_SERVER) + 'cloud_server', self._cloud_server) self._integration_language = user_input.get( 'integration_language', DEFAULT_INTEGRATION_LANGUAGE) self._miot_i18n = MIoTI18n( @@ -242,7 +243,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self._miot_i18n.init_async() webhook_path = webhook_async_generate_path( webhook_id=self._virtual_did) - self._oauth_redirect_url = ( + self._oauth_redirect_url_full = ( f'{user_input.get("oauth_redirect_url")}{webhook_path}') return await self.async_step_oauth(user_input) # Generate default language from HomeAssistant config (not user config) @@ -257,33 +258,38 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): data_schema=vol.Schema({ vol.Required( 'cloud_server', - default=DEFAULT_CLOUD_SERVER): vol.In(CLOUD_SERVERS), + default=self._cloud_server # type: ignore + ): vol.In(CLOUD_SERVERS), vol.Required( 'integration_language', - default=default_language): vol.In(INTEGRATION_LANGUAGES), + default=default_language # type: ignore + ): vol.In(INTEGRATION_LANGUAGES), vol.Required( 'oauth_redirect_url', - default=OAUTH_REDIRECT_URL): vol.In([OAUTH_REDIRECT_URL]), + default=OAUTH_REDIRECT_URL # type: ignore + ): vol.In([OAUTH_REDIRECT_URL]), }), last_step=False, ) - async def async_step_oauth(self, user_input=None): + async def async_step_oauth( + self, user_input: Optional[dict] = None + ): # 1: Init miot_oauth, generate auth url try: - if self._miot_oauth is None: + if not self._miot_oauth: _LOGGER.info( 'async_step_oauth, redirect_url: %s', - self._oauth_redirect_url) + self._oauth_redirect_url_full) miot_oauth = MIoTOauthClient( client_id=OAUTH2_CLIENT_ID, - redirect_url=self._oauth_redirect_url, + redirect_url=self._oauth_redirect_url_full, cloud_server=self._cloud_server ) state = str(secrets.randbits(64)) self.hass.data[DOMAIN][self._virtual_did]['oauth_state'] = state self._oauth_auth_url = miot_oauth.gen_auth_url( - redirect_url=self._oauth_redirect_url, state=state) + redirect_url=self._oauth_redirect_url_full, state=state) _LOGGER.info( 'async_step_oauth, oauth_url: %s', self._oauth_auth_url) webhook_async_unregister( @@ -298,7 +304,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) self._fut_oauth_code = self.hass.data[DOMAIN][ self._virtual_did].get('fut_oauth_code', None) - if self._fut_oauth_code is None: + if not self._fut_oauth_code: self._fut_oauth_code = self._main_loop.create_future() self.hass.data[DOMAIN][self._virtual_did][ 'fut_oauth_code'] = self._fut_oauth_code @@ -337,11 +343,16 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def __check_oauth_async(self) -> None: # TASK 1: Get oauth code + if not self._fut_oauth_code: + raise MIoTConfigError('oauth_code_fut_error') oauth_code: Optional[str] = await self._fut_oauth_code - + if not oauth_code: + raise MIoTConfigError('oauth_code_error') # TASK 2: Get access_token and user_info from miot_oauth if not self._auth_info: try: + if not self._miot_oauth: + raise MIoTConfigError('oauth_client_error') auth_info = await self._miot_oauth.get_access_token_async( code=oauth_code) if not self._miot_http: @@ -363,7 +374,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): try: self._nick_name = ( await self._miot_http.get_user_info_async() or {} - ).get('miliaoNick', DEFAULT_NICK_NAME) + ).get('miliaoNick', self._nick_name) except (MIoTOauthError, json.JSONDecodeError): self._nick_name = DEFAULT_NICK_NAME _LOGGER.error('get nick name failed') @@ -374,6 +385,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # TASK 3: Get home info try: + if not self._miot_http: + raise MIoTConfigError('http_client_error') self._home_info_buffer = ( await self._miot_http.get_devices_async()) _LOGGER.info('get_homeinfos response: %s', self._home_info_buffer) @@ -433,7 +446,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): f'{home_info["home_name"]} ' f'[ {len(dev_list)} {tip_devices} {tip_central} ]') - self._home_list = dict(sorted(home_list.items())) + self._home_list_show = dict(sorted(home_list.items())) # TASK 7: Get user's MiHome certificate if self._cloud_server in SUPPORT_CENTRAL_GATEWAY_CTRL: @@ -454,6 +467,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): user_key=user_key, did=self._virtual_did) crt_str = await self._miot_http.get_central_cert_async( csr_str) + if not crt_str: + raise MIoTError('get_central_cert_async failed') if not await miot_cert.update_user_cert_async( cert=crt_str): raise MIoTError('update_user_cert_async failed') @@ -492,10 +507,12 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors={'base': error_reason}, ) - async def async_step_homes_select(self, user_input=None): + async def async_step_homes_select( + self, user_input: Optional[dict] = None + ): _LOGGER.debug('async_step_homes_select') try: - if user_input is None: + if not user_input: return await self.__display_homes_select_form('') home_selected: list = user_input.get('home_infos', []) @@ -506,7 +523,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 'homes']['home_list'].items(): if home_id in home_selected: self._home_selected[home_id] = home_info - self._area_name_rule = user_input.get('area_name_rule') + self._area_name_rule = user_input.get( + 'area_name_rule', self._area_name_rule) # Storage device list devices_list: dict[str, dict] = { did: dev_info @@ -544,10 +562,16 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id='homes_select', data_schema=vol.Schema({ - vol.Required('home_infos'): cv.multi_select(self._home_list), - vol.Required('area_name_rule', default='room'): vol.In( - self._miot_i18n.translate(key='config.room_name_rule')), - vol.Required('advanced_options', default=False): bool, + vol.Required('home_infos'): cv.multi_select( + self._home_list_show), + vol.Required( + 'area_name_rule', + default=self._area_name_rule # type: ignore + ): vol.In(self._miot_i18n.translate( + key='config.room_name_rule')), + vol.Required( + 'advanced_options', default=False # type: ignore + ): bool, }), errors={'base': reason}, description_placeholders={ @@ -556,7 +580,9 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): last_step=False, ) - async def async_step_advanced_options(self, user_input: dict = None): + async def async_step_advanced_options( + self, user_input: Optional[dict] = None + ): if user_input: self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode) self._action_debug = user_input.get( @@ -570,16 +596,22 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id='advanced_options', data_schema=vol.Schema({ - vol.Required('devices_filter', default=False): bool, - vol.Required('ctrl_mode', default=DEFAULT_CTRL_MODE): vol.In( - self._miot_i18n.translate(key='config.control_mode')), - vol.Required('action_debug', default=self._action_debug): bool, + vol.Required( + 'devices_filter', default=False): bool, # type: ignore + vol.Required( + 'ctrl_mode', default=self._ctrl_mode # type: ignore + ): vol.In(self._miot_i18n.translate(key='config.control_mode')), + vol.Required( + 'action_debug', default=self._action_debug # type: ignore + ): bool, vol.Required( 'hide_non_standard_entities', - default=self._hide_non_standard_entities): bool, + default=self._hide_non_standard_entities # type: ignore + ): bool, vol.Required( 'display_devices_changed_notify', - default=self._display_devices_changed_notify): bool, + default=self._display_devices_changed_notify # type: ignore + ): bool, }), last_step=False, ) @@ -645,16 +677,17 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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') + key='config.other.devices') # type: ignore tip_without_room: str = self._miot_i18n.translate( - key='config.other.without_room') + key='config.other.without_room') # type: ignore trans_statistics_logic: dict = self._miot_i18n.translate( - key='config.statistics_logic') + key='config.statistics_logic') # type: ignore trans_filter_mode: dict = self._miot_i18n.translate( - key='config.filter_mode') + key='config.filter_mode') # type: ignore trans_connect_type: dict = self._miot_i18n.translate( - key='config.connect_type') + key='config.connect_type') # type: ignore room_device_count: dict = {} model_device_count: dict = {} @@ -692,22 +725,27 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id='devices_filter', data_schema=vol.Schema({ - vol.Required('room_filter_mode', default='exclude'): - vol.In(trans_filter_mode), + vol.Required( + 'room_filter_mode', default='exclude' # type: ignore + ): vol.In(trans_filter_mode), vol.Optional('room_list'): cv.multi_select(room_list), - vol.Required('type_filter_mode', default='exclude'): - vol.In(trans_filter_mode), + vol.Required( + 'type_filter_mode', default='exclude' # type: ignore + ): vol.In(trans_filter_mode), vol.Optional('type_list'): cv.multi_select(type_list), - vol.Required('model_filter_mode', default='exclude'): - vol.In(trans_filter_mode), + vol.Required( + 'model_filter_mode', default='exclude' # type: ignore + ): vol.In(trans_filter_mode), vol.Optional('model_list'): cv.multi_select(dict(sorted( model_list.items(), key=lambda item: item[0]))), - vol.Required('devices_filter_mode', default='exclude'): - vol.In(trans_filter_mode), + vol.Required( + 'devices_filter_mode', default='exclude' # type: ignore + ): vol.In(trans_filter_mode), vol.Optional('device_list'): cv.multi_select(dict(sorted( device_list.items(), key=lambda device: device[1]))), - vol.Required('statistics_logic', default='or'): - vol.In(trans_statistics_logic), + vol.Required( + 'statistics_logic', default='or' # type: ignore + ): vol.In(trans_statistics_logic), }), errors={'base': reason}, last_step=False @@ -759,7 +797,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 'uid': self._uid, 'nick_name': self._nick_name, 'cloud_server': self._cloud_server, - 'oauth_redirect_url': self._oauth_redirect_url, + 'oauth_redirect_url': self._oauth_redirect_url_full, 'ctrl_mode': self._ctrl_mode, 'home_selected': self._home_selected, 'area_name_rule': self._area_name_rule, @@ -784,7 +822,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): # pylint: disable=inconsistent-quotes _config_entry: config_entries.ConfigEntry _main_loop: asyncio.AbstractEventLoop - _miot_client: Optional[MIoTClient] + _miot_client: MIoTClient _miot_network: Optional[MIoTNetwork] _miot_storage: Optional[MIoTStorage] @@ -799,7 +837,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): _uid: Optional[str] _storage_path: Optional[str] _cloud_server: Optional[str] - _oauth_redirect_url: Optional[str] + _oauth_redirect_url_full: str _integration_language: Optional[str] _ctrl_mode: Optional[str] _nick_name: Optional[str] @@ -811,7 +849,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): _home_selected_dict: Optional[dict] _home_info_buffer: Optional[dict[str, str | dict[str, dict]]] _home_list: Optional[dict] - _device_list: Optional[dict[str, dict]] + _device_list: dict[str, dict] _devices_add: list[str] _devices_remove: list[str] @@ -851,7 +889,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self._uid = self._entry_data['uid'] self._storage_path = self._entry_data['storage_path'] self._cloud_server = self._entry_data['cloud_server'] - self._oauth_redirect_url = self._entry_data['oauth_redirect_url'] + self._oauth_redirect_url_full = '' self._ctrl_mode = self._entry_data['ctrl_mode'] self._integration_language = self._entry_data['integration_language'] self._nick_name = self._entry_data['nick_name'] @@ -959,7 +997,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): if user_input: webhook_path = webhook_async_generate_path( webhook_id=self._virtual_did) - self._oauth_redirect_url = ( + self._oauth_redirect_url_full = ( f'{user_input.get("oauth_redirect_url")}{webhook_path}') return await self.async_step_oauth(user_input) return self.async_show_form( @@ -981,9 +1019,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): state = str(secrets.randbits(64)) self.hass.data[DOMAIN][self._virtual_did]['oauth_state'] = state self._miot_oauth.set_redirect_url( - redirect_url=self._oauth_redirect_url) + redirect_url=self._oauth_redirect_url_full) self._oauth_auth_url = self._miot_oauth.gen_auth_url( - redirect_url=self._oauth_redirect_url, state=state) + redirect_url=self._oauth_redirect_url_full, state=state) _LOGGER.info( 'async_step_oauth, oauth_url: %s', self._oauth_auth_url) @@ -1166,7 +1204,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): self._nick_name_new = user_input.get('nick_name') return await self.async_step_homes_select() - async def async_step_homes_select(self, user_input=None): + async def async_step_homes_select( + self, user_input: Optional[dict] = None + ): if not self._update_devices: return await self.async_step_update_trans_rules() if not user_input: @@ -1236,27 +1276,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow): if dev_info['home_id'] in self._home_selected_list} if not self._device_list: return await self.__display_homes_select_form('no_devices') - # Statistics devices changed - self._devices_add = [] - self._devices_remove = [] - local_devices = await self._miot_storage.load_async( - domain='miot_devices', - name=f'{self._uid}_{self._cloud_server}', - type_=dict) or {} + self._device_list_sorted = dict(sorted( + self._device_list.items(), key=lambda item: + item[1].get('home_id', '')+item[1].get('room_id', ''))) - self._devices_add = [ - did for did in self._device_list.keys() if did not in local_devices] - self._devices_remove = [ - did for did in local_devices.keys() if did not in self._device_list] - _LOGGER.debug( - 'devices update, add->%s, remove->%s', - self._devices_add, self._devices_remove) - return await self.async_step_update_trans_rules() + if user_input.get('devices_filter', False): + return await self.async_step_devices_filter() + return await self.update_devices_done_async() async def __display_homes_select_form(self, reason: str): return self.async_show_form( step_id='homes_select', data_schema=vol.Schema({ + vol.Required('devices_filter', default=False): bool, vol.Required('home_infos', default=self._home_selected_list): cv.multi_select(self._home_list), vol.Required('ctrl_mode', default=self._ctrl_mode): vol.In( @@ -1269,6 +1301,100 @@ class OptionsFlowHandler(config_entries.OptionsFlow): last_step=False ) + async def async_step_devices_filter( + self, user_input: Optional[dict] = None + ): + if user_input: + return await self.update_devices_done_async() + 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') + tip_without_room: str = self._miot_i18n.translate( + key='config.other.without_room') + trans_statistics_logic: dict = self._miot_i18n.translate( + key='config.statistics_logic') + trans_filter_mode: dict = self._miot_i18n.translate( + key='config.filter_mode') + trans_connect_type: dict = self._miot_i18n.translate( + key='config.connect_type') + + room_device_count: dict = {} + model_device_count: dict = {} + connect_type_count: dict = {} + device_list: dict = {} + for did, info in self._device_list_sorted.items(): + device_list[did] = ( + f'[ {info["home_name"]} {info["room_name"]} ] ' + + f'{info["name"]}, {did}') + room_device_count.setdefault(info['room_id'], 0) + room_device_count[info['room_id']] += 1 + model_device_count.setdefault(info['model'], 0) + model_device_count[info['model']] += 1 + connect_type_count.setdefault(str(info['connect_type']), 0) + connect_type_count[str(info['connect_type'])] += 1 + model_list: dict = {} + for model, count in model_device_count.items(): + model_list[model] = f'{model} [ {count} {tip_devices} ]' + type_list: dict = { + k: f'{trans_connect_type.get(k, f"Connect Type ({k})")} ' + f'[ {v} {tip_devices} ]' + for k, v in connect_type_count.items()} + room_list: dict = {} + for home_id, home_info in self._home_selected_dict.items(): + for room_id, room_name in home_info['room_info'].items(): + if room_id not in room_device_count: + continue + room_list[room_id] = ( + f'{home_info["home_name"]} {room_name}' + f' [ {room_device_count[room_id]}{tip_devices} ]') + if home_id in room_device_count: + room_list[home_id] = ( + f'{home_info["home_name"]} {tip_without_room}' + f' [ {room_device_count[home_id]}{tip_devices} ]') + return self.async_show_form( + step_id='devices_filter', + data_schema=vol.Schema({ + vol.Required('room_filter_mode', default='exclude'): + vol.In(trans_filter_mode), + vol.Optional('room_list'): cv.multi_select(room_list), + vol.Required('type_filter_mode', default='exclude'): + vol.In(trans_filter_mode), + vol.Optional('type_list'): cv.multi_select(type_list), + vol.Required('model_filter_mode', default='exclude'): + vol.In(trans_filter_mode), + vol.Optional('model_list'): cv.multi_select(dict(sorted( + model_list.items(), key=lambda item: item[0]))), + vol.Required('devices_filter_mode', default='exclude'): + vol.In(trans_filter_mode), + vol.Optional('device_list'): cv.multi_select(dict(sorted( + device_list.items(), key=lambda device: device[1]))), + vol.Required('statistics_logic', default='or'): + vol.In(trans_statistics_logic), + }), + errors={'base': reason}, + last_step=False + ) + + async def update_devices_done_async(self): + # Statistics devices changed + self._devices_add = [] + self._devices_remove = [] + local_devices: dict = await self._miot_storage.load_async( + domain='miot_devices', + name=f'{self._uid}_{self._cloud_server}', + type_=dict) or {} # type: ignore + + self._devices_add = [ + did for did in self._device_list.keys() if did not in local_devices] + self._devices_remove = [ + did for did in local_devices.keys() if did not in self._device_list] + _LOGGER.debug( + 'devices update, add->%s, remove->%s', + self._devices_add, self._devices_remove) + return await self.async_step_update_trans_rules() + async def async_step_update_trans_rules(self, user_input=None): if not self._update_trans_rules: return await self.async_step_update_lan_ctrl_config() @@ -1397,7 +1523,6 @@ class OptionsFlowHandler(config_entries.OptionsFlow): last_step=True ) - self._entry_data['oauth_redirect_url'] = self._oauth_redirect_url if self._lang_new != self._integration_language: self._entry_data['integration_language'] = self._lang_new self._need_reload = True