feat: improve config flow devices filter

This commit is contained in:
topsworld 2024-12-23 15:07:24 +08:00
parent 5d9a00d0b7
commit 13d76b66de
23 changed files with 164 additions and 100 deletions

View File

@ -50,7 +50,7 @@ import hashlib
import json
import secrets
import traceback
from typing import Optional
from typing import Optional, Set
from aiohttp import web
from aiohttp.hdrs import METH_GET
import voluptuous as vol
@ -116,9 +116,10 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_area_name_rule: Optional[str]
_action_debug: bool
_hide_non_standard_entities: bool
_display_devices_changed_notify: bool
_auth_info: Optional[dict]
_nick_name: Optional[str]
_home_selected: Optional[dict]
_home_selected: dict
_home_info_buffer: Optional[dict[str, str | dict[str, dict]]]
_home_list: Optional[dict]
_device_list_sorted: dict
@ -152,6 +153,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._area_name_rule = None
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._home_selected = {}
@ -365,13 +367,6 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
except (MIoTOauthError, json.JSONDecodeError):
self._nick_name = DEFAULT_NICK_NAME
_LOGGER.error('get nick name failed')
# Save auth_info
if not (await self._miot_storage.update_user_config_async(
uid=self._uid, cloud_server=self._cloud_server, config={
'auth_info': self._auth_info
})):
raise MIoTError(
'miot_storage.update_user_config_async error')
except Exception as err:
_LOGGER.error(
'get_access_token, %s, %s', err, traceback.format_exc())
@ -385,6 +380,12 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._uid = self._home_info_buffer['uid']
if self._uid == self._nick_name:
self._nick_name = DEFAULT_NICK_NAME
# Save auth_info
if not (await self._miot_storage.update_user_config_async(
uid=self._uid, cloud_server=self._cloud_server, config={
'auth_info': self._auth_info
})):
raise MIoTError('miot_storage.update_user_config_async error')
except Exception as err:
_LOGGER.error(
'get_homeinfos error, %s, %s', err, traceback.format_exc())
@ -495,11 +496,11 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_LOGGER.debug('async_step_homes_select')
try:
if user_input is None:
return await self.display_homes_select_form('')
return await self.__display_homes_select_form('')
home_selected: list = user_input.get('home_infos', [])
if not home_selected:
return await self.display_homes_select_form(
return await self.__display_homes_select_form(
'no_family_selected')
for home_id, home_info in self._home_info_buffer[
'homes']['home_list'].items():
@ -512,7 +513,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
for did, dev_info in self._home_info_buffer['devices'].items()
if dev_info['home_id'] in home_selected}
if not devices_list:
return await self.display_homes_select_form('no_devices')
return await self.__display_homes_select_form('no_devices')
self._device_list_sorted = dict(sorted(
devices_list.items(), key=lambda item:
item[1].get('home_id', '')+item[1].get('room_id', '')))
@ -524,7 +525,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_LOGGER.error(
'save devices async failed, %s, %s',
self._uid, self._cloud_server)
return await self.display_homes_select_form(
return await self.__display_homes_select_form(
'devices_storage_failed')
if user_input.get('advanced_options', False):
return await self.async_step_advanced_options()
@ -539,7 +540,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
'error': f'config_flow error, {err}'}
) from err
async def display_homes_select_form(self, reason: str):
async def __display_homes_select_form(self, reason: str):
return self.async_show_form(
step_id='homes_select',
data_schema=vol.Schema({
@ -576,7 +577,9 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
vol.Required(
'hide_non_standard_entities',
default=self._hide_non_standard_entities): bool,
vol.Required(
'display_devices_changed_notify',
default=self._display_devices_changed_notify): bool,
}),
last_step=False,
)
@ -586,93 +589,68 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
):
if user_input:
# Room filter
include_items: dict = {}
exclude_items: dict = {}
room_list_in: list = user_input.get('room_list', [])
if room_list_in:
room_filter_mode: str = user_input.get(
'room_filter_mode', None)
if room_filter_mode == 'exclude':
self._device_list_filter = {
did: dev_info
for did, dev_info in self._device_list_sorted.items()
if dev_info['room_id'] not in room_list_in}
elif room_filter_mode == 'include':
self._device_list_filter = {
did: dev_info
for did, dev_info in self._device_list_sorted.items()
if dev_info['room_id'] in room_list_in}
# Type filter
if user_input.get(
'room_filter_mode', 'include') == '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', [])
if type_list_in:
type_filter_mode: str = user_input.get(
'type_filter_mode', None)
if type_filter_mode == 'exclude':
self._device_list_filter = {
did: dev_info
for did, dev_info in self._device_list_sorted.items()
if dev_info['connect_type'] not in type_list_in}
elif type_filter_mode == 'include':
self._device_list_filter = {
did: dev_info
for did, dev_info in self._device_list_sorted.items()
if str(dev_info['connect_type']) in type_list_in}
if user_input.get(
'type_filter_mode', 'include') == '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', [])
if model_list_in:
model_filter_mode: str = user_input.get(
'model_filter_mode', None)
if model_filter_mode == 'exclude':
self._device_list_filter = {
did: dev_info
for did, dev_info in self._device_list_sorted.items()
if dev_info['model'] not in model_list_in}
elif model_filter_mode == 'include':
self._device_list_filter = {
did: dev_info
for did, dev_info in self._device_list_sorted.items()
if dev_info['model'] in model_list_in}
if user_input.get(
'model_filter_mode', 'include') == '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', [])
if device_list_in:
devices_filter_mode: str = user_input.get(
'devices_filter_mode', None)
if devices_filter_mode == 'exclude':
self._device_list_filter = {
did: dev_info
for did, dev_info in self._device_list_sorted.items()
if did not in device_list_in}
elif devices_filter_mode == 'include':
self._device_list_filter = {
did: dev_info
for did, dev_info in self._device_list_sorted.items()
if did in device_list_in}
if user_input.get(
'devices_filter_mode', 'include') == 'include':
include_items['did'] = device_list_in
else:
raise MIoTError('invalid devices_filter_mode')
if self._device_list_filter:
exclude_items['did'] = device_list_in
device_filter_list = self.__devices_filter(
devices=self._device_list_sorted,
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'})
self._device_list_sorted = dict(sorted(
self._device_list_filter.items(), key=lambda item:
device_filter_list.items(), key=lambda item:
item[1].get('home_id', '')+item[1].get('room_id', '')))
# Save devices
if not await self._miot_storage.save_async(
domain='miot_devices',
name=f'{self._uid}_{self._cloud_server}',
data=self._device_list_sorted):
_LOGGER.error(
'save devices async failed, %s, %s',
self._uid, self._cloud_server)
raise AbortFlow(
reason='config_flow_error',
description_placeholders={
'error': 'save devices failed'})
return await self.__display_devices_filter_form(
reason='no_devices_selected')
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')
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(
@ -684,24 +662,21 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
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}')
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.items():
for room_id, room_name in home_info['room_info'].items():
@ -714,7 +689,6 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
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({
@ -731,11 +705,47 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
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])))
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
)
def __devices_filter(
self, devices: dict, logic_or: bool, item_in: dict, item_ex: dict
) -> dict:
include_set: Set = set([])
if not item_in:
include_set = set(devices.keys())
else:
filter_item: list[set] = []
for key, value in item_in.items():
filter_item.append(set([
did for did, info in devices.items()
if str(info[key]) in value]))
include_set = (
set.union(*filter_item)
if logic_or else set.intersection(*filter_item))
if not include_set:
return {}
if item_ex:
filter_item: list[set] = []
for key, value in item_ex.items():
filter_item.append(set([
did for did, info in devices.items()
if str(info[key]) in value]))
exclude_set: Set = (
set.union(*filter_item)
if logic_or else set.intersection(*filter_item))
if exclude_set:
include_set = include_set-exclude_set
if not include_set:
return {}
return {
did: info for did, info in devices.items() if did in include_set}
async def config_flow_done(self):
return self.async_create_entry(
title=(
@ -756,6 +766,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
'action_debug': self._action_debug,
'hide_non_standard_entities':
self._hide_non_standard_entities,
'display_devices_changed_notify':
self._display_devices_changed_notify
})
@ staticmethod
@ -1206,11 +1218,11 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
if home_id in home_list]
self._home_list = dict(sorted(home_list.items()))
return await self.display_homes_select_form('')
return await self.__display_homes_select_form('')
self._home_selected_list = user_input.get('home_infos', [])
if not self._home_selected_list:
return await self.display_homes_select_form('no_family_selected')
return await self.__display_homes_select_form('no_family_selected')
self._ctrl_mode = user_input.get('ctrl_mode')
self._home_selected_dict = {}
for home_id, home_info in self._home_info_buffer[
@ -1223,7 +1235,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
for did, dev_info in self._home_info_buffer['devices'].items()
if dev_info['home_id'] in self._home_selected_list}
if not self._device_list:
return await self.display_homes_select_form('no_devices')
return await self.__display_homes_select_form('no_devices')
# Statistics devices changed
self._devices_add = []
self._devices_remove = []
@ -1241,17 +1253,14 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
self._devices_add, self._devices_remove)
return await self.async_step_update_trans_rules()
async def display_homes_select_form(self, reason: str):
async def __display_homes_select_form(self, reason: str):
return self.async_show_form(
step_id='homes_select',
data_schema=vol.Schema({
vol.Required(
'ctrl_mode', default=self._ctrl_mode
): vol.In(self._miot_i18n.translate(key='config.control_mode')),
vol.Required(
'home_infos',
default=self._home_selected_list
): cv.multi_select(self._home_list),
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(
self._miot_i18n.translate(key='config.control_mode')),
}),
errors={'base': reason},
description_placeholders={

View File

@ -9,6 +9,10 @@
"auto": "automatisch",
"cloud": "Cloud"
},
"statistics_logic": {
"or": "ODER-Logik",
"and": "UND-Logik"
},
"filter_mode": {
"exclude": "ausschließen",
"include": "einschließen"

View File

@ -9,6 +9,10 @@
"auto": "Auto",
"cloud": "Cloud"
},
"statistics_logic": {
"or": "OR logic",
"and": "AND logic"
},
"filter_mode": {
"exclude": "Exclude",
"include": "Include"

View File

@ -9,6 +9,10 @@
"auto": "automático",
"cloud": "nube"
},
"statistics_logic": {
"or": "lógica OR",
"and": "lógica AND"
},
"filter_mode": {
"exclude": "excluir",
"include": "incluir"

View File

@ -9,6 +9,10 @@
"auto": "automatique",
"cloud": "cloud"
},
"statistics_logic": {
"or": "ou logique",
"and": "et logique"
},
"filter_mode": {
"exclude": "exclure",
"include": "inclure"

View File

@ -9,6 +9,10 @@
"auto": "自動",
"cloud": "クラウド"
},
"statistics_logic": {
"or": "ORロジック",
"and": "ANDロジック"
},
"filter_mode": {
"exclude": "除外",
"include": "含む"

View File

@ -9,6 +9,10 @@
"auto": "Automatisch",
"cloud": "Cloud"
},
"statistics_logic": {
"or": "OF-logica",
"and": "EN-logica"
},
"filter_mode": {
"exclude": "Uitsluiten",
"include": "Inclusief"

View File

@ -9,6 +9,10 @@
"auto": "automático",
"cloud": "nuvem"
},
"statistics_logic": {
"or": "lógica OU",
"and": "lógica E"
},
"filter_mode": {
"exclude": "excluir",
"include": "incluir"

View File

@ -9,6 +9,10 @@
"auto": "Automático",
"cloud": "Nuvem"
},
"statistics_logic": {
"or": "Ou lógica",
"and": "E lógica"
},
"filter_mode": {
"exclude": "Excluir",
"include": "Incluir"

View File

@ -9,6 +9,10 @@
"auto": "автоматический",
"cloud": "облако"
},
"statistics_logic": {
"or": "логика ИЛИ",
"and": "логика И"
},
"filter_mode": {
"exclude": "исключить",
"include": "включить"

View File

@ -15,6 +15,10 @@
"room": "房间名 (卧室)",
"home": "家庭名 (米家)"
},
"statistics_logic": {
"or": "或逻辑",
"and": "与逻辑"
},
"filter_mode": {
"exclude": "排除",
"include": "包含"

View File

@ -9,6 +9,10 @@
"auto": "自動",
"cloud": "雲端"
},
"statistics_logic": {
"or": "或邏輯",
"and": "與邏輯"
},
"filter_mode": {
"exclude": "排除",
"include": "包含"

View File

@ -45,6 +45,7 @@
"title": "Geräte filtern",
"description": "## Gebrauchsanweisung\r\n- Unterstützt das Filtern von Geräten nach Raumnamen und Gerätetypen sowie das Filtern nach Gerätedimensionen.\r\n- Sie können auch die entsprechende Integrationsoption \"Konfiguration> Geräteliste aktualisieren\" aufrufen, um die Filterung erneut durchzuführen.",
"data": {
"statistics_logic": "Statistiklogik",
"room_filter_mode": "Familienraum filtern",
"room_list": "Familienraum",
"type_filter_mode": "Gerätetyp filtern",

View File

@ -45,6 +45,7 @@
"title": "Filter Devices",
"description": "## Introduction\r\n- Supports filtering devices by room name and device type, and also supports device dimension filtering.\r\n- You can also re-filter on the corresponding integration page [Configuration>Update Device List].",
"data": {
"statistics_logic": "Statistics Logic",
"room_filter_mode": "Filter Family Rooms",
"room_list": "Family Rooms",
"type_filter_mode": "Filter Device Connect Type",

View File

@ -45,6 +45,7 @@
"title": "Filtrar Dispositivos",
"description": "## Introducción\r\n- Admite la filtración de dispositivos por nombre de habitación y tipo de dispositivo, y también admite la filtración por familia.\r\n- También puede volver a filtrar en la página correspondiente de la integración [Configuración>Actualizar lista de dispositivos].",
"data": {
"statistics_logic": "Lógica de Estadísticas",
"room_filter_mode": "Filtrar Habitaciones de la Familia",
"room_list": "Habitaciones de la Familia",
"type_filter_mode": "Filtrar Tipo de Dispositivo",

View File

@ -45,6 +45,7 @@
"title": "Filtrer les Appareils",
"description": "## Introduction\r\n- Prend en charge le filtrage des appareils en fonction du nom de la pièce et du type d'appareil, ainsi que le filtrage basé sur les appareils.\r\n- Vous pouvez également accéder à la page de filtrage correspondante de l'intégration [Configuration> Mettre à jour la liste des appareils] pour refiltrer.",
"data": {
"statistics_logic": "Logique de Statistiques",
"room_filter_mode": "Filtrer les Pièces",
"room_list": "Pièces",
"type_filter_mode": "Filtrer les Types d'Appareils",

View File

@ -45,6 +45,7 @@
"title": "デバイスをフィルタリング",
"description": "## 紹介\r\n- 部屋名とデバイスタイプでデバイスをフィルタリングすることができます。デバイスの次元でフィルタリングすることもできます。\r\n- 対応する統合項目【設定>デバイスリストの更新】ページに移動して再度フィルタリングすることもできます。",
"data": {
"statistics_logic": "統計ロジック",
"room_filter_mode": "家族の部屋をフィルタリング",
"room_list": "家族の部屋",
"type_filter_mode": "デバイスタイプをフィルタリング",

View File

@ -45,6 +45,7 @@
"title": "Apparaten filteren",
"description": "## Inleiding\r\n- Ondersteunt het filteren van apparaten op basis van kamer- en apparaattypen, en ondersteunt ook apparaatdimensiefiltering.\r\n- U kunt ook naar de overeenkomstige integratie-item [Configuratie>Apparaatlijst bijwerken] pagina gaan om opnieuw te filteren.",
"data": {
"statistics_logic": "Statistische logica",
"room_filter_mode": "Kamerfiltermodus",
"room_list": "Kamers",
"type_filter_mode": "Apparaattypen filteren",

View File

@ -45,6 +45,7 @@
"title": "Filtrar Dispositivos",
"description": "## Introdução\r\n- Suporte para filtrar dispositivos por nome da sala e tipo de dispositivo, bem como filtragem por família.\r\n- Você também pode acessar a página correspondente da integração [Configuração>Atualizar Lista de Dispositivos] para refiltrar.",
"data": {
"statistics_logic": "Lógica de Estatísticas",
"room_filter_mode": "Filtrar por Sala",
"room_list": "Salas",
"type_filter_mode": "Filtrar por Tipo de Dispositivo",

View File

@ -45,6 +45,7 @@
"title": "Filtrar Dispositivos",
"description": "## Introdução\r\n- Suporta a filtragem de dispositivos por nome de sala e tipo de dispositivo, bem como a filtragem por família.\r\n- Pode também aceder à página de configuração correspondente da integração [Configuração > Atualizar Lista de Dispositivos] para refazer a filtragem.",
"data": {
"statistics_logic": "Lógica de Estatísticas",
"room_filter_mode": "Filtrar por Sala",
"room_list": "Salas",
"type_filter_mode": "Filtrar por Tipo de Dispositivo",

View File

@ -45,6 +45,7 @@
"title": "Фильтрация устройств",
"description": "## Введение\r\n- Поддерживает фильтрацию устройств по названию комнаты и типу устройства, а также фильтрацию по уровню устройства.\r\n- Вы также можете перейти на соответствующую страницу интеграции [Настройки> Обновить список устройств], чтобы перефильтровать.",
"data": {
"statistics_logic": "Логика статистики",
"room_filter_mode": "Фильтрация по комнатам семьи",
"room_list": "Комнаты семьи",
"type_filter_mode": "Фильтрация по типу устройства",

View File

@ -43,8 +43,9 @@
},
"devices_filter": {
"title": "筛选设备",
"description": "## 使用介绍\r\n- 支持按照房间名称和设备类型筛选设备,同时也支持设备维度筛选。\r\n- 您也可以进入对应集成项【配置>更新设备列表】页面重新筛选。",
"description": "## 使用介绍\r\n支持按照房间名称、设备接入类型、设备型号筛选设备,同时也支持设备维度筛选。\r\n- 统计优先级:排除优先级高于包含优先级,会先取包含项,然后再排除。\r\n- 筛选优先级:筛选设备>筛选设备型号>筛选设备接入类型>筛选家庭房间\r\n### 统计逻辑\r\n- 与逻辑:取所有同模式筛选项的交集。\r\n- 或逻辑:取所有同模式筛选项的并集。\r\n### 筛选模式\r\n- 排除:移除不需要的项。\r\n- 包含:包含需要的项。\r\n- 您也可以进入对应集成项【配置>更新设备列表】页面重新筛选。",
"data": {
"statistics_logic": "统计逻辑",
"room_filter_mode": "筛选家庭房间",
"room_list": "家庭房间",
"type_filter_mode": "筛选设备接入类型",

View File

@ -45,6 +45,7 @@
"title": "篩選設備",
"description": "## 使用介紹\r\n- 支持按照房間名稱和設備類型篩選設備,同時也支持設備維度篩選。\r\n- 您也可以進入對應集成項【配置>更新設備列表】頁面重新篩選。",
"data": {
"statistics_logic": "統計邏輯",
"room_filter_mode": "篩選家庭房間",
"room_list": "家庭房間",
"type_filter_mode": "篩選設備接入類型",