From a879ae2cdfcba7999de527853782c00509b8623d Mon Sep 17 00:00:00 2001 From: Zeyu Li Date: Fri, 20 Dec 2024 17:34:03 +0800 Subject: [PATCH 01/17] docs: fix README_zh.md about changelog (#270) fix: error messages and incorrent return value which could possibly lead to crash in mint_storage.py --- custom_components/xiaomi_home/miot/miot_storage.py | 8 ++++---- doc/README_zh.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_storage.py b/custom_components/xiaomi_home/miot/miot_storage.py index 19f4b4f..dfd6b28 100644 --- a/custom_components/xiaomi_home/miot/miot_storage.py +++ b/custom_components/xiaomi_home/miot/miot_storage.py @@ -129,7 +129,7 @@ def __load( self, full_path: str, type_: type = bytes, with_hash_check: bool = True ) -> Union[bytes, str, dict, list, None]: if not os.path.exists(full_path): - _LOGGER.debug('load error, file not exists, %s', full_path) + _LOGGER.debug('load error, file does not exist, %s', full_path) return None if not os.access(full_path, os.R_OK): _LOGGER.error('load error, file not readable, %s', full_path) @@ -160,7 +160,7 @@ def __load( if type_ in [dict, list]: return json.loads(data_bytes) _LOGGER.error( - 'load error, un-support data type, %s', type_.__name__) + 'load error, unsupported data type, %s', type_.__name__) return None except (OSError, TypeError) as e: _LOGGER.error('load error, %s, %s', e, traceback.format_exc()) @@ -219,8 +219,8 @@ def __save( w_bytes = json.dumps(data).encode('utf-8') else: _LOGGER.error( - 'save error, un-support data type, %s', type_.__name__) - return None + 'save error, unsupported data type, %s', type_.__name__) + return False with open(full_path, 'wb') as w_file: w_file.write(w_bytes) if with_hash: diff --git a/doc/README_zh.md b/doc/README_zh.md index 3b4ead3..c221440 100644 --- a/doc/README_zh.md +++ b/doc/README_zh.md @@ -379,7 +379,7 @@ siid、piid、eiid、aiid、value 均为十进制三位整数。 - [许可证](../LICENSE.md) - 贡献指南: [English](../CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md) -- [更新日志](./CHANGELOG.md) +- [更新日志](../CHANGELOG.md) - 开发文档: https://developers.home-assistant.io/docs/creating_component_index ## 目录结构 From aacb794e1fe89caaa4da5c22a58d589b6630a275 Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Fri, 20 Dec 2024 17:34:34 +0800 Subject: [PATCH 02/17] feat: Use aiohttp instead of waiting for blocking calls (#227) * Use native async call instead of converting blocking calls * remove nullable declarations * fixs * Fix star expression * fix gather again * remove unused private function * Fix naming conflict * Add the deleted function back. Disable the warning instead. * remove trailing space * handle wrong mime type from cloud * Fix request header * fix missing await --- custom_components/xiaomi_home/manifest.json | 3 +- .../xiaomi_home/miot/miot_cloud.py | 185 +++++++----------- 2 files changed, 77 insertions(+), 111 deletions(-) diff --git a/custom_components/xiaomi_home/manifest.json b/custom_components/xiaomi_home/manifest.json index 406fe9d..5f55d15 100644 --- a/custom_components/xiaomi_home/manifest.json +++ b/custom_components/xiaomi_home/manifest.json @@ -23,7 +23,8 @@ "paho-mqtt<=2.0.0", "numpy", "cryptography", - "psutil" + "psutil", + "aiohttp[speedups]" ], "version": "v0.1.2", "zeroconf": [ diff --git a/custom_components/xiaomi_home/miot/miot_cloud.py b/custom_components/xiaomi_home/miot/miot_cloud.py index 270a66d..dd4c282 100644 --- a/custom_components/xiaomi_home/miot/miot_cloud.py +++ b/custom_components/xiaomi_home/miot/miot_cloud.py @@ -51,10 +51,9 @@ import logging import re import time -from functools import partial from typing import Optional from urllib.parse import urlencode -import requests +import aiohttp # pylint: disable=relative-beyond-top-level from .common import calc_group_id @@ -71,8 +70,9 @@ class MIoTOauthClient: """oauth agent url, default: product env.""" - _main_loop: asyncio.AbstractEventLoop = None - _oauth_host: str = None + _main_loop: asyncio.AbstractEventLoop + _session: aiohttp.ClientSession + _oauth_host: str _client_id: int _redirect_url: str @@ -94,9 +94,10 @@ def __init__( self._oauth_host = DEFAULT_OAUTH2_API_HOST else: self._oauth_host = f'{cloud_server}.{DEFAULT_OAUTH2_API_HOST}' + self._session = aiohttp.ClientSession() - async def __call_async(self, func): - return await self._main_loop.run_in_executor(executor=None, func=func) + def __del__(self): + self._session.close() def set_redirect_url(self, redirect_url: str) -> None: if not isinstance(redirect_url, str) or redirect_url.strip() == '': @@ -140,21 +141,22 @@ def gen_auth_url( return f'{OAUTH2_AUTH_URL}?{encoded_params}' - def _get_token(self, data) -> dict: - http_res = requests.get( + async def __get_token_async(self, data) -> dict: + http_res = await self._session.get( url=f'https://{self._oauth_host}/app/v2/ha/oauth/get_token', params={'data': json.dumps(data)}, headers={'content-type': 'application/x-www-form-urlencoded'}, timeout=MIHOME_HTTP_API_TIMEOUT ) - if http_res.status_code == 401: + if http_res.status == 401: raise MIoTOauthError( 'unauthorized(401)', MIoTErrorCode.CODE_OAUTH_UNAUTHORIZED) - if http_res.status_code != 200: + if http_res.status != 200: raise MIoTOauthError( - f'invalid http status code, {http_res.status_code}') + f'invalid http status code, {http_res.status}') - res_obj = http_res.json() + res_str = await http_res.text() + res_obj = json.loads(res_str) if ( not res_obj or res_obj.get('code', None) != 0 @@ -172,7 +174,7 @@ def _get_token(self, data) -> dict: (res_obj['result'].get('expires_in', 0)*TOKEN_EXPIRES_TS_RATIO)) } - def get_access_token(self, code: str) -> dict: + async def get_access_token_async(self, code: str) -> dict: """get access token by authorization code Args: @@ -184,16 +186,13 @@ def get_access_token(self, code: str) -> dict: if not isinstance(code, str): raise MIoTOauthError('invalid code') - return self._get_token(data={ + return await self.__get_token_async(data={ 'client_id': self._client_id, 'redirect_uri': self._redirect_url, 'code': code, }) - async def get_access_token_async(self, code: str) -> dict: - return await self.__call_async(partial(self.get_access_token, code)) - - def refresh_access_token(self, refresh_token: str) -> dict: + async def refresh_access_token_async(self, refresh_token: str) -> dict: """get access token by refresh token. Args: @@ -205,16 +204,12 @@ def refresh_access_token(self, refresh_token: str) -> dict: if not isinstance(refresh_token, str): raise MIoTOauthError('invalid refresh_token') - return self._get_token(data={ + return await self._get_token_async(data={ 'client_id': self._client_id, 'redirect_uri': self._redirect_url, 'refresh_token': refresh_token, }) - async def refresh_access_token_async(self, refresh_token: str) -> dict: - return await self.__call_async( - partial(self.refresh_access_token, refresh_token)) - class MIoTHttpClient: """MIoT http client.""" @@ -222,6 +217,7 @@ class MIoTHttpClient: GET_PROP_AGGREGATE_INTERVAL: float = 0.2 GET_PROP_MAX_REQ_COUNT = 150 _main_loop: asyncio.AbstractEventLoop + _session: aiohttp.ClientSession _host: str _base_url: str _client_id: str @@ -254,10 +250,10 @@ def __init__( cloud_server=cloud_server, client_id=client_id, access_token=access_token) - async def __call_async(self, func) -> any: - if self._main_loop is None: - raise MIoTHttpError('miot http, un-support async methods') - return await self._main_loop.run_in_executor(executor=None, func=func) + self._session = aiohttp.ClientSession() + + def __del__(self): + self._session.close() def update_http_header( self, cloud_server: Optional[str] = None, @@ -276,36 +272,35 @@ def update_http_header( self._access_token = access_token @property - def __api_session(self) -> requests.Session: - session = requests.Session() - session.headers.update({ + def __api_request_headers(self) -> dict: + return { 'Host': self._host, 'X-Client-BizId': 'haapi', 'Content-Type': 'application/json', 'Authorization': f'Bearer{self._access_token}', 'X-Client-AppId': self._client_id, - }) - return session + } - def mihome_api_get( + # pylint: disable=unused-private-member + async def __mihome_api_get_async( self, url_path: str, params: dict, timeout: int = MIHOME_HTTP_API_TIMEOUT ) -> dict: - http_res = None - with self.__api_session as session: - http_res = session.get( - url=f'{self._base_url}{url_path}', - params=params, - timeout=timeout) - if http_res.status_code == 401: + http_res = await self._session.get( + url=f'{self._base_url}{url_path}', + params=params, + headers=self.__api_request_headers, + timeout=timeout) + if http_res.status == 401: raise MIoTHttpError( 'mihome api get failed, unauthorized(401)', MIoTErrorCode.CODE_HTTP_INVALID_ACCESS_TOKEN) - if http_res.status_code != 200: + if http_res.status != 200: raise MIoTHttpError( - f'mihome api get failed, {http_res.status_code}, ' + f'mihome api get failed, {http_res.status}, ' f'{url_path}, {params}') - res_obj: dict = http_res.json() + res_str = await http_res.text() + res_obj: dict = json.loads(res_str) if res_obj.get('code', None) != 0: raise MIoTHttpError( f'invalid response code, {res_obj.get("code",None)}, ' @@ -315,28 +310,25 @@ def mihome_api_get( self._base_url, url_path, params, res_obj) return res_obj - def mihome_api_post( + async def __mihome_api_post_async( self, url_path: str, data: dict, timeout: int = MIHOME_HTTP_API_TIMEOUT ) -> dict: - encoded_data = None - if data: - encoded_data = json.dumps(data).encode('utf-8') - http_res = None - with self.__api_session as session: - http_res = session.post( - url=f'{self._base_url}{url_path}', - data=encoded_data, - timeout=timeout) - if http_res.status_code == 401: + http_res = await self._session.post( + url=f'{self._base_url}{url_path}', + json=data, + headers=self.__api_request_headers, + timeout=timeout) + if http_res.status == 401: raise MIoTHttpError( 'mihome api get failed, unauthorized(401)', MIoTErrorCode.CODE_HTTP_INVALID_ACCESS_TOKEN) - if http_res.status_code != 200: + if http_res.status != 200: raise MIoTHttpError( - f'mihome api post failed, {http_res.status_code}, ' + f'mihome api post failed, {http_res.status}, ' f'{url_path}, {data}') - res_obj: dict = http_res.json() + res_str = await http_res.text() + res_obj: dict = json.loads(res_str) if res_obj.get('code', None) != 0: raise MIoTHttpError( f'invalid response code, {res_obj.get("code",None)}, ' @@ -346,8 +338,8 @@ def mihome_api_post( self._base_url, url_path, data, res_obj) return res_obj - def get_user_info(self) -> dict: - http_res = requests.get( + 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}, @@ -355,7 +347,8 @@ def get_user_info(self) -> dict: timeout=MIHOME_HTTP_API_TIMEOUT ) - res_obj = http_res.json() + res_str = await http_res.text() + res_obj = json.loads(res_str) if ( not res_obj or res_obj.get('code', None) != 0 @@ -366,14 +359,11 @@ def get_user_info(self) -> dict: return res_obj['data'] - async def get_user_info_async(self) -> dict: - return await self.__call_async(partial(self.get_user_info)) - - def get_central_cert(self, csr: str) -> Optional[str]: + async def get_central_cert_async(self, csr: str) -> Optional[str]: if not isinstance(csr, str): raise MIoTHttpError('invalid params') - res_obj: dict = self.mihome_api_post( + res_obj: dict = await self.__mihome_api_post_async( url_path='/app/v2/ha/oauth/get_central_crt', data={ 'csr': str(base64.b64encode(csr.encode('utf-8')), 'utf-8') @@ -387,11 +377,8 @@ def get_central_cert(self, csr: str) -> Optional[str]: return cert - async def get_central_cert_async(self, csr: str) -> Optional[str]: - return await self.__call_async(partial(self.get_central_cert, csr)) - - def __get_dev_room_page(self, max_id: str = None) -> dict: - res_obj = self.mihome_api_post( + async def __get_dev_room_page_async(self, max_id: str = None) -> dict: + res_obj = await self.__mihome_api_post_async( url_path='/app/v2/homeroom/get_dev_room_page', data={ 'start_id': max_id, @@ -419,7 +406,7 @@ def __get_dev_room_page(self, max_id: str = None) -> dict: res_obj['result'].get('has_more', False) and isinstance(res_obj['result'].get('max_id', None), str) ): - next_list = self.__get_dev_room_page( + next_list = await self.__get_dev_room_page_async( max_id=res_obj['result']['max_id']) for home_id, info in next_list.items(): home_list.setdefault(home_id, {'dids': [], 'room_info': {}}) @@ -432,8 +419,8 @@ def __get_dev_room_page(self, max_id: str = None) -> dict: return home_list - def get_homeinfos(self) -> dict: - res_obj = self.mihome_api_post( + async def get_homeinfos_async(self) -> dict: + res_obj = await self.__mihome_api_post_async( url_path='/app/v2/homeroom/gethome', data={ 'limit': 150, @@ -485,7 +472,7 @@ def get_homeinfos(self) -> dict: res_obj['result'].get('has_more', False) and isinstance(res_obj['result'].get('max_id', None), str) ): - more_list = self.__get_dev_room_page( + more_list = await self.__get_dev_room_page_async( max_id=res_obj['result']['max_id']) for home_id, info in more_list.items(): if home_id not in home_infos['homelist']: @@ -507,16 +494,10 @@ def get_homeinfos(self) -> dict: 'share_home_list': home_infos.get('share_home_list', []) } - async def get_homeinfos_async(self) -> dict: - return await self.__call_async(self.get_homeinfos) - - def get_uid(self) -> str: - return self.get_homeinfos().get('uid', None) - async def get_uid_async(self) -> str: return (await self.get_homeinfos_async()).get('uid', None) - def __get_device_list_page( + async def __get_device_list_page_async( self, dids: list[str], start_did: str = None ) -> dict[str, dict]: req_data: dict = { @@ -527,7 +508,7 @@ def __get_device_list_page( if start_did: req_data['start_did'] = start_did device_infos: dict = {} - res_obj = self.mihome_api_post( + res_obj = await self.__mihome_api_post_async( url_path='/app/v2/home/device_list_page', data=req_data ) @@ -578,7 +559,7 @@ def __get_device_list_page( next_start_did = res_obj.get('next_start_did', None) if res_obj.get('has_more', False) and next_start_did: - device_infos.update(self.__get_device_list_page( + device_infos.update(await self.__get_device_list_page_async( dids=dids, start_did=next_start_did)) return device_infos @@ -587,8 +568,7 @@ async def get_devices_with_dids_async( self, dids: list[str] ) -> dict[str, dict]: results: list[dict[str, dict]] = await asyncio.gather( - *[self.__call_async( - partial(self.__get_device_list_page, dids[index:index+150])) + *[self.__get_device_list_page_async(dids[index:index+150]) for index in range(0, len(dids), 150)]) devices = {} for result in results: @@ -665,12 +645,12 @@ async def get_devices_async( 'devices': devices } - def get_props(self, params: list) -> list: + async def get_props_async(self, params: list) -> list: """ params = [{"did": "xxxx", "siid": 2, "piid": 1}, {"did": "xxxxxx", "siid": 2, "piid": 2}] """ - res_obj = self.mihome_api_post( + res_obj = await self.__mihome_api_post_async( url_path='/app/v2/miotspec/prop/get', data={ 'datasource': 1, @@ -681,11 +661,9 @@ def get_props(self, params: list) -> list: raise MIoTHttpError('invalid response result') return res_obj['result'] - async def get_props_async(self, params: list) -> list: - return await self.__call_async(partial(self.get_props, params)) - def get_prop(self, did: str, siid: int, piid: int) -> any: - results = self.get_props( + async def __get_prop_async(self, did: str, siid: int, piid: int) -> any: + results = await self.get_props_async( params=[{'did': did, 'siid': siid, 'piid': piid}]) if not results: return None @@ -711,7 +689,7 @@ async def __get_prop_handler(self) -> bool: if not props_buffer: _LOGGER.error('get prop error, empty request list') return False - results = await self.__call_async(partial(self.get_props, props_buffer)) + results = await self.get_props_async(props_buffer) for result in results: if not all( @@ -747,8 +725,7 @@ async def get_prop_async( self, did: str, siid: int, piid: int, immediately: bool = False ) -> any: if immediately: - return await self.__call_async( - partial(self.get_prop, did, siid, piid)) + return await self.__get_prop_async(did, siid, piid) key: str = f'{did}.{siid}.{piid}' prop_obj = self._get_prop_list.get(key, None) if prop_obj: @@ -766,11 +743,11 @@ async def get_prop_async( return await fut - def set_prop(self, params: list) -> list: + async def set_prop_async(self, params: list) -> list: """ params = [{"did": "xxxx", "siid": 2, "piid": 1, "value": False}] """ - res_obj = self.mihome_api_post( + res_obj = await self.__mihome_api_post_async( url_path='/app/v2/miotspec/prop/set', data={ 'params': params @@ -782,20 +759,14 @@ def set_prop(self, params: list) -> list: return res_obj['result'] - async def set_prop_async(self, params: list) -> list: - """ - params = [{"did": "xxxx", "siid": 2, "piid": 1, "value": False}] - """ - return await self.__call_async(partial(self.set_prop, params)) - - def action( + async def action_async( self, did: str, siid: int, aiid: int, in_list: list[dict] ) -> dict: """ params = {"did": "xxxx", "siid": 2, "aiid": 1, "in": []} """ # NOTICE: Non-standard action param - res_obj = self.mihome_api_post( + res_obj = await self.__mihome_api_post_async( url_path='/app/v2/miotspec/action', data={ 'params': { @@ -810,9 +781,3 @@ def action( raise MIoTHttpError('invalid response result') return res_obj['result'] - - async def action_async( - self, did: str, siid: int, aiid: int, in_list: list[dict] - ) -> dict: - return await self.__call_async( - partial(self.action, did, siid, aiid, in_list)) From 6ce3206b30841db7a6d551a16dedafbf58746f43 Mon Sep 17 00:00:00 2001 From: whoiam <46884440+Jimmo-o@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:35:21 +0800 Subject: [PATCH 03/17] fix: HASS core version requirements (#214) * Update README.md * Update hacs.json * Update README_zh.md --- README.md | 2 +- doc/README_zh.md | 2 +- hacs.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe3b75c..2268b9d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Xiaomi Home Integration is an integrated component of Home Assistant supported b > Home Assistant version requirement: > -> - Core $\geq$ 2024.11.0 +> - Core $\geq$ 2024.4.4 > - Operating System $\geq$ 13.0 ### Method 1: Git clone from GitHub diff --git a/doc/README_zh.md b/doc/README_zh.md index c221440..7e1a48b 100644 --- a/doc/README_zh.md +++ b/doc/README_zh.md @@ -8,7 +8,7 @@ > Home Assistant 版本要求: > -> - Core $\geq$ 2024.11.0 +> - Core $\geq$ 2024.4.4 > - Operating System $\geq$ 13.0 ### 方法 1:使用 git clone 命令从 GitHub 下载 diff --git a/hacs.json b/hacs.json index 98096d8..366fc8e 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { "name": "Xiaomi Home", - "homeassistant": "2024.11.0", + "homeassistant": "2024.4.4", "hacs": "1.34.0" } From bd3a98b976d3092c194479bf2fcfaaf3f8a0c1af Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:21:43 +0800 Subject: [PATCH 04/17] Fix local ctrl error (#271) * feat: common.py add gen_absolute_path, load_yaml_file * fix: miot_lan add profile devices filter * fix: add lan ctrl profile model list * test: improve lan test * fix: fix pylint redefined-outer-name * feat: update tools to update profile models file * fix: fix pylint waning * fix: update miot lan NETWORK_UNSTABLE_RESUME_TH value --- custom_components/xiaomi_home/miot/common.py | 21 +- .../xiaomi_home/miot/lan/profile_models.yaml | 1320 +++++++++++++++++ .../xiaomi_home/miot/miot_lan.py | 29 +- test/conftest.py | 7 + test/test_lan.py | 43 +- tools/common.py | 44 + tools/update_lan_rule.py | 80 + 7 files changed, 1534 insertions(+), 10 deletions(-) create mode 100644 custom_components/xiaomi_home/miot/lan/profile_models.yaml create mode 100644 tools/common.py create mode 100644 tools/update_lan_rule.py diff --git a/custom_components/xiaomi_home/miot/common.py b/custom_components/xiaomi_home/miot/common.py index 5a5137d..0b03f96 100644 --- a/custom_components/xiaomi_home/miot/common.py +++ b/custom_components/xiaomi_home/miot/common.py @@ -46,10 +46,19 @@ Common utilities. """ import json +from os import path import random from typing import Optional import hashlib from paho.mqtt.client import MQTTMatcher +import yaml + +MIOT_ROOT_PATH: str = path.dirname(path.abspath(__file__)) + + +def gen_absolute_path(relative_path: str) -> str: + """Generate an absolute path.""" + return path.join(MIOT_ROOT_PATH, relative_path) def calc_group_id(uid: str, home_id: str) -> str: @@ -64,6 +73,12 @@ def load_json_file(json_file: str) -> dict: return json.load(f) +def load_yaml_file(yaml_file: str) -> dict: + """Load a YAML file.""" + with open(yaml_file, 'r', encoding='utf-8') as f: + return yaml.load(f, Loader=yaml.FullLoader) + + def randomize_int(value: int, ratio: float) -> int: """Randomize an integer value.""" return int(value * (1 - ratio + random.random()*2*ratio)) @@ -74,12 +89,12 @@ class MIoTMatcher(MQTTMatcher): def iter_all_nodes(self) -> any: """Return an iterator on all nodes with their paths and contents.""" - def rec(node, path): + def rec(node, path_): # pylint: disable=protected-access if node._content: - yield ('/'.join(path), node._content) + yield ('/'.join(path_), node._content) for part, child in node._children.items(): - yield from rec(child, path + [part]) + yield from rec(child, path_ + [part]) return rec(self._root, []) def get(self, topic: str) -> Optional[any]: diff --git a/custom_components/xiaomi_home/miot/lan/profile_models.yaml b/custom_components/xiaomi_home/miot/lan/profile_models.yaml new file mode 100644 index 0000000..5e14086 --- /dev/null +++ b/custom_components/xiaomi_home/miot/lan/profile_models.yaml @@ -0,0 +1,1320 @@ +090615.curtain.mt800w: + ts: 1604589513 +090615.curtain.p01: + ts: 1603355069 +090615.curtain.sidt82: + ts: 1604589553 +090615.switch.switch01: + ts: 1605166385 +090615.switch.switch02: + ts: 1604589655 +090615.switch.switch03: + ts: 1605161171 +090615.switch.xswitch01: + ts: 1603965395 +090615.switch.xswitch02: + ts: 1603967482 +090615.switch.xswitch03: + ts: 1603967572 +1245.airpurifier.dl01: + ts: 1607502661 +17216.massage.ec1266a: + ts: 1615881124 +397.light.hallight: + ts: 1605161883 +666.curtain.em75: + ts: 1605857796 +aden.aircondition.a1: + ts: 1618982254 +aden.aircondition.a2: + ts: 1605167390 +aden.aircondition.a4: + ts: 1605167477 +aice.motor.kzmu3: + ts: 1646394583 +air.fan.ca23ad9: + ts: 1635406920 +airdog.airpurifier.x5: + ts: 1623915265 +airdog.airpurifier.x7: + ts: 1614656371 +airdog.airpurifier.x7sm: + ts: 1614656402 +asp.treadmill.pbj: + ts: 1611217046 +aux.aircondition.v1: + ts: 1626427548 +bdx.i_stove.a1xs: + ts: 1610614201 +bdx.i_stove.a1z: + ts: 1614667065 +bdx.i_stove.c1x: + ts: 1610614303 +bdx.i_stove.c2x: + ts: 1611823486 +bj352.airmonitor.m30: + ts: 1686644541 +bj352.waterpuri.s100cm: + ts: 1615795630 +cgllc.airmonitor.b1: + ts: 1676339912 +cgllc.airmonitor.s1: + ts: 1608189468 +cgllc.clock.cgc1: + ts: 1686644422 +cgllc.clock.dove: + ts: 1619607474 +cgllc.magnet.hodor: + ts: 1724329476 +cgllc.motion.cgpr1: + ts: 1639479505 +cgllc.sensor_ht.cgm1: + ts: 1602667557 +cgllc.sensor_ht.dk2: + ts: 1620724988 +cgllc.sensor_ht.g1: + ts: 1607505446 +cgllc.sensor_ht.qpg1: + ts: 1602667461 +chuangmi.camera.ipc004b: + ts: 1531108800 +chuangmi.camera.ipc007b: + ts: 1531108800 +chuangmi.camera.ipc009: + ts: 1531108800 +chuangmi.camera.ipc010: + ts: 1531108800 +chuangmi.camera.ipc013: + ts: 1531108800 +chuangmi.camera.ipc013d: + ts: 1531108800 +chuangmi.camera.ipc016: + ts: 1531108800 +chuangmi.camera.ipc017: + ts: 1531108800 +chuangmi.camera.ipc019: + ts: 1531108800 +chuangmi.camera.ipc019b: + ts: 1531108800 +chuangmi.camera.ipc019e: + ts: 1531108800 +chuangmi.camera.ipc020: + ts: 1531108800 +chuangmi.camera.ipc021: + ts: 1531108800 +chuangmi.camera.v2: + ts: 1531108800 +chuangmi.camera.v3: + ts: 1531108800 +chuangmi.camera.v4: + ts: 1531108800 +chuangmi.camera.v5: + ts: 1531108800 +chuangmi.camera.v6: + ts: 1531108800 +chuangmi.camera.xiaobai: + ts: 1531108800 +chuangmi.cateye.i023a01: + ts: 1624535092 +chuangmi.cateye.ipc018: + ts: 1632735241 +chuangmi.cateye.ipc508: + ts: 1633677521 +chuangmi.door.hmi515: + ts: 1640334316 +chuangmi.lock.hmi501: + ts: 1614742147 +chuangmi.lock.hmi501b01: + ts: 1614742108 +chuangmi.lock.hmi503a01: + ts: 1614742180 +chuangmi.lock.hmi505a01: + ts: 1614742900 +chuangmi.plug.hmi206: + ts: 1645409193 +chuangmi.plug.hmi208: + ts: 1677652804 +chuangmi.plug.m1: + ts: 1620814339 +chuangmi.plug.m3: + ts: 1637228027 +chuangmi.plug.v1: + ts: 1621925183 +chuangmi.plug.v3: + ts: 1644480255 +chuangmi.radio.v1: + ts: 1531108800 +chuangmi.radio.v2: + ts: 1531108800 +chunmi.cooker.eh1: + ts: 1607339278 +chunmi.cooker.eh402: + ts: 1638506382 +chunmi.cooker.k1pro1: + ts: 1607393855 +chunmi.cooker.normal2: + ts: 1607393898 +chunmi.cooker.normal3: + ts: 1620369915 +chunmi.cooker.normal4: + ts: 1607394370 +chunmi.cooker.normal5: + ts: 1607394381 +chunmi.cooker.normalcd1: + ts: 1607395198 +chunmi.cooker.normalcd2: + ts: 1607395220 +chunmi.cooker.press1: + ts: 1607395268 +chunmi.cooker.press2: + ts: 1607395280 +chunmi.ihcooker.chefnic: + ts: 1604591216 +chunmi.ihcooker.double: + ts: 1609924806 +chunmi.ihcooker.hk1: + ts: 1605167058 +chunmi.ihcooker.tkpro1: + ts: 1614756253 +chunmi.ihcooker.tkv1: + ts: 1615801005 +chunmi.ihcooker.v1: + ts: 1605167116 +chunmi.juicer.a1: + ts: 1624530635 +chunmi.microwave.n20l01: + ts: 1638505282 +chunmi.microwave.n23l01: + ts: 1638505524 +chunmi.oven.steam30lv1: + ts: 1605855556 +chunmi.pre_cooker.eh1: + ts: 1614570773 +cleargrass.sensor_ht.dk1: + ts: 1619344277 +deerma.humidifier.jsq1: + ts: 1683863719 +deerma.humidifier.mjjsq: + ts: 1684727440 +dicook.cooker.wfz4003: + ts: 1614678405 +dmaker.airfresh.a1: + ts: 1715677691 +dmaker.airfresh.t2017: + ts: 1686731233 +dmaker.fan.p5: + ts: 1655793784 +dsm.lock.h3: + ts: 1615283790 +dsm.lock.q3: + ts: 1614741870 +dsm.lock.r5: + ts: 1614741913 +dun.cateye.nknk500: + ts: 1615358692 +duoqin.safe.pbfv01: + ts: 1614740167 +fawad.airrtc.fwd20011: + ts: 1610607149 +fbs.airmonitor.pth02: + ts: 1686644918 +hannto.printer.anise: + ts: 1618989537 +hannto.printer.honey: + ts: 1607504864 +hannto.printer.honey1s: + ts: 1614332725 +hfjh.fishbowl.v1: + ts: 1615278556 +hhcc.plantmonitor.v1: + ts: 1664163526 +hith.foot_bath.q2: + ts: 1531108800 +huohe.lock.m1: + ts: 1635410938 +hutlon.lock.v0001: + ts: 1634799698 +idelan.aircondition.v1: + ts: 1614666973 +idelan.aircondition.v2: + ts: 1626427579 +ihealth.bp.bpm1: + ts: 1608189506 +ihealth.bpm.kd5907: + ts: 1614673307 +ikea.light.led1536g5: + ts: 1605162819 +ikea.light.led1537r6: + ts: 1605162872 +ikea.light.led1545g12: + ts: 1605162937 +ikea.light.led1623g12: + ts: 1605163009 +ikea.light.led1649c5: + ts: 1605163064 +imibar.cooker.mbihr3: + ts: 1624620659 +imou99.camera.tp2: + ts: 1531108800 +isa.camera.df3: + ts: 1531108800 +isa.camera.hl5: + ts: 1531108800 +isa.camera.hlc6: + ts: 1531108800 +isa.camera.isc5: + ts: 1531108800 +isa.camera.isc5c1: + ts: 1621238175 +isa.magnet.dw2hl: + ts: 1638274655 +jiqid.mistory.pro: + ts: 1531108800 +jiqid.mistory.v1: + ts: 1531108800 +jiqid.mistudy.v2: + ts: 1610612349 +jiwu.lock.jwp01: + ts: 1614752632 +jyaiot.cm.ccj01: + ts: 1611824545 +ksmb.treadmill.v1: + ts: 1611211447 +ksmb.treadmill.v2: + ts: 1610606684 +ksmb.walkingpad.v1: + ts: 1621238199 +ksmb.walkingpad.v3: + ts: 1621238214 +kxf321.mop.mo001: + ts: 1638343629 +lcrmcr.safe.20mini: + ts: 1617765416 +lcrmcr.safe.30mk: + ts: 1628215806 +lcrmcr.safe.an35sidz: + ts: 1614741042 +lcrmcr.safe.an35sizw: + ts: 1614741076 +lcrmcr.safe.d60ht: + ts: 1624351108 +lcrmcr.safe.ms30b: + ts: 1606986586 +lcrmcr.safe.ms30mp: + ts: 1604587256 +lcrmcr.safe.ms55kn: + ts: 1606980410 +lcrmcr.safe.ms80b: + ts: 1614740261 +lcrmcr.safe.sd003: + ts: 1637033698 +lcrmcr.safe.x142: + ts: 1634813125 +leshow.fan.ss310: + ts: 1686896880 +leshow.fan.ss320: + ts: 1686896668 +leshow.fan.ss4: + ts: 1606376586 +leshow.heater.bs1: + ts: 1608187309 +leshow.humidifier.is2: + ts: 1604589602 +linp.doorbell.g03: + ts: 1609311251 +linp.remote.k9b: + ts: 1621825941 +linp.remote.k9b1: + ts: 1621825926 +linp.remote.k9b11: + ts: 1621825903 +loock.cateye.v01: + ts: 1627291525 +loock.cateye.v02: + ts: 1661739898 +loock.lock.cc2s: + ts: 1614752275 +loock.lock.cc2xpro: + ts: 1614752365 +loock.lock.fcl112: + ts: 1658997537 +loock.lock.fcp50m: + ts: 1647246522 +loock.lock.fvl109: + ts: 1640252939 +loock.lock.fvl111: + ts: 1646134370 +loock.lock.ojjz1: + ts: 1614741370 +loock.lock.p50: + ts: 1644572168 +loock.lock.pfvl10: + ts: 1630040903 +loock.lock.s30: + ts: 1614740862 +loock.lock.s30v2: + ts: 1614829588 +loock.lock.s50c: + ts: 1649309703 +loock.lock.s50f: + ts: 1639967451 +loock.lock.t1: + ts: 1634542543 +loock.lock.t1pro: + ts: 1634543113 +loock.lock.t2v1: + ts: 1655983879 +loock.lock.v1: + ts: 1618889646 +loock.lock.v14: + ts: 1614741632 +loock.lock.v15: + ts: 1619508939 +loock.lock.v16: + ts: 1621235279 +loock.lock.v3: + ts: 1619341106 +loock.lock.v4: + ts: 1619340970 +loock.lock.v5: + ts: 1614752242 +loock.lock.v6: + ts: 1634796911 +loock.lock.v7: + ts: 1614741195 +loock.lock.v8: + ts: 1619413983 +loock.lock.v9: + ts: 1614741328 +loock.lock.xfvl10: + ts: 1632814256 +loock.safe.v1: + ts: 1619607755 +lumi.acpartner.v1: + ts: 1531108800 +lumi.acpartner.v2: + ts: 1531108800 +lumi.acpartner.v3: + ts: 1531108800 +lumi.airer.acn01: + ts: 1611818317 +lumi.airrtc.tcpco2ecn01: + ts: 1531108800 +lumi.airrtc.tcpecn01: + ts: 1531108800 +lumi.airrtc.tcpecn02: + ts: 1531108800 +lumi.camera.aq1: + ts: 1531108800 +lumi.camera.gwagl01: + ts: 1531108800 +lumi.ctrl_86plug.aq1: + ts: 1627292331 +lumi.ctrl_86plug.v1: + ts: 1627291597 +lumi.ctrl_ln1.aq1: + ts: 1648791623 +lumi.ctrl_ln1.v1: + ts: 1653297884 +lumi.ctrl_ln2.aq1: + ts: 1653292857 +lumi.ctrl_ln2.v1: + ts: 1653299006 +lumi.ctrl_neutral1.v1: + ts: 1653294867 +lumi.ctrl_neutral2.v1: + ts: 1653294492 +lumi.curtain.aq2: + ts: 1605857829 +lumi.curtain.hagl04: + ts: 1615351634 +lumi.curtain.v1: + ts: 1608188918 +lumi.flood.bmcn01: + ts: 1614666824 +lumi.gateway.aqhm01: + ts: 1687160720 +lumi.gateway.aqhm02: + ts: 1687162682 +lumi.gateway.lmuk01: + ts: 1687164111 +lumi.gateway.mieu01: + ts: 1687163976 +lumi.gateway.mihk01: + ts: 1687163477 +lumi.gateway.mitw01: + ts: 1687160954 +lumi.gateway.v1: + ts: 1687162995 +lumi.gateway.v2: + ts: 1687163777 +lumi.gateway.v3: + ts: 1686895771 +lumi.light.aqcn02: + ts: 1620727535 +lumi.light.cwopcn01: + ts: 1605855768 +lumi.light.cwopcn02: + ts: 1605855809 +lumi.light.cwopcn03: + ts: 1605855836 +lumi.lock.acn02: + ts: 1623928631 +lumi.lock.acn03: + ts: 1614752574 +lumi.lock.bacn01: + ts: 1614741699 +lumi.lock.bmcn02: + ts: 1637292825 +lumi.lock.bmcn03: + ts: 1634546176 +lumi.lock.bmcn04: + ts: 1636451160 +lumi.lock.bmcn05: + ts: 1636454200 +lumi.lock.bzacn1: + ts: 1614741727 +lumi.lock.bzacn2: + ts: 1614741815 +lumi.lock.eicn02: + ts: 1639976382 +lumi.lock.mcn007: + ts: 1650446757 +lumi.lock.mcn01: + ts: 1679881881 +lumi.lock.wbmcn1: + ts: 1619422072 +lumi.motion.bmgl01: + ts: 1639983139 +lumi.plug.v1: + ts: 1653299040 +lumi.relay.c2acn01: + ts: 1609310261 +lumi.remote.b186acn01: + ts: 1614154507 +lumi.remote.b186acn02: + ts: 1607395626 +lumi.remote.b1acn01: + ts: 1608794798 +lumi.remote.b286acn01: + ts: 1607397501 +lumi.remote.b286acn02: + ts: 1608794723 +lumi.remote.b286opcn01: + ts: 1614154602 +lumi.remote.b486opcn01: + ts: 1614154660 +lumi.remote.b686opcn01: + ts: 1614154713 +lumi.sensor_86sw1.v1: + ts: 1609311038 +lumi.sensor_86sw2.v1: + ts: 1608795035 +lumi.sensor_ht.v1: + ts: 1621239877 +lumi.sensor_magnet.aq2: + ts: 1641112867 +lumi.sensor_magnet.v2: + ts: 1641113779 +lumi.sensor_motion.aq2: + ts: 1676433994 +lumi.sensor_motion.v2: + ts: 1672818550 +lumi.sensor_natgas.v1: + ts: 1635762582 +lumi.sensor_smoke.mcn02: + ts: 1634635296 +lumi.sensor_smoke.v1: + ts: 1634809938 +lumi.sensor_switch.aq2: + ts: 1615256430 +lumi.sensor_switch.aq3: + ts: 1607399487 +lumi.sensor_switch.v2: + ts: 1609310683 +lumi.sensor_wleak.aq1: + ts: 1614669352 +lumi.switch.b1lacn02: + ts: 1653297814 +lumi.switch.b1nacn02: + ts: 1653297756 +lumi.switch.b2lacn02: + ts: 1653296711 +lumi.switch.b2nacn02: + ts: 1655201416 +lumi.switch.l3acn3: + ts: 1653296585 +lumi.switch.n3acn3: + ts: 1653294817 +lumi.vibration.aq1: + ts: 1614156721 +lumi.weather.v1: + ts: 1621239934 +madv.alarm.winlock1: + ts: 1611215780 +madv.cateye.dlowl: + ts: 1632714747 +madv.cateye.dlowlplus: + ts: 1615876742 +madv.cateye.dlowlse: + ts: 1607409634 +madv.cateye.dlowlse2: + ts: 1615876830 +madv.cateye.miowl: + ts: 1632714912 +madv.cateye.miowlv2: + ts: 1614152626 +madv.cateye.miowlv2l: + ts: 1615450328 +miaomiaoce.clock.ht02: + ts: 1620728504 +miaomiaoce.sensor_ht.h1: + ts: 1623929417 +miaomiaoce.sensor_ht.t1: + ts: 1616057242 +miaomiaoce.sensor_ht.t2: + ts: 1636603553 +miir.aircondition.ir01: + ts: 1531108800 +miir.aircondition.ir02: + ts: 1531108800 +miir.fan.ir01: + ts: 1531108800 +miir.light.ir01: + ts: 1531108800 +miir.projector.ir01: + ts: 1531108800 +miir.stb.ir01: + ts: 1531108800 +miir.tv.hir01: + ts: 1531108800 +miir.tv.ir01: + ts: 1531108800 +miir.tvbox.ir01: + ts: 1531108800 +mijia.camera.v1: + ts: 1531108800 +mijia.camera.v3: + ts: 1531108800 +minij.washer.v1: + ts: 1611818798 +minij.washer.v10: + ts: 1608792100 +minij.washer.v11: + ts: 1608792159 +minij.washer.v12: + ts: 1614656828 +minij.washer.v14: + ts: 1608792176 +minij.washer.v15: + ts: 1607410326 +minij.washer.v5: + ts: 1622792196 +minij.washer.v8: + ts: 1615777868 +miot.light.plato2: + ts: 1685518142 +miot.light.plato3: + ts: 1675941846 +miot.light.plato4: + ts: 1675941712 +mmgg.feeder.petfeeder: + ts: 1646394400 +mmgg.feeder.snack: + ts: 1607503182 +moyu.washer.s1hm: + ts: 1624620888 +mrbond.airer.m1pro: + ts: 1646393746 +mrbond.airer.m1s: + ts: 1646393874 +msj.f_washer.m1: + ts: 1614914340 +mxiang.cateye.mdb10: + ts: 1616140362 +mxiang.cateye.xmcatt1: + ts: 1616140207 +nwt.derh.wdh318efw1: + ts: 1611822375 +opple.light.bydceiling: + ts: 1608187619 +opple.light.fanlight: + ts: 1608793315 +opple.remote.5pb111: + ts: 1627453753 +opple.remote.5pb112: + ts: 1627453840 +opple.remote.5pb113: + ts: 1636599905 +ows.towel_w.mj1x0: + ts: 1610604939 +philips.light.bceiling1: + ts: 1642048160 +philips.light.bceiling2: + ts: 1642048073 +philips.light.bulb: + ts: 1620814142 +philips.light.candle: + ts: 1620814078 +philips.light.candle2: + ts: 1620814121 +philips.light.cbulb: + ts: 1639039185 +philips.light.ceiling: + ts: 1646394746 +philips.light.downlight: + ts: 1620814088 +philips.light.lnblight1: + ts: 1605857128 +philips.light.lnblight2: + ts: 1623902902 +philips.light.lnlrlight: + ts: 1621845517 +philips.light.lrceiling: + ts: 1642047751 +philips.light.mono1: + ts: 1608790977 +philips.light.moonlight: + ts: 1642582053 +philips.light.nlight: + ts: 1614147878 +philips.light.rwread: + ts: 1603352187 +philips.light.sread1: + ts: 1620814093 +philips.light.sread2: + ts: 1615259714 +philips.light.zyceiling: + ts: 1637228869 +philips.light.zysread: + ts: 1615970237 +philips.light.zystrip: + ts: 1605166337 +phnix.waterheater.sf: + ts: 1616050758 +pwzn.relay.apple: + ts: 1611217196 +pwzn.relay.banana: + ts: 1646647255 +qike.bhf_light.qk201801: + ts: 1608174909 +qmi.powerstrip.v1: + ts: 1621240280 +roborock.sweeper.s5v2: + ts: 1531108800 +roborock.vacuum.a01: + ts: 1685425888 +roborock.vacuum.a08: + ts: 1626231977 +roborock.vacuum.a09: + ts: 1611048152 +roborock.vacuum.a11: + ts: 1615360022 +roborock.vacuum.c1: + ts: 1531108800 +roborock.vacuum.e2: + ts: 1531108800 +roborock.vacuum.m1s: + ts: 1531108800 +roborock.vacuum.p5: + ts: 1611048094 +roborock.vacuum.s5: + ts: 1531108800 +roborock.vacuum.t4: + ts: 1615449261 +roborock.vacuum.t6: + ts: 1619423841 +rockrobo.vacuum.v1: + ts: 1531108800 +roome.bhf_light.yf6002: + ts: 1531108800 +rotai.massage.rt5728: + ts: 1610607000 +rotai.massage.rt5850: + ts: 1611816888 +rotai.massage.rt5850s: + ts: 1616727205 +rotai.massage.rt5863: + ts: 1611827937 +rotai.massage.rt5870: + ts: 1632376570 +scishare.coffee.s1102: + ts: 1611824402 +shuii.humidifier.jsq002: + ts: 1606376290 +skyrc.pet_waterer.fre1: + ts: 1608186812 +smith.waterheater.cxea1: + ts: 1611826349 +smith.waterheater.cxeb1: + ts: 1611826388 +smith.waterpuri.jnt600: + ts: 1531108800 +soocare.toothbrush.m1s: + ts: 1610611310 +soocare.toothbrush.t501: + ts: 1672192586 +sxds.pillow.pillow02: + ts: 1611222235 +syniot.curtain.syc1: + ts: 1608794071 +tinymu.toilet.ailid: + ts: 1615292165 +tinymu.toiletlid.v1: + ts: 1608791137 +tokit.cooker.press1: + ts: 1614842943 +tokit.cooker.tk20l01: + ts: 1646646903 +tokit.cooker.tk4001: + ts: 1639651673 +tokit.ihcooker.tkpro1: + ts: 1624533824 +tokit.ihcooker.tkv1: + ts: 1614667669 +tokit.oven.tk12l01: + ts: 1616729526 +tokit.oven.tk32pro1: + ts: 1617002408 +tokit.pre_cooker.tkih1: + ts: 1607410832 +viomi.aircondition.v10: + ts: 1606375041 +viomi.aircondition.v21: + ts: 1610608182 +viomi.aircondition.v22: + ts: 1610608198 +viomi.aircondition.v23: + ts: 1610608214 +viomi.aircondition.v24: + ts: 1610608242 +viomi.aircondition.v25: + ts: 1610608276 +viomi.aircondition.v26: + ts: 1609924120 +viomi.aircondition.v6: + ts: 1608188576 +viomi.aircondition.v7: + ts: 1608794437 +viomi.aircondition.v8: + ts: 1606376969 +viomi.aircondition.v9: + ts: 1608794507 +viomi.bhf_light.v1: + ts: 1614308454 +viomi.cooker.v1: + ts: 1607410870 +viomi.cooker.v2: + ts: 1607410911 +viomi.curtain.v1: + ts: 1658732587 +viomi.dishwasher.m01: + ts: 1610605899 +viomi.dishwasher.m02: + ts: 1610605964 +viomi.dishwasher.v01: + ts: 1614307805 +viomi.dishwasher.v03: + ts: 1614308206 +viomi.dishwasher.v05: + ts: 1610606831 +viomi.fridge.m1: + ts: 1614667789 +viomi.fridge.p1: + ts: 1614655704 +viomi.fridge.u1: + ts: 1614667927 +viomi.fridge.u12: + ts: 1614666058 +viomi.fridge.u13: + ts: 1614667152 +viomi.fridge.u15: + ts: 1607505693 +viomi.fridge.u18: + ts: 1614655755 +viomi.fridge.u2: + ts: 1531108800 +viomi.fridge.u24: + ts: 1614667214 +viomi.fridge.u4: + ts: 1614667295 +viomi.fridge.u6: + ts: 1614667319 +viomi.fridge.u7: + ts: 1614667341 +viomi.fridge.u8: + ts: 1624589504 +viomi.fridge.v3: + ts: 1614667699 +viomi.fridge.w1: + ts: 1607505903 +viomi.fridge.w2: + ts: 1614655838 +viomi.fridge.x11: + ts: 1614655870 +viomi.fridge.x12: + ts: 1614656914 +viomi.fridge.x4: + ts: 1614223422 +viomi.fridge.x7: + ts: 1637305932 +viomi.health_pot.v1: + ts: 1607502489 +viomi.hood.a10: + ts: 1611823231 +viomi.hood.a9: + ts: 1611823172 +viomi.hood.c1: + ts: 1611220032 +viomi.hood.c2: + ts: 1611823145 +viomi.hood.h1: + ts: 1610612690 +viomi.hood.h3: + ts: 1611823101 +viomi.hood.h4: + ts: 1611823078 +viomi.hood.v1: + ts: 1684390912 +viomi.i_stove.v1: + ts: 1611825720 +viomi.i_stove.v3: + ts: 1606372593 +viomi.juicer.v1: + ts: 1619688553 +viomi.juicer.v2: + ts: 1607504511 +viomi.lock.lbt14a: + ts: 1617940506 +viomi.lock.lbt41e: + ts: 1633750723 +viomi.lock.lbt48a: + ts: 1648788518 +viomi.lock.lbt51a: + ts: 1648536993 +viomi.lock.link1: + ts: 1634546007 +viomi.lock.link2: + ts: 1614740735 +viomi.lock.link2p: + ts: 1626166712 +viomi.lock.link2v: + ts: 1626166615 +viomi.lock.link3: + ts: 1639967286 +viomi.lock.link4s: + ts: 1681980137 +viomi.oven.so1: + ts: 1621243328 +viomi.oven.so2: + ts: 1611816582 +viomi.steriliser.v1: + ts: 1607503139 +viomi.vacuum.v10: + ts: 1619171223 +viomi.vacuum.v3: + ts: 1619171356 +viomi.vacuum.v31: + ts: 1624348346 +viomi.vacuum.v6: + ts: 1620720046 +viomi.vacuum.v7: + ts: 1618969602 +viomi.vacuum.v9: + ts: 1626232015 +viomi.washer.s1: + ts: 1616644328 +viomi.washer.u1: + ts: 1648122511 +viomi.washer.u2: + ts: 1632886871 +viomi.washer.u3: + ts: 1616644429 +viomi.washer.u4: + ts: 1616644467 +viomi.washer.u5: + ts: 1616647921 +viomi.washer.v10: + ts: 1616647971 +viomi.washer.v11: + ts: 1616644507 +viomi.washer.v4: + ts: 1616644547 +viomi.washer.v5: + ts: 1616407442 +viomi.washer.v6: + ts: 1616644619 +viomi.washer.v7: + ts: 1616644253 +viomi.washer.v8: + ts: 1616644309 +viomi.waterheater.e1: + ts: 1615343927 +viomi.waterheater.e3: + ts: 1615344258 +viomi.waterheater.e4: + ts: 1615345547 +viomi.waterheater.e7: + ts: 1604589755 +viomi.waterheater.e8: + ts: 1606982993 +viomi.waterheater.u1: + ts: 1616648390 +viomi.waterheater.u10: + ts: 1606984271 +viomi.waterheater.u11: + ts: 1606984299 +viomi.waterheater.u12: + ts: 1606376892 +viomi.waterheater.u15: + ts: 1646394494 +viomi.waterheater.u16: + ts: 1637304955 +viomi.waterheater.u3: + ts: 1620811497 +viomi.waterheater.u4: + ts: 1615864332 +viomi.waterheater.u6: + ts: 1615442495 +viomi.waterheater.u7: + ts: 1605857988 +viomi.waterheater.u8: + ts: 1615864849 +xiaomi.aircondition.ma1: + ts: 1721628903 +xiaomi.aircondition.ma2: + ts: 1721181139 +xiaomi.aircondition.ma4: + ts: 1726230966 +xiaomi.aircondition.ma5: + ts: 1721629118 +xiaomi.aircondition.ma6: + ts: 1721629272 +xiaomi.aircondition.ma9: + ts: 1721629362 +xiaomi.wifispeaker.l04m: + ts: 1658817956 +xiaomi.wifispeaker.l06a: + ts: 1672731009 +xiaomi.wifispeaker.l09a: + ts: 1626336048 +xiaomi.wifispeaker.l7a: + ts: 1637306490 +xiaomi.wifispeaker.lx01: + ts: 1637306716 +xiaomi.wifispeaker.lx04: + ts: 1669695484 +xiaomi.wifispeaker.lx05: + ts: 1672299502 +xiaomi.wifispeaker.lx06: + ts: 1672299546 +xiaomi.wifispeaker.lx5a: + ts: 1672299577 +xiaomi.wifispeaker.s12: + ts: 1672299594 +xiaomi.wifispeaker.x08a: + ts: 1672818945 +xiaomi.wifispeaker.x08c: + ts: 1658819390 +xiaovv.camera.lamp: + ts: 1531108800 +xiaovv.camera.ptz: + ts: 1531108800 +xiaovv.camera.xva3: + ts: 1531108800 +xiaovv.camera.xvb4: + ts: 1531108800 +xiaovv.camera.xvd5: + ts: 1531108800 +xiaovv.camera.xvsnowman: + ts: 1531108800 +xjx.toilet.pro: + ts: 1615965466 +xjx.toilet.pure: + ts: 1615969114 +xjx.toilet.relax: + ts: 1615968257 +xjx.toilet.zero: + ts: 1618302169 +ydhome.cateye.pr1: + ts: 1615360329 +ydhome.lock.c1p: + ts: 1614740027 +ydhome.lock.m2p: + ts: 1620728837 +ydhome.lock.m2silver: + ts: 1614743037 +ydzl.waterpuri.t1: + ts: 1609926143 +yeelink.bhf_light.v1: + ts: 1608790026 +yeelink.bhf_light.v2: + ts: 1608790085 +yeelink.bhf_light.v3: + ts: 1608790102 +yeelink.bhf_light.v5: + ts: 1601292562 +yeelink.light.bslamp1: + ts: 1703120679 +yeelink.light.bslamp2: + ts: 1703120782 +yeelink.light.bslamp3: + ts: 1646394180 +yeelink.light.ceil27: + ts: 1646394323 +yeelink.light.ceiling1: + ts: 1626343431 +yeelink.light.ceiling10: + ts: 1646393927 +yeelink.light.ceiling11: + ts: 1626341443 +yeelink.light.ceiling12: + ts: 1646122053 +yeelink.light.ceiling13: + ts: 1626341533 +yeelink.light.ceiling14: + ts: 1626341941 +yeelink.light.ceiling15: + ts: 1626342080 +yeelink.light.ceiling16: + ts: 1626342123 +yeelink.light.ceiling17: + ts: 1626342230 +yeelink.light.ceiling18: + ts: 1626342296 +yeelink.light.ceiling19: + ts: 1626342431 +yeelink.light.ceiling2: + ts: 1637308139 +yeelink.light.ceiling20: + ts: 1626339402 +yeelink.light.ceiling21: + ts: 1703121290 +yeelink.light.ceiling22: + ts: 1703121209 +yeelink.light.ceiling23: + ts: 1703120912 +yeelink.light.ceiling24: + ts: 1626343504 +yeelink.light.ceiling3: + ts: 1626343459 +yeelink.light.ceiling4: + ts: 1646393658 +yeelink.light.ceiling5: + ts: 1626342461 +yeelink.light.ceiling6: + ts: 1626343314 +yeelink.light.ceiling7: + ts: 1626343090 +yeelink.light.ceiling8: + ts: 1626343130 +yeelink.light.ceiling9: + ts: 1626343286 +yeelink.light.color1: + ts: 1626343469 +yeelink.light.color2: + ts: 1626343174 +yeelink.light.color3: + ts: 1626166445 +yeelink.light.color4: + ts: 1626343343 +yeelink.light.color5: + ts: 1626227410 +yeelink.light.color6: + ts: 1637309445 +yeelink.light.color7: + ts: 1626227373 +yeelink.light.color8: + ts: 1626343365 +yeelink.light.ct2: + ts: 1626343390 +yeelink.light.lamp1: + ts: 1626342474 +yeelink.light.lamp10: + ts: 1646122763 +yeelink.light.lamp2: + ts: 1638589912 +yeelink.light.lamp3: + ts: 1626343245 +yeelink.light.lamp4: + ts: 1629022996 +yeelink.light.lamp5: + ts: 1626343399 +yeelink.light.lamp7: + ts: 1626418317 +yeelink.light.lamp9: + ts: 1626343409 +yeelink.light.mono1: + ts: 1626343418 +yeelink.light.mono4: + ts: 1626343373 +yeelink.light.mono5: + ts: 1626343352 +yeelink.light.nl1: + ts: 1639479487 +yeelink.light.panel1: + ts: 1626343379 +yeelink.light.panel3: + ts: 1626343334 +yeelink.light.strip1: + ts: 1626343486 +yeelink.light.strip2: + ts: 1732605062 +yeelink.light.strip4: + ts: 1626342354 +yeelink.light.strip6: + ts: 1726730787 +yeelink.mirror.bm1: + ts: 1607504924 +yeelink.remote.remote: + ts: 1608186464 +yeelink.ven_fan.vf1: + ts: 1621237766 +yeelink.wifispeaker.v1: + ts: 1611818727 +yilai.light.ceiling1: + ts: 1603354689 +yilai.light.ceiling2: + ts: 1623911828 +yilai.light.ceiling3: + ts: 1606986226 +yuemee.airmonitor.mhfd1: + ts: 1619608729 +yunlu.door.sd2101: + ts: 1656661489 +yunlu.door.sd2103: + ts: 1657762233 +yunlu.door.sd2104: + ts: 1658317727 +yunmi.kettle.r1: + ts: 1614648943 +yunmi.kettle.r2: + ts: 1606372087 +yunmi.kettle.r3: + ts: 1637309534 +yunmi.plmachine.mg2: + ts: 1611833658 +yunmi.waterpuri.c5: + ts: 1604588322 +yunmi.waterpuri.lx11: + ts: 1604588450 +yunmi.waterpuri.lx12: + ts: 1722239438 +yunmi.waterpuri.lx2: + ts: 1614155130 +yunmi.waterpuri.lx3: + ts: 1606371237 +yunmi.waterpuri.lx4: + ts: 1606978913 +yunmi.waterpuri.lx5: + ts: 1603963118 +yunmi.waterpuri.lx6: + ts: 1606371311 +yunmi.waterpuri.lx7: + ts: 1603963241 +yunmi.waterpuri.lx8: + ts: 1603963353 +yunmi.waterpuri.lx9: + ts: 1607505784 +yunmi.waterpuri.s3: + ts: 1614303228 +yunmi.waterpuri.s4: + ts: 1611221280 +yunmi.waterpuri.s5: + ts: 1607501816 +yunmi.waterpuri.x2: + ts: 1603963460 +yunmi.waterpuri.x7: + ts: 1603963565 +yunmi.waterpurifier.v2: + ts: 1632377061 +yunmi.waterpurifier.v3: + ts: 1611221428 +yyunyi.wopener.yypy24: + ts: 1616741966 +zdeer.ajh.a8: + ts: 1531108800 +zdeer.ajh.a9: + ts: 1531108800 +zdeer.ajh.zda10: + ts: 1531108800 +zdeer.ajh.zda9: + ts: 1531108800 +zdeer.ajh.zjy: + ts: 1531108800 +zhimi.aircondition.ma1: + ts: 1615185265 +zhimi.aircondition.ma3: + ts: 1615185279 +zhimi.aircondition.ma4: + ts: 1626334057 +zhimi.aircondition.v1: + ts: 1610610931 +zhimi.aircondition.va1: + ts: 1609924720 +zhimi.aircondition.za1: + ts: 1626334343 +zhimi.aircondition.za2: + ts: 1626334315 +zhimi.airfresh.va2: + ts: 1690860366 +zhimi.airfresh.va4: + ts: 1690860327 +zhimi.airmonitor.v1: + ts: 1621243516 +zhimi.airpurifier.m1: + ts: 1636711231 +zhimi.airpurifier.m2: + ts: 1636711316 +zhimi.airpurifier.ma2: + ts: 1636962519 +zhimi.airpurifier.mb1: + ts: 1604588729 +zhimi.airpurifier.mc1: + ts: 1644487928 +zhimi.airpurifier.sa2: + ts: 1635820002 +zhimi.airpurifier.v1: + ts: 1635855633 +zhimi.airpurifier.v3: + ts: 1676339933 +zhimi.airpurifier.v6: + ts: 1636978652 +zhimi.airpurifier.v7: + ts: 1605856617 +zhimi.airpurifier.v8: + ts: 1626168185 +zhimi.fan.sa1: + ts: 1700644763 +zhimi.fan.v2: + ts: 1615975066 +zhimi.fan.v3: + ts: 1689041686 +zhimi.fan.za1: + ts: 1604590500 +zhimi.fan.za3: + ts: 1689040855 +zhimi.fan.za4: + ts: 1689040869 +zhimi.heater.ma1: + ts: 1645683725 +zhimi.heater.za1: + ts: 1645683820 +zhimi.humidifier.ca1: + ts: 1636099783 +zhimi.humidifier.v1: + ts: 1639639102 +zhimi.lock.da1: + ts: 1614741765 +zhimi.lock.da2: + ts: 1614741779 +zhimi.toilet.sa1: + ts: 1608186583 +zhimi.toilet.va1: + ts: 1607503081 +zimi.clock.myk01: + ts: 1531108800 +zimi.mosq.v1: + ts: 1620728957 +zimi.powerstrip.v2: + ts: 1620812714 diff --git a/custom_components/xiaomi_home/miot/miot_lan.py b/custom_components/xiaomi_home/miot/miot_lan.py index 525be2f..07aadbc 100644 --- a/custom_components/xiaomi_home/miot/miot_lan.py +++ b/custom_components/xiaomi_home/miot/miot_lan.py @@ -71,7 +71,8 @@ from .miot_ev import MIoTEventLoop, TimeoutHandle from .miot_network import InterfaceStatus, MIoTNetwork, NetworkInfo from .miot_mdns import MipsService, MipsServiceState -from .common import randomize_int, MIoTMatcher +from .common import ( + randomize_int, load_yaml_file, gen_absolute_path, MIoTMatcher) _LOGGER = logging.getLogger(__name__) @@ -175,7 +176,7 @@ class MIoTLanDevice: OT_HEADER_LEN: int = 32 NETWORK_UNSTABLE_CNT_TH: int = 10 NETWORK_UNSTABLE_TIME_TH: int = 120000 - NETWORK_UNSTABLE_RESUME_TH: int = 300 + NETWORK_UNSTABLE_RESUME_TH: int = 300000 FAST_PING_INTERVAL: int = 5000 CONSTRUCT_STATE_PENDING: int = 15000 KA_INTERVAL_MIN = 10000 @@ -472,6 +473,8 @@ class MIoTLan: OT_PROBE_INTERVAL_MIN: int = 5000 OT_PROBE_INTERVAL_MAX: int = 45000 + PROFILE_MODELS_FILE: str = 'lan/profile_models.yaml' + _main_loop: asyncio.AbstractEventLoop _net_ifs: set[str] _network: MIoTNetwork @@ -502,6 +505,8 @@ class MIoTLan: _lan_state_sub_map: dict[str, Callable[[bool], asyncio.Future]] _lan_ctrl_vote_map: dict[str, bool] + _profile_models: dict[str, dict] + _init_done: bool def __init__( @@ -597,6 +602,12 @@ async def init_async(self) -> None: if self._net_ifs.isdisjoint(self._available_net_ifs): _LOGGER.info('no valid net_ifs') return + try: + self._profile_models = load_yaml_file( + yaml_file=gen_absolute_path(self.PROFILE_MODELS_FILE)) + except Exception as err: # pylint: disable=broad-exception-caught + _LOGGER.error('load profile models error, %s', err) + self._profile_models = {} self._mev = MIoTEventLoop() self._queue = queue.Queue() self._cmd_event_fd = os.eventfd(0, os.O_NONBLOCK) @@ -620,6 +631,7 @@ async def deinit_async(self) -> None: self.__lan_send_cmd(MIoTLanCmdType.DEINIT, None) self._thread.join() + self._profile_models = {} self._lan_devices = {} self._broadcast_socks = {} self._local_port = None @@ -1032,6 +1044,19 @@ def __cmd_read_handler(self, ctx: any) -> None: elif mips_cmd.type_ == MIoTLanCmdType.DEVICE_UPDATE: devices: dict[str, dict] = mips_cmd.data for did, info in devices.items(): + # did MUST be digit(UINT64) + if not did.isdigit(): + _LOGGER.info('invalid did, %s', did) + continue + if ( + 'model' not in info + or info['model'] in self._profile_models): + # Do not support the local control of + # Profile device for the time being + _LOGGER.info( + 'model not support local ctrl, %s, %s', + did, info.get('model')) + continue if did not in self._lan_devices: if 'token' not in info: _LOGGER.error( diff --git a/test/conftest.py b/test/conftest.py index e6583d9..9263402 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -43,6 +43,13 @@ def load_py_file(): dst=path.join(TEST_FILES_PATH, 'specs'), dirs_exist_ok=True) print('loaded spec test folder, specs') + # Copy lan files to test folder + shutil.copytree( + src=path.join( + TEST_ROOT_PATH, '../custom_components/xiaomi_home/miot/lan'), + dst=path.join(TEST_FILES_PATH, 'lan'), + dirs_exist_ok=True) + print('loaded lan test folder, lan') # Copy i18n files to test folder shutil.copytree( src=path.join( diff --git a/test/test_lan.py b/test/test_lan.py index e86a8be..4f96725 100755 --- a/test/test_lan.py +++ b/test/test_lan.py @@ -8,8 +8,37 @@ # pylint: disable=import-outside-toplevel, unused-argument +@pytest.mark.parametrize('test_devices', [{ + # specv2 model + '123456': { + 'token': '11223344556677d9a03d43936fc384205', + 'model': 'xiaomi.gateway.hub1' + }, + # profile model + '123457': { + 'token': '11223344556677d9a03d43936fc384205', + 'model': 'yeelink.light.lamp9' + }, + '123458': { + 'token': '11223344556677d9a03d43936fc384205', + 'model': 'zhimi.heater.ma1' + }, + # Non -digital did + 'group.123456': { + 'token': '11223344556677d9a03d43936fc384205', + 'model': 'mijia.light.group3' + }, + 'proxy.123456.1': { + 'token': '11223344556677d9a03d43936fc384205', + 'model': 'xiaomi.light.p1' + }, + 'miwifi_123456': { + 'token': '11223344556677d9a03d43936fc384205', + 'model': 'xiaomi.light.p1' + } +}]) @pytest.mark.asyncio -async def test_lan_async(): +async def test_lan_async(test_devices: dict): """ Use the central hub gateway as a test equipment, and through the local area network control central hub gateway indicator light switch. Please replace @@ -21,10 +50,13 @@ async def test_lan_async(): from miot.miot_lan import MIoTLan from miot.miot_mdns import MipsService - test_did = '' - test_token = '' + # Your central hub gateway did + test_did = '111111' + # Your central hub gateway did + test_token = '11223344556677d9a03d43936fc384205' test_model = 'xiaomi.gateway.hub1' - test_if_names = [''] + # Your computer interface list, such as enp3s0, wlp5s0 + test_if_names = ['enp3s0', 'wlp5s0'] # Check test params assert int(test_did) > 0 @@ -76,7 +108,8 @@ async def lan_state_change(state: bool): test_did: { 'token': test_token, 'model': test_model - } + }, + **test_devices }) # Test sub device state diff --git a/tools/common.py b/tools/common.py new file mode 100644 index 0000000..8dbb03a --- /dev/null +++ b/tools/common.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +"""Common functions.""" +import json +import yaml +from urllib.parse import urlencode +from urllib.request import Request, urlopen + + +def load_yaml_file(yaml_file: str) -> dict: + with open(yaml_file, 'r', encoding='utf-8') as file: + return yaml.safe_load(file) + + +def save_yaml_file(yaml_file: str, data: dict) -> None: + with open(yaml_file, 'w', encoding='utf-8') as file: + yaml.safe_dump( + data=data, stream=file, allow_unicode=True) + + +def load_json_file(json_file: str) -> dict: + with open(json_file, 'r', encoding='utf-8') as file: + return json.load(file) + + +def save_json_file(json_file: str, data: dict) -> None: + with open(json_file, 'w', encoding='utf-8') as file: + json.dump(data, file, ensure_ascii=False, indent=4) + + +def http_get( + url: str, params: dict = None, headers: dict = None +) -> dict: + if params: + encoded_params = urlencode(params) + full_url = f'{url}?{encoded_params}' + else: + full_url = url + request = Request(full_url, method='GET', headers=headers or {}) + content: bytes = None + with urlopen(request) as response: + content = response.read() + return ( + json.loads(str(content, 'utf-8')) + if content is not None else None) diff --git a/tools/update_lan_rule.py b/tools/update_lan_rule.py new file mode 100644 index 0000000..9f4d4fa --- /dev/null +++ b/tools/update_lan_rule.py @@ -0,0 +1,80 @@ +""" Update LAN rule.""" +# -*- coding: utf-8 -*- +# pylint: disable=relative-beyond-top-level +from os import path +from common import ( + http_get, + load_yaml_file, + save_yaml_file) + + +ROOT_PATH: str = path.dirname(path.abspath(__file__)) +LAN_PROFILE_MODELS_FILE: str = path.join( + ROOT_PATH, + '../custom_components/xiaomi_home/miot/lan/profile_models.yaml') + + +SPECIAL_MODELS: list[str] = [ + # model2class-v2 + 'chuangmi.camera.ipc007b', 'chuangmi.camera.ipc019b', + 'chuangmi.camera.ipc019e', 'chuangmi.camera.ipc020', + 'chuangmi.camera.v2', 'chuangmi.camera.v5', + 'chuangmi.camera.v6', 'chuangmi.camera.xiaobai', + 'chuangmi.radio.v1', 'chuangmi.radio.v2', + 'hith.foot_bath.q2', 'imou99.camera.tp2', + 'isa.camera.hl5', 'isa.camera.isc5', + 'jiqid.mistory.pro', 'jiqid.mistory.v1', + 'lumi.airrtc.tcpco2ecn01', 'lumi.airrtc.tcpecn02', + 'lumi.camera.gwagl01', 'miir.light.ir01', + 'miir.projector.ir01', 'miir.tv.hir01', + 'miir.tvbox.ir01', 'roome.bhf_light.yf6002', + 'smith.waterpuri.jnt600', 'viomi.fridge.u2', + 'xiaovv.camera.lamp', 'xiaovv.camera.ptz', + 'xiaovv.camera.xva3', 'xiaovv.camera.xvb4', + 'xiaovv.camera.xvsnowman', 'zdeer.ajh.a8', + 'zdeer.ajh.a9', 'zdeer.ajh.zda10', + 'zdeer.ajh.zda9', 'zdeer.ajh.zjy', 'zimi.clock.myk01', + # specialModels + 'chuangmi.camera.ipc004b', 'chuangmi.camera.ipc009', + 'chuangmi.camera.ipc010', 'chuangmi.camera.ipc013', + 'chuangmi.camera.ipc013d', 'chuangmi.camera.ipc016', + 'chuangmi.camera.ipc017', 'chuangmi.camera.ipc019', + 'chuangmi.camera.ipc021', 'chuangmi.camera.v3', + 'chuangmi.camera.v4', 'isa.camera.df3', + 'isa.camera.hlc6', 'lumi.acpartner.v1', + 'lumi.acpartner.v2', 'lumi.acpartner.v3', + 'lumi.airrtc.tcpecn01', 'lumi.camera.aq1', + 'miir.aircondition.ir01', 'miir.aircondition.ir02', + 'miir.fan.ir01', 'miir.stb.ir01', + 'miir.tv.ir01', 'mijia.camera.v1', + 'mijia.camera.v3', 'roborock.sweeper.s5v2', + 'roborock.vacuum.c1', 'roborock.vacuum.e2', + 'roborock.vacuum.m1s', 'roborock.vacuum.s5', + 'rockrobo.vacuum.v1', 'xiaovv.camera.xvd5'] + + +def update_profile_model(file_path: str): + profile_rules: dict = http_get( + url='https://miot-spec.org/instance/translate/models') + if not profile_rules and 'models' not in profile_rules and not isinstance( + profile_rules['models'], dict): + raise ValueError('Failed to get profile rule') + local_rules: dict = load_yaml_file( + yaml_file=file_path) or {} + for rule, ts in profile_rules['models'].items(): + if rule not in local_rules: + local_rules[rule] = {'ts': ts} + else: + local_rules[rule]['ts'] = ts + for mode in SPECIAL_MODELS: + if mode not in local_rules: + local_rules[mode] = {'ts': 1531108800} + else: + local_rules[mode]['ts'] = 1531108800 + local_rules = dict(sorted(local_rules.items())) + save_yaml_file( + yaml_file=file_path, data=local_rules) + + +update_profile_model(file_path=LAN_PROFILE_MODELS_FILE) +print('profile model list updated.') From 67785f747aaf4055028253e105ccfe9c38b447e8 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:20:49 +0800 Subject: [PATCH 05/17] doc: update changelog and version (#290) * doc: update changelog and version * doc: update changelog --- CHANGELOG.md | 11 +++++++++++ custom_components/xiaomi_home/manifest.json | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a14aec7..e1fdf52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # CHANGELOG +## v0.1.3 +### Added +### Changed +- Remove default bug label. [#276](https://github.com/XiaoMi/ha_xiaomi_home/pull/276) +- Improve multi-language translation actions. [#256](https://github.com/XiaoMi/ha_xiaomi_home/pull/256) +- Use aiohttp instead of waiting for blocking calls. [#227](https://github.com/XiaoMi/ha_xiaomi_home/pull/227) +- Language supports dt. [#237](https://github.com/XiaoMi/ha_xiaomi_home/pull/237) +### Fixed +- Fix local control error. [#271](https://github.com/XiaoMi/ha_xiaomi_home/pull/271) +- Fix README_zh and miot_storage. [#270](https://github.com/XiaoMi/ha_xiaomi_home/pull/270) + ## v0.1.2 ### Added - Support Xiaomi Heater devices. https://github.com/XiaoMi/ha_xiaomi_home/issues/124 https://github.com/XiaoMi/ha_xiaomi_home/issues/117 diff --git a/custom_components/xiaomi_home/manifest.json b/custom_components/xiaomi_home/manifest.json index 5f55d15..19afdc2 100644 --- a/custom_components/xiaomi_home/manifest.json +++ b/custom_components/xiaomi_home/manifest.json @@ -26,8 +26,8 @@ "psutil", "aiohttp[speedups]" ], - "version": "v0.1.2", + "version": "v0.1.3", "zeroconf": [ "_miot-central._tcp.local." ] -} +} \ No newline at end of file From f87e746188ad6f6411b1e83733cd4d02e5fb1285 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Sat, 21 Dec 2024 10:42:48 +0800 Subject: [PATCH 06/17] fix: fix miot cloud token refresh (#307) --- custom_components/xiaomi_home/miot/miot_cloud.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_cloud.py b/custom_components/xiaomi_home/miot/miot_cloud.py index dd4c282..2d5c49c 100644 --- a/custom_components/xiaomi_home/miot/miot_cloud.py +++ b/custom_components/xiaomi_home/miot/miot_cloud.py @@ -204,7 +204,7 @@ async def refresh_access_token_async(self, refresh_token: str) -> dict: if not isinstance(refresh_token, str): raise MIoTOauthError('invalid refresh_token') - return await self._get_token_async(data={ + return await self.__get_token_async(data={ 'client_id': self._client_id, 'redirect_uri': self._redirect_url, 'refresh_token': refresh_token, @@ -661,7 +661,6 @@ async def get_props_async(self, params: list) -> list: raise MIoTHttpError('invalid response result') return res_obj['result'] - async def __get_prop_async(self, did: str, siid: int, piid: int) -> any: results = await self.get_props_async( params=[{'did': did, 'siid': siid, 'piid': piid}]) From 02ddf8df56d1392a03d7dfb74c659202f469d844 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Sat, 21 Dec 2024 11:08:36 +0800 Subject: [PATCH 07/17] fix: fix lan ctrl filter logic (#303) * style: rename devices_filter to homes_select * fix: fix miot_lan update_devices * fix: async blocking call --- custom_components/xiaomi_home/config_flow.py | 36 +++++++++---------- .../xiaomi_home/miot/miot_client.py | 2 ++ .../xiaomi_home/miot/miot_lan.py | 5 +-- .../xiaomi_home/translations/de.json | 4 +-- .../xiaomi_home/translations/en.json | 4 +-- .../xiaomi_home/translations/es.json | 4 +-- .../xiaomi_home/translations/fr.json | 4 +-- .../xiaomi_home/translations/ja.json | 4 +-- .../xiaomi_home/translations/nl.json | 4 +-- .../xiaomi_home/translations/pt-BR.json | 4 +-- .../xiaomi_home/translations/pt.json | 4 +-- .../xiaomi_home/translations/ru.json | 4 +-- .../xiaomi_home/translations/zh-Hans.json | 4 +-- .../xiaomi_home/translations/zh-Hant.json | 4 +-- 14 files changed, 45 insertions(+), 42 deletions(-) diff --git a/custom_components/xiaomi_home/config_flow.py b/custom_components/xiaomi_home/config_flow.py index c1e4e67..b9f3776 100644 --- a/custom_components/xiaomi_home/config_flow.py +++ b/custom_components/xiaomi_home/config_flow.py @@ -315,7 +315,7 @@ async def async_step_oauth(self, user_input=None): _LOGGER.error('task_oauth exception, %s', error) self._config_error_reason = str(error) return self.async_show_progress_done(next_step_id='oauth_error') - return self.async_show_progress_done(next_step_id='devices_filter') + return self.async_show_progress_done(next_step_id='homes_select') return self.async_show_progress( step_id='oauth', progress_action='oauth', @@ -469,15 +469,15 @@ async def async_step_oauth_error(self, user_input=None): errors={'base': error_reason}, ) - async def async_step_devices_filter(self, user_input=None): - _LOGGER.debug('async_step_devices_filter') + async def async_step_homes_select(self, user_input=None): + _LOGGER.debug('async_step_homes_select') try: if user_input is None: - return await self.display_device_filter_form('') + return await self.display_homes_select_form('') home_selected: list = user_input.get('home_infos', []) if not home_selected: - return await self.display_device_filter_form( + return await self.display_homes_select_form( 'no_family_selected') self._ctrl_mode = user_input.get('ctrl_mode') for home_id, home_info in self._home_info_buffer[ @@ -495,7 +495,7 @@ async def async_step_devices_filter(self, user_input=None): 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_device_filter_form('no_devices') + return await self.display_homes_select_form('no_devices') devices_list_sort = dict(sorted( devices_list.items(), key=lambda item: item[1].get('home_id', '')+item[1].get('room_id', ''))) @@ -506,7 +506,7 @@ async def async_step_devices_filter(self, user_input=None): _LOGGER.error( 'save devices async failed, %s, %s', self._uid, self._cloud_server) - return await self.display_device_filter_form( + return await self.display_homes_select_form( 'devices_storage_failed') if not (await self._miot_storage.update_user_config_async( uid=self._uid, cloud_server=self._cloud_server, config={ @@ -535,7 +535,7 @@ async def async_step_devices_filter(self, user_input=None): }) except Exception as err: _LOGGER.error( - 'async_step_devices_filter, %s, %s', + 'async_step_homes_select, %s, %s', err, traceback.format_exc()) raise AbortFlow( reason='config_flow_error', @@ -543,9 +543,9 @@ async def async_step_devices_filter(self, user_input=None): 'error': f'config_flow error, {err}'} ) from err - async def display_device_filter_form(self, reason: str): + async def display_homes_select_form(self, reason: str): return self.async_show_form( - step_id='devices_filter', + step_id='homes_select', data_schema=vol.Schema({ vol.Required('ctrl_mode', default=DEFAULT_CTRL_MODE): vol.In( self._miot_i18n.translate(key='config.control_mode')), @@ -941,7 +941,7 @@ async def async_step_config_options(self, user_input=None): async def async_step_update_user_info(self, user_input=None): if not self._update_user_info: - return await self.async_step_devices_filter() + return await self.async_step_homes_select() if not user_input: nick_name_new = ( await self._miot_http.get_user_info_async() or {}).get( @@ -958,9 +958,9 @@ async def async_step_update_user_info(self, user_input=None): ) self._nick_name_new = user_input.get('nick_name') - return await self.async_step_devices_filter() + return await self.async_step_homes_select() - async def async_step_devices_filter(self, user_input=None): + async def async_step_homes_select(self, user_input=None): if not self._update_devices: return await self.async_step_update_trans_rules() if not user_input: @@ -1012,11 +1012,11 @@ async def async_step_devices_filter(self, user_input=None): if home_id in home_list] self._home_list = dict(sorted(home_list.items())) - return await self.display_device_filter_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_device_filter_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[ @@ -1029,7 +1029,7 @@ async def async_step_devices_filter(self, user_input=None): 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_device_filter_form('no_devices') + return await self.display_homes_select_form('no_devices') # Statistics devices changed self._devices_add = [] self._devices_remove = [] @@ -1047,9 +1047,9 @@ async def async_step_devices_filter(self, user_input=None): self._devices_add, self._devices_remove) return await self.async_step_update_trans_rules() - async def display_device_filter_form(self, reason: str): + async def display_homes_select_form(self, reason: str): return self.async_show_form( - step_id='devices_filter', + step_id='homes_select', data_schema=vol.Schema({ vol.Required( 'ctrl_mode', default=self._ctrl_mode diff --git a/custom_components/xiaomi_home/miot/miot_client.py b/custom_components/xiaomi_home/miot/miot_client.py index 31c87f0..7423e20 100644 --- a/custom_components/xiaomi_home/miot/miot_client.py +++ b/custom_components/xiaomi_home/miot/miot_client.py @@ -1028,6 +1028,7 @@ async def __on_miot_lan_state_change(self, state: bool) -> None: self._miot_lan.update_devices(devices={ did: { 'token': info['token'], + 'model': info['model'], 'connect_type': info['connect_type']} for did, info in self._device_list_cache.items() if 'token' in info and 'connect_type' in info @@ -1291,6 +1292,7 @@ async def __refresh_cloud_devices_async(self) -> None: self._miot_lan.update_devices(devices={ did: { 'token': info['token'], + 'model': info['model'], 'connect_type': info['connect_type']} for did, info in self._device_list_cache.items() if 'token' in info and 'connect_type' in info diff --git a/custom_components/xiaomi_home/miot/miot_lan.py b/custom_components/xiaomi_home/miot/miot_lan.py index 07aadbc..6679328 100644 --- a/custom_components/xiaomi_home/miot/miot_lan.py +++ b/custom_components/xiaomi_home/miot/miot_lan.py @@ -603,8 +603,9 @@ async def init_async(self) -> None: _LOGGER.info('no valid net_ifs') return try: - self._profile_models = load_yaml_file( - yaml_file=gen_absolute_path(self.PROFILE_MODELS_FILE)) + self._profile_models = await self._main_loop.run_in_executor( + None, load_yaml_file, + gen_absolute_path(self.PROFILE_MODELS_FILE)) except Exception as err: # pylint: disable=broad-exception-caught _LOGGER.error('load profile models error, %s', err) self._profile_models = {} diff --git a/custom_components/xiaomi_home/translations/de.json b/custom_components/xiaomi_home/translations/de.json index 226aa57..b06c460 100644 --- a/custom_components/xiaomi_home/translations/de.json +++ b/custom_components/xiaomi_home/translations/de.json @@ -22,7 +22,7 @@ "title": "Fehler bei der Anmeldung", "description": "Klicken Sie auf \"Weiter\", um es erneut zu versuchen." }, - "devices_filter": { + "homes_select": { "title": "Familie und Geräte auswählen", "description": "## Gebrauchsanweisung\r\n### Steuerungsmodus\r\n- Automatisch: Wenn im lokalen Netzwerk ein verfügbarer Xiaomi-Zentralgateway vorhanden ist, wird Home Assistant bevorzugt Steuerbefehle über den Zentralgateway senden, um eine lokale Steuerung zu ermöglichen. Wenn im lokalen Netzwerk kein Zentralgateway vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden, um eine lokale Steuerung zu ermöglichen. Nur wenn die oben genannten Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Steuerbefehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden nur über die Cloud gesendet.\r\n### Familienimport für importierte Geräte\r\nDie Integration fügt Geräte aus den ausgewählten Familien hinzu.\r\n### Raumnamensynchronisationsmodus\r\nWenn Geräte von der Xiaomi Home App zu Home Assistant synchronisiert werden, wird die Bezeichnung des Bereichs, in dem sich die Geräte in Home Assistant befinden, nach folgenden Regeln benannt. Beachten Sie, dass das Synchronisieren von Geräten den von Xiaomi Home App festgelegten Familien- und Raum-Einstellungen nicht ändert.\r\n- Nicht synchronisieren: Das Gerät wird keinem Bereich hinzugefügt.\r\n- Andere Optionen: Der Bereich, in den das Gerät aufgenommen wird, wird nach dem Namen der Familie oder des Raums in der Xiaomi Home App benannt.\r\n### Action-Debug-Modus\r\nFür von MIoT-Spec-V2 definierte Gerätemethoden wird neben der Benachrichtigungs-Entität auch eine Texteingabe-Entität generiert. Damit können Sie bei der Fehlerbehebung Steuerbefehle an das Gerät senden.\r\n### Verstecke Nicht-Standard-Entitäten\r\nVerstecke Entitäten, die von nicht standardmäßigen MIoT-Spec-V2-Instanzen mit einem Namen beginnen, der mit einem \"*\" beginnt.\r\n\r\n \r\n### Hallo {nick_name}! Bitte wählen Sie den Steuerungsmodus der Integration sowie die Familie aus, in der sich die hinzuzufügenden Geräte befinden.", "data": { @@ -87,7 +87,7 @@ "nick_name": "Benutzername" } }, - "devices_filter": { + "homes_select": { "title": "Familie und Geräte neu auswählen", "description": "## Gebrauchsanweisung\r\n### Steuerungsmodus\r\n- Automatisch: Wenn im lokalen Netzwerk ein verfügbarer Xiaomi-Zentralgateway vorhanden ist, wird Home Assistant bevorzugt Steuerbefehle über den Zentralgateway senden, um eine lokale Steuerung zu ermöglichen. Wenn im lokalen Netzwerk kein Zentralgateway vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden, um eine lokale Steuerung zu ermöglichen. Nur wenn die oben genannten Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Steuerbefehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden nur über die Cloud gesendet.\r\n### Familienimport für importierte Geräte\r\nDie Integration fügt Geräte aus den ausgewählten Familien hinzu.\r\n \r\n### Hallo {nick_name}! Bitte wählen Sie den Steuerungsmodus der Integration sowie die Familie aus, in der sich die hinzuzufügenden Geräte befinden.", "data": { diff --git a/custom_components/xiaomi_home/translations/en.json b/custom_components/xiaomi_home/translations/en.json index 8fe89c5..ddb424a 100644 --- a/custom_components/xiaomi_home/translations/en.json +++ b/custom_components/xiaomi_home/translations/en.json @@ -22,7 +22,7 @@ "title": "Login Error", "description": "Click NEXT to try again." }, - "devices_filter": { + "homes_select": { "title": "Select Home and Devices", "description": "## Usage Instructions\r\n### Control mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi LAN control function. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Import devices from home\r\nThe integration will add devices from the selected homes.\n### Room name synchronizing mode\nWhen importing devices from Xiaomi Home APP to Home Assistant, the naming convention of the area where the device is added to is as follows. Note that the device synchronizing process does not change the home or room settings in Xiaomi Home APP.\r\n- Do not synchronize: The device will not be added to any area.\r\n- Other options: The device will be added to an area named as the home and/or room name that already exists in Xiaomi Home APP.\r\n### Debug mode for action\r\nFor the action defined in MIoT-Spec-V2 of the device, a Text entity along with a Notify entity will be created, in which you can send control commands to the device for debugging.\r\n### Hide non-standard created entities\r\nHide the entities generated from non-standard MIoT-Spec-V2 instances, whose names begin with \"*\".\r\n\r\n \r\n### Hello {nick_name}, please select the integration control mode and the home where the device you want to import.", "data": { @@ -87,7 +87,7 @@ "nick_name": "Nick Name" } }, - "devices_filter": { + "homes_select": { "title": "Re-select Home and Devices", "description": "## Usage Instructions\r\n### Control mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi LAN control function. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Import devices from home\r\nThe integration will add devices from the selected homes.\r\n \r\n### Hello {nick_name}, please select the integration control mode and the home where the device you want to import.", "data": { diff --git a/custom_components/xiaomi_home/translations/es.json b/custom_components/xiaomi_home/translations/es.json index 22fd8a5..23ee109 100644 --- a/custom_components/xiaomi_home/translations/es.json +++ b/custom_components/xiaomi_home/translations/es.json @@ -22,7 +22,7 @@ "title": "Error de inicio de sesión", "description": "Haga clic en \"Siguiente\" para volver a intentarlo" }, - "devices_filter": { + "homes_select": { "title": "Seleccionar hogares y dispositivos", "description": "## Instrucciones de uso\r\n### Modo de control\r\n- Automático: Cuando hay un gateway central de Xiaomi disponible en la red local, Home Assistant priorizará el envío de comandos de control de dispositivos a través del gateway central para lograr un control localizado. Si no hay un gateway central en la red local, intentará enviar comandos de control a través del protocolo Xiaomi OT para lograr un control localizado. Solo cuando no se cumplan las condiciones anteriores de control localizado, los comandos de control del dispositivo se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Hogares de dispositivos importados\r\nLa integración agregará los dispositivos en los hogares seleccionados.\r\n### Modo de sincronización del nombre de la habitación\r\nCuando se sincronizan los dispositivos desde la aplicación Xiaomi Home a Home Assistant, los nombres de las áreas donde se encuentran los dispositivos en Home Assistant seguirán las reglas de nomenclatura a continuación. Tenga en cuenta que el proceso de sincronización de dispositivos no cambiará la configuración de hogares y habitaciones en la aplicación Xiaomi Home.\r\n- Sin sincronización: el dispositivo no se agregará a ninguna área.\r\n- Otras opciones: la zona donde se agrega el dispositivo tendrá el mismo nombre que el hogar o la habitación en la aplicación Xiaomi Home.\r\n### Modo de depuración de Action\r\nPara los métodos definidos por MIoT-Spec-V2, además de generar una entidad de notificación, también se generará una entidad de cuadro de entrada de texto que se puede utilizar para enviar comandos de control al dispositivo durante la depuración.\r\n### Ocultar entidades generadas no estándar\r\nOcultar las entidades generadas por la instancia no estándar MIoT-Spec-V2 que comienzan con \"*\".\r\n\r\n \r\n### ¡Hola, {nick_name}! Seleccione el modo de control de integración y el hogar donde se encuentran los dispositivos que desea agregar.", "data": { @@ -87,7 +87,7 @@ "nick_name": "Apodo de usuario" } }, - "devices_filter": { + "homes_select": { "title": "Recomendar hogares y dispositivos", "description": "## Instrucciones de uso\r\n### Modo de control\r\n- Automático: Cuando hay un gateway central de Xiaomi disponible en la red local, Home Assistant priorizará el envío de comandos de control de dispositivos a través del gateway central para lograr un control localizado. Si no hay un gateway central en la red local, intentará enviar comandos de control a través del protocolo Xiaomi OT para lograr un control localizado. Solo cuando no se cumplan las condiciones anteriores de control localizado, los comandos de control del dispositivo se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Hogares de dispositivos importados\r\nLa integración agregará los dispositivos en los hogares seleccionados.\r\n \r\n### ¡Hola, {nick_name}! Seleccione el modo de control de integración y el hogar donde se encuentran los dispositivos que desea agregar.", "data": { diff --git a/custom_components/xiaomi_home/translations/fr.json b/custom_components/xiaomi_home/translations/fr.json index 9be87b8..82c8908 100644 --- a/custom_components/xiaomi_home/translations/fr.json +++ b/custom_components/xiaomi_home/translations/fr.json @@ -22,7 +22,7 @@ "title": "Erreur de connexion", "description": "Cliquez sur \"Suivant\" pour réessayer" }, - "devices_filter": { + "homes_select": { "title": "Sélectionner une maison et des appareils", "description": "## Instructions d'utilisation\r\n### Mode de contrôle\r\n- Automatique: Lorsqu'il y a une passerelle centrale Xiaomi disponible dans le réseau local, Home Assistant priorisera l'envoi des commandes de contrôle des appareils via la passerelle centrale pour réaliser un contrôle localisé. S'il n'y a pas de passerelle centrale dans le réseau local, il tentera d'envoyer des commandes de contrôle via le protocole Xiaomi OT pour réaliser un contrôle localisé. Ce n'est que lorsque les conditions de contrôle localisé ci-dessus ne sont pas remplies que les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud: Les commandes de contrôle ne sont envoyées que via le cloud.\r\n### Importer une maison pour les appareils\r\nL'intégration ajoutera les appareils de la maison sélectionnée.\r\n### Mode de synchronisation des noms de pièces\r\nLors de la synchronisation des appareils de Xiaomi Home à Home Assistant, le nom de la pièce où se trouve l'appareil sera nommé selon les règles suivantes. Notez que le processus de synchronisation des appareils n'affecte pas les paramètres de la maison et de la pièce dans Xiaomi Home APP.\r\n- Ne pas synchroniser: L'appareil ne sera ajouté à aucune zone.\r\n- Autre option: La zone dans laquelle l'appareil est ajouté est nommée en fonction du nom de la maison ou de la pièce de Xiaomi Home APP.\r\n### Mode de débogage d'action\r\nPour les méthodes définies par MIoT-Spec-V2, en plus de générer une entité de notification, une entité de zone de texte sera également générée pour que vous puissiez envoyer des commandes de contrôle à l'appareil lors du débogage.\r\n### Masquer les entités générées non standard\r\nMasquer les entités générées non standard de MIoT-Spec-V2 commençant par \"*\".\r\n\r\n \r\n### {nick_name} Bonjour ! Veuillez sélectionner le mode de contrôle de l'intégration et la maison où se trouvent les appareils à ajouter.", "data": { @@ -87,7 +87,7 @@ "nick_name": "Pseudo utilisateur" } }, - "devices_filter": { + "homes_select": { "title": "Re-sélectionner une maison et des appareils", "description": "## Instructions d'utilisation\r\n### Mode de contrôle\r\n- Automatique: Lorsqu'il y a une passerelle centrale Xiaomi disponible dans le réseau local, Home Assistant priorisera l'envoi des commandes de contrôle des appareils via la passerelle centrale pour réaliser un contrôle localisé. S'il n'y a pas de passerelle centrale dans le réseau local, il tentera d'envoyer des commandes de contrôle via le protocole Xiaomi OT pour réaliser un contrôle localisé. Ce n'est que lorsque les conditions de contrôle localisé ci-dessus ne sont pas remplies que les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud: Les commandes de contrôle ne sont envoyées que via le cloud.\r\n### Importer une maison pour les appareils\r\nL'intégration ajoutera les appareils de la maison sélectionnée.\r\n \r\n### {nick_name} Bonjour ! Veuillez sélectionner le mode de contrôle de l'intégration et la maison où se trouvent les appareils à ajouter.", "data": { diff --git a/custom_components/xiaomi_home/translations/ja.json b/custom_components/xiaomi_home/translations/ja.json index 780378d..a8d6b36 100644 --- a/custom_components/xiaomi_home/translations/ja.json +++ b/custom_components/xiaomi_home/translations/ja.json @@ -22,7 +22,7 @@ "title": "ログインエラー", "description": "「次へ」をクリックして再試行してください" }, - "devices_filter": { + "homes_select": { "title": "ホームとデバイスを選択", "description": "## 使用方法\r\n### 制御モード\r\n- 自動: ローカルエリアネットワーク内に利用可能なXiaomi中央ゲートウェイが存在する場合、Home Assistantは中央ゲートウェイを介してデバイス制御コマンドを優先的に送信し、ローカル制御機能を実現します。ローカルエリアネットワーク内に中央ゲートウェイが存在しない場合、Xiaomi OTプロトコルを介して制御コマンドを送信し、ローカル制御機能を実現しようとします。上記のローカル制御条件が満たされない場合にのみ、デバイス制御コマンドはクラウドを介して送信されます。\r\n- クラウド: 制御コマンドはクラウドを介してのみ送信されます。\r\n### 導入されたデバイスのホーム\r\n統合は、選択された家庭にあるデバイスを追加します。\r\n### 部屋名同期モード\r\nXiaomi Home アプリから Home Assistant に同期されるデバイスの場合、デバイスが Home Assistant 内でどのような領域にあるかを示す名前の命名方式は、以下のルールに従います。ただし、デバイスの同期プロセスは、Xiaomi Home アプリで家庭および部屋の設定を変更しないことに注意してください。\r\n- 同期しない:デバイスはどの領域にも追加されません。\r\n- その他のオプション:デバイスが追加される領域は、Xiaomi Home アプリの家庭または部屋の名前に従って命名されます。\r\n### Action デバッグモード\r\nデバイスが MIoT-Spec-V2 で定義された方法を実行する場合、通知エンティティの生成に加えて、テキスト入力ボックスエンティティも生成されます。これを使用して、デバイスに制御命令を送信することができます。\r\n### 非標準生成エンティティを非表示にする\r\n「*」で始まる名前の非標準 MIoT-Spec-V2 インスタンスによって生成されたエンティティを非表示にします。\r\n\r\n \r\n### {nick_name} さん、こんにちは! 統合制御モードと追加するデバイスがあるホームを選択してください。", "data": { @@ -87,7 +87,7 @@ "nick_name": "ユーザー名" } }, - "devices_filter": { + "homes_select": { "title": "ホームとデバイスを再度選択", "description": "## 使用方法\r\n### 制御モード\r\n- 自動: ローカルエリアネットワーク内に利用可能なXiaomi中央ゲートウェイが存在する場合、Home Assistantは中央ゲートウェイを介してデバイス制御コマンドを優先的に送信し、ローカル制御機能を実現します。ローカルエリアネットワーク内に中央ゲートウェイが存在しない場合、Xiaomi OTプロトコルを介して制御コマンドを送信し、ローカル制御機能を実現しようとします。上記のローカル制御条件が満たされない場合にのみ、デバイス制御コマンドはクラウドを介して送信されます。\r\n- クラウド: 制御コマンドはクラウドを介してのみ送信されます。\r\n### 導入されたデバイスのホーム\r\n統合は、選択された家庭にあるデバイスを追加します。\r\n \r\n### {nick_name} さん、こんにちは! 統合制御モードと追加するデバイスがあるホームを選択してください。", "data": { diff --git a/custom_components/xiaomi_home/translations/nl.json b/custom_components/xiaomi_home/translations/nl.json index aa15df8..a01f845 100644 --- a/custom_components/xiaomi_home/translations/nl.json +++ b/custom_components/xiaomi_home/translations/nl.json @@ -22,7 +22,7 @@ "title": "Inlogfout", "description": "Klik OP VOLGENDE om het opnieuw te proberen." }, - "devices_filter": { + "homes_select": { "title": "Selecteer Huis en Apparaten", "description": "## Gebruiksinstructies\r\n### Controlemodus\r\n- Auto: Wanneer er een beschikbare Xiaomi centrale hubgateway in het lokale netwerk is, geeft Home Assistant de voorkeur aan het verzenden van apparaatbedieningscommando's via de centrale hubgateway om lokale controle te bereiken. Als er geen centrale hubgateway in het lokale netwerk is, zal het proberen bedieningscommando's te verzenden via de Xiaomi LAN-controlefunctie. Alleen wanneer de bovenstaande lokale controlevoorwaarden niet zijn vervuld, worden de apparaatbedieningscommando's via de cloud verzonden.\r\n- Cloud: Alle bedieningscommando's worden via de cloud verzonden.\r\n### Apparaten importeren vanuit huis\r\nDe integratie voegt apparaten toe van de geselecteerde huizen.\n### Ruimtenaamsynchronisatiemodus\nBij het importeren van apparaten vanuit de Xiaomi Home APP naar Home Assistant is de naamgevingsconventie van het gebied waarin het apparaat wordt toegevoegd als volgt. Opmerking: het synchronisatieproces van het apparaat verandert de huis- of ruimte-instellingen in de Xiaomi Home APP niet.\r\n- Niet synchroniseren: Het apparaat wordt aan geen enkel gebied toegevoegd.\r\n- Andere opties: Het apparaat wordt toegevoegd aan een gebied dat is genoemd naar de huis- en/of ruimtenamen die al bestaan in de Xiaomi Home APP.\r\n### Debugmodus voor actie\r\nVoor de actie gedefinieerd in MIoT-Spec-V2 van het apparaat, wordt er een Tekstentiteit samen met een Notificatie-entiteit aangemaakt, waarin u bedieningscommando's naar het apparaat kunt sturen voor debugging.\r\n### Verberg niet-standaard gemaakte entiteiten\r\nVerberg de entiteiten die zijn gegenereerd vanuit niet-standaard MIoT-Spec-V2-instanties, waarvan de namen beginnen met \"*\".\r\n\r\n \r\n### Hallo {nick_name}, selecteer alstublieft de integratie controlemethodiek en het huis waar het apparaat dat u wilt importeren zich bevindt.", "data": { @@ -87,7 +87,7 @@ "nick_name": "Bijnaam" } }, - "devices_filter": { + "homes_select": { "title": "Huis en Apparaten opnieuw selecteren", "description": "## Gebruiksinstructies\r\n### Controlemodus\r\n- Auto: Wanneer er een beschikbare Xiaomi centrale hubgateway in het lokale netwerk is, geeft Home Assistant de voorkeur aan het verzenden van apparaatbedieningscommando's via de centrale hubgateway om lokale controle te bereiken. Als er geen centrale hubgateway in het lokale netwerk is, zal het proberen bedieningscommando's te verzenden via de Xiaomi LAN-controlefunctie. Alleen wanneer de bovenstaande lokale controlevoorwaarden niet zijn vervuld, worden de apparaatbedieningscommando's via de cloud verzonden.\r\n- Cloud: Alle bedieningscommando's worden via de cloud verzonden.\r\n### Apparaten importeren vanuit huis\r\nDe integratie voegt apparaten toe van de geselecteerde huizen.\r\n \r\n### Hallo {nick_name}, selecteer alstublieft de integratie controlemethodiek en het huis waar het apparaat dat u wilt importeren zich bevindt.", "data": { diff --git a/custom_components/xiaomi_home/translations/pt-BR.json b/custom_components/xiaomi_home/translations/pt-BR.json index 4ae8c2e..fca54a5 100644 --- a/custom_components/xiaomi_home/translations/pt-BR.json +++ b/custom_components/xiaomi_home/translations/pt-BR.json @@ -22,7 +22,7 @@ "title": "Erro de Login", "description": "Clique em AVANÇAR para tentar novamente." }, - "devices_filter": { + "homes_select": { "title": "Selecione a Casa e os Dispositivos", "description": "## Instruções de Uso\r\n### Modo de controle\r\n- Auto: Quando houver um gateway central Xiaomi disponível na rede local, o Home Assistant priorizará o envio de comandos de controle do dispositivo através do gateway central, obtendo assim controle local. Se não houver gateway central na rede local, ele tentará enviar comandos através da função de controle LAN da Xiaomi. Somente quando as condições de controle local acima não forem atendidas, os comandos serão enviados pela nuvem.\r\n- Nuvem: Todos os comandos de controle são enviados através da nuvem.\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\n### Modo de sincronização do nome do cômodo\r\nAo importar dispositivos do aplicativo Xiaomi Home para o Home Assistant, a convenção de nomeação da área onde o dispositivo é adicionado é a seguinte. Observe que o processo de sincronização do dispositivo não altera as configurações de casa ou cômodo no aplicativo Xiaomi Home.\r\n- Não sincronizar: O dispositivo não será adicionado a nenhuma área.\r\n- Outras opções: O dispositivo será adicionado a uma área nomeada de acordo com o nome da casa e/ou do cômodo que já existem no aplicativo Xiaomi Home.\r\n### Modo de depuração para ação\r\nPara as ações definidas no MIoT-Spec-V2 do dispositivo, será criada uma entidade de texto juntamente com uma entidade de notificação, nas quais você poderá enviar comandos de controle ao dispositivo para fins de depuração.\r\n### Ocultar entidades criadas não padrão\r\nOculta as entidades geradas a partir de instâncias não padrão do MIoT-Spec-V2, cujos nomes começam com \"*\".\r\n\r\n \r\n### Olá {nick_name}, selecione o modo de controle da integração e a casa onde estão os dispositivos que você deseja importar.", "data": { @@ -87,7 +87,7 @@ "nick_name": "Apelido" } }, - "devices_filter": { + "homes_select": { "title": "Selecionar novamente Casa e Dispositivos", "description": "## Instruções de Uso\r\n### Modo de controle\r\n- Auto: Quando houver um gateway central Xiaomi disponível na rede local, o Home Assistant priorizará o envio de comandos através dele para obter controle local. Caso não haja, tentará enviar comandos através da função de controle LAN da Xiaomi. Somente se as condições anteriores não forem atendidas, o controle será feito pela nuvem.\r\n- Nuvem: Todos os comandos de controle são enviados pela nuvem.\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\r\n \r\n### Olá {nick_name}, selecione o modo de controle da integração e a casa de onde deseja importar dispositivos.", "data": { diff --git a/custom_components/xiaomi_home/translations/pt.json b/custom_components/xiaomi_home/translations/pt.json index ca72df4..480f59e 100644 --- a/custom_components/xiaomi_home/translations/pt.json +++ b/custom_components/xiaomi_home/translations/pt.json @@ -22,7 +22,7 @@ "title": "Erro de Login", "description": "Clique em SEGUINTE para tentar novamente." }, - "devices_filter": { + "homes_select": { "title": "Selecione a Casa e os Dispositivos", "description": "## Instruções de Utilização\r\n### Modo de Controlo\r\n- Automático: Quando existir um gateway central Xiaomi disponível na rede local, o Home Assistant dará prioridade ao envio de comandos de controlo através do gateway central, permitindo um controlo local. Caso não exista um gateway central na rede local, tentará enviar comandos através da funcionalidade de controlo LAN. Apenas se estas condições não forem cumpridas, os comandos serão enviados pela nuvem.\r\n- Nuvem: Todos os comandos de controlo são enviados através da nuvem.\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\n### Modo de sincronização do nome da divisão\r\nAo importar dispositivos da aplicação Xiaomi Home para o Home Assistant, a nomeação da área onde o dispositivo é adicionado segue as regras abaixo. Note que o processo de sincronização dos dispositivos não altera as definições de casa ou divisão na aplicação Xiaomi Home.\r\n- Não sincronizar: O dispositivo não será atribuído a qualquer área.\r\n- Outras opções: O dispositivo será adicionado a uma área cujo nome corresponde ao da casa e/ou divisão definida na aplicação Xiaomi Home.\r\n### Modo de depuração de ação\r\nPara as ações definidas no MIoT-Spec-V2 do dispositivo, será criada uma entidade do tipo texto juntamente com uma entidade de notificação, nas quais poderá enviar comandos de controlo ao dispositivo para fins de depuração.\r\n### Ocultar entidades não padrão\r\nOculta as entidades geradas a partir de instâncias não padrão do MIoT-Spec-V2, cujos nomes começam por \"*\".\r\n\r\n \r\n### Olá {nick_name}, selecione o modo de controlo da integração e a casa na qual deseja importar os dispositivos.", "data": { @@ -87,7 +87,7 @@ "nick_name": "Alcunha" } }, - "devices_filter": { + "homes_select": { "title": "Selecionar novamente a Casa e os Dispositivos", "description": "## Instruções de Utilização\r\n### Modo de Controlo\r\n- Automático: Quando houver um gateway central Xiaomi disponível na rede local, o Home Assistant priorizará o envio de comandos através dele para obter controlo local. Se não existir um gateway central, tentará enviar comandos através da função de controlo LAN da Xiaomi. Apenas se estas condições não forem satisfeitas, os comandos serão enviados pela nuvem.\r\n- Nuvem: Todos os comandos de controlo são enviados através da nuvem.\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\r\n \r\n### Olá {nick_name}, selecione o modo de controlo da integração e a casa da qual pretende importar dispositivos.", "data": { diff --git a/custom_components/xiaomi_home/translations/ru.json b/custom_components/xiaomi_home/translations/ru.json index 778206d..8418f93 100644 --- a/custom_components/xiaomi_home/translations/ru.json +++ b/custom_components/xiaomi_home/translations/ru.json @@ -22,7 +22,7 @@ "title": "Ошибка входа в систему", "description": "Нажмите кнопку «Далее», чтобы повторить попытку" }, - "devices_filter": { + "homes_select": { "title": "Выберите дом и устройства", "description": "## Инструкция по использованию\r\n### Режим управления\r\n- Авто: Когда в локальной сети доступен центральный шлюз Xiaomi, Home Assistant будет в первую очередь отправлять команды управления устройствами через центральный шлюз для достижения локализованного управления. Если в локальной сети нет центрального шлюза, он попытается отправить команды управления через протокол Xiaomi OT для достижения локализованного управления. Только если вышеуказанные условия локализованного управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: Команды управления отправляются только через облако.\r\n### Импорт домашнего устройства\r\nИнтеграция добавит устройства из выбранных домов.\r\n### Режим синхронизации имен комнат\r\nПри синхронизации устройств из приложения Xiaomi Home в Home Assistant имена комнат устройств в Home Assistant будут именоваться в соответствии с именами дома или комнаты в приложении Xiaomi Home.\r\n- Не синхронизировать: устройство не будет добавлено в любую область.\r\n- Другие параметры: область, в которую добавляется устройство, называется именем дома или комнаты в приложении Xiaomi Home.\r\n### Режим отладки Action\r\nДля методов, определенных в MIoT-Spec-V2, помимо создания уведомительной сущности будет создана сущность текстового поля ввода, которую можно использовать для отправки команд управления устройством во время отладки.\r\n### Скрыть нестандартные сущности\r\nСкрыть сущности, созданные нестандартными примерами MIoT-Spec-V2, имена которых начинаются с « * ».\r\n\r\n \r\n### {nick_name} Здравствуйте! Выберите режим управления интеграцией и дом, в котором находятся устройства, которые вы хотите добавить.", "data": { @@ -87,7 +87,7 @@ "nick_name": "Имя пользователя" } }, - "devices_filter": { + "homes_select": { "title": "Выберите дом и устройства", "description": "## Инструкция по использованию\r\n### Режим управления\r\n- Авто: Когда в локальной сети доступен центральный шлюз Xiaomi, Home Assistant будет в первую очередь отправлять команды управления устройствами через центральный шлюз для достижения локализованного управления. Если в локальной сети нет центрального шлюза, он попытается отправить команды управления через протокол Xiaomi OT для достижения локализованного управления. Только если вышеуказанные условия локализованного управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: Команды управления отправляются только через облако.\r\n### Импорт домашнего устройства\r\nИнтеграция добавит устройства из выбранных домов.\r\n \r\n### {nick_name} Здравствуйте! Выберите режим управления интеграцией и дом, в котором находятся устройства, которые вы хотите добавить.", "data": { diff --git a/custom_components/xiaomi_home/translations/zh-Hans.json b/custom_components/xiaomi_home/translations/zh-Hans.json index 8326832..37b905d 100644 --- a/custom_components/xiaomi_home/translations/zh-Hans.json +++ b/custom_components/xiaomi_home/translations/zh-Hans.json @@ -22,7 +22,7 @@ "title": "登录出现错误", "description": "点击“下一步”重试" }, - "devices_filter": { + "homes_select": { "title": "选择家庭与设备", "description": "## 使用介绍\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令,以实现本地化控制功能。本地局域网不存在中枢时,会尝试通过小米OT协议发送控制指令,以实现本地化控制功能。只有当上述本地化控制条件不满足时,设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### 导入设备的家庭\r\n集成将添加已选中家庭中的设备。\r\n### 房间名同步模式\r\n将设备从米家APP同步到 Home Assistant 时,设备在 Home Assistant 中所处区域的名称的命名方式将遵循以下规则。注意,设备同步过程不会改变米家APP中家庭和房间的设置。\r\n- 不同步:设备不会被添加至任何区域。\r\n- 其它选项:设备所添加到的区域以米家APP中的家庭或房间名称命名。\r\n### Action 调试模式\r\n对于设备 MIoT-Spec-V2 定义的方法,在生成通知实体之外,还会生成一个文本输入框实体,您可以在调试时用它向设备发送控制指令。\r\n### 隐藏非标准生成实体\r\n隐藏名称以“*”开头的非标准 MIoT-Spec-V2 实例生成的实体。\r\n\r\n \r\n### {nick_name} 您好!请选择集成控制模式以及您想要添加的设备所处的家庭。", "data": { @@ -87,7 +87,7 @@ "nick_name": "用户昵称" } }, - "devices_filter": { + "homes_select": { "title": "重新选择家庭与设备", "description": "## 使用介绍\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令,以实现本地化控制功能。本地局域网不存在中枢时,会尝试通过小米OT协议发送控制指令,以实现本地化控制功能。只有当上述本地化控制条件不满足时,设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### 导入设备的家庭\r\n集成将添加已选中家庭中的设备。\r\n \r\n### {nick_name} 您好!请选择集成控制模式以及您想要添加的设备所处的家庭。", "data": { diff --git a/custom_components/xiaomi_home/translations/zh-Hant.json b/custom_components/xiaomi_home/translations/zh-Hant.json index 04823c1..c8551a0 100644 --- a/custom_components/xiaomi_home/translations/zh-Hant.json +++ b/custom_components/xiaomi_home/translations/zh-Hant.json @@ -22,7 +22,7 @@ "title": "登錄出現錯誤", "description": "點擊“下一步”重試" }, - "devices_filter": { + "homes_select": { "title": "選擇家庭與設備", "description": "## 使用介紹\r\n### 控制模式\r\n- 自動:本地區域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。本地區域網不存在中樞時,會嘗試通過小米OT協議發送控制指令,以實現本地化控制功能。只有當上述本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令僅通過雲端發送。\r\n### 導入設備的家庭\r\n集成將添加已選中家庭中的設備。\r\n### 房間名同步模式\r\n將設備從米家APP同步到 Home Assistant 時,設備在 Home Assistant 中所處區域的名稱的命名方式將遵循以下規則。注意,設備同步過程不會改變米家APP中家庭和房間的設置。\r\n- 不同步:設備不會被添加至任何區域。\r\n- 其它選項:設備所添加到的區域以米家APP中的家庭或房間名稱命名。\r\n### Action 調試模式\r\n對於設備 MIoT-Spec-V2 定義的方法,在生成通知實體之外,還會生成一個文本輸入框實體,您可以在調試時用它向設備發送控制指令。\r\n### 隱藏非標準生成實體\r\n隱藏名稱以“*”開頭的非標準 MIoT-Spec-V2 實例生成的實體。\r\n\r\n \r\n### {nick_name} 您好!請選擇集成控制模式以及您想要添加的設備所處的家庭。", "data": { @@ -87,7 +87,7 @@ "nick_name": "用戶暱稱" } }, - "devices_filter": { + "homes_select": { "title": "重新選擇家庭與設備", "description": "\r\n## 使用介紹\r\n### 控制模式\r\n- 自動:本地局域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。只有當本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令強制通過雲端發送。\r\n### 導入設備的家庭\r\n集成將添加已選中家庭中的設備。\r\n \r\n### {nick_name} 您好!請選擇集成控制模式以及您想要添加的設備所處的家庭。", "data": { From afef70983953c3caf597747ff80b1b824b673c97 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Sat, 21 Dec 2024 11:41:24 +0800 Subject: [PATCH 08/17] doc: update changelog and version to v0.1.4b0 (#312) --- CHANGELOG.md | 7 +++++++ custom_components/xiaomi_home/manifest.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1fdf52..50dfdee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## v0.1.4b0 +### Added +### Changed +### Fixed +- Fix miot cloud token refresh logic. [#307](https://github.com/XiaoMi/ha_xiaomi_home/pull/307) +- Fix lan ctrl filter logic. [#303](https://github.com/XiaoMi/ha_xiaomi_home/pull/303) + ## v0.1.3 ### Added ### Changed diff --git a/custom_components/xiaomi_home/manifest.json b/custom_components/xiaomi_home/manifest.json index 19afdc2..b5994a7 100644 --- a/custom_components/xiaomi_home/manifest.json +++ b/custom_components/xiaomi_home/manifest.json @@ -26,7 +26,7 @@ "psutil", "aiohttp[speedups]" ], - "version": "v0.1.3", + "version": "v0.1.4b0", "zeroconf": [ "_miot-central._tcp.local." ] From c1867e2baf4946f4666b7e4057509d2f615d3012 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Sun, 22 Dec 2024 10:46:58 +0800 Subject: [PATCH 09/17] fix: fix type error, wrong use of any and Any (#338) * fix: fix type error, wrong use of any and Any * fix: wrong use of session close * fix: fix test_lan type error * fix: remove __del__ * feat: oauth, http add deinit_async --- custom_components/xiaomi_home/climate.py | 4 +- custom_components/xiaomi_home/config_flow.py | 20 ++- custom_components/xiaomi_home/event.py | 3 +- custom_components/xiaomi_home/fan.py | 2 +- custom_components/xiaomi_home/humidifier.py | 4 +- custom_components/xiaomi_home/light.py | 4 +- custom_components/xiaomi_home/miot/common.py | 8 +- .../xiaomi_home/miot/miot_client.py | 81 +++++++++--- .../xiaomi_home/miot/miot_cloud.py | 22 +-- .../xiaomi_home/miot/miot_device.py | 60 ++++----- .../xiaomi_home/miot/miot_error.py | 5 +- custom_components/xiaomi_home/miot/miot_ev.py | 38 +++--- .../xiaomi_home/miot/miot_mips.py | 125 +++++++++--------- .../xiaomi_home/miot/miot_spec.py | 10 +- .../xiaomi_home/miot/miot_storage.py | 12 +- custom_components/xiaomi_home/sensor.py | 3 +- custom_components/xiaomi_home/water_heater.py | 6 +- test/test_lan.py | 3 +- 18 files changed, 237 insertions(+), 173 deletions(-) diff --git a/custom_components/xiaomi_home/climate.py b/custom_components/xiaomi_home/climate.py index 106abb9..bd4cfe3 100644 --- a/custom_components/xiaomi_home/climate.py +++ b/custom_components/xiaomi_home/climate.py @@ -47,7 +47,7 @@ """ from __future__ import annotations import logging -from typing import Optional +from typing import Any, Optional from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -415,7 +415,7 @@ def swing_mode(self) -> Optional[str]: return SWING_OFF return None - def __ac_state_changed(self, prop: MIoTSpecProperty, value: any) -> None: + def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None: del prop if not isinstance(value, str): _LOGGER.error( diff --git a/custom_components/xiaomi_home/config_flow.py b/custom_components/xiaomi_home/config_flow.py index b9f3776..36f9815 100644 --- a/custom_components/xiaomi_home/config_flow.py +++ b/custom_components/xiaomi_home/config_flow.py @@ -315,6 +315,9 @@ async def async_step_oauth(self, user_input=None): _LOGGER.error('task_oauth exception, %s', error) self._config_error_reason = str(error) return self.async_show_progress_done(next_step_id='oauth_error') + if self._miot_oauth: + await self._miot_oauth.deinit_async() + self._miot_oauth = None return self.async_show_progress_done(next_step_id='homes_select') return self.async_show_progress( step_id='oauth', @@ -336,10 +339,16 @@ async def __check_oauth_async(self) -> None: try: auth_info = await self._miot_oauth.get_access_token_async( code=oauth_code) - self._miot_http = MIoTHttpClient( - cloud_server=self._cloud_server, - client_id=OAUTH2_CLIENT_ID, - access_token=auth_info['access_token']) + if not self._miot_http: + self._miot_http = MIoTHttpClient( + cloud_server=self._cloud_server, + client_id=OAUTH2_CLIENT_ID, + access_token=auth_info['access_token']) + else: + self._miot_http.update_http_header( + cloud_server=self._cloud_server, + client_id=OAUTH2_CLIENT_ID, + access_token=auth_info['access_token']) self._auth_info = auth_info # Gen uuid self._uuid = hashlib.sha256( @@ -449,6 +458,9 @@ async def __check_oauth_async(self) -> None: # Auth success, unregister oauth webhook webhook_async_unregister(self.hass, webhook_id=self._virtual_did) + if self._miot_http: + await self._miot_http.deinit_async() + self._miot_http = None _LOGGER.info( '__check_oauth_async, webhook.async_unregister: %s', self._virtual_did) diff --git a/custom_components/xiaomi_home/event.py b/custom_components/xiaomi_home/event.py index 9b12526..7892290 100644 --- a/custom_components/xiaomi_home/event.py +++ b/custom_components/xiaomi_home/event.py @@ -46,6 +46,7 @@ Event entities for Xiaomi Home. """ from __future__ import annotations +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -84,6 +85,6 @@ def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecEvent) -> None: # Set device_class self._attr_device_class = spec.device_class - def on_event_occurred(self, name: str, arguments: list[dict[int, any]]): + def on_event_occurred(self, name: str, arguments: list[dict[int, Any]]): """An event is occurred.""" self._trigger_event(event_type=name, event_attributes=arguments) diff --git a/custom_components/xiaomi_home/fan.py b/custom_components/xiaomi_home/fan.py index adae99a..42947ce 100644 --- a/custom_components/xiaomi_home/fan.py +++ b/custom_components/xiaomi_home/fan.py @@ -93,7 +93,7 @@ class Fan(MIoTServiceEntity, FanEntity): _speed_min: Optional[int] _speed_max: Optional[int] _speed_step: Optional[int] - _mode_list: Optional[dict[any, any]] + _mode_list: Optional[dict[Any, Any]] def __init__( self, miot_device: MIoTDevice, entity_data: MIoTEntityData diff --git a/custom_components/xiaomi_home/humidifier.py b/custom_components/xiaomi_home/humidifier.py index 62cd9cd..9739da4 100644 --- a/custom_components/xiaomi_home/humidifier.py +++ b/custom_components/xiaomi_home/humidifier.py @@ -47,7 +47,7 @@ """ from __future__ import annotations import logging -from typing import Optional +from typing import Any, Optional from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -97,7 +97,7 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity): _prop_target_humidity: Optional[MIoTSpecProperty] _prop_humidity: Optional[MIoTSpecProperty] - _mode_list: dict[any, any] + _mode_list: dict[Any, Any] def __init__( self, miot_device: MIoTDevice, entity_data: MIoTEntityData diff --git a/custom_components/xiaomi_home/light.py b/custom_components/xiaomi_home/light.py index 49229f5..bcfef6c 100644 --- a/custom_components/xiaomi_home/light.py +++ b/custom_components/xiaomi_home/light.py @@ -47,7 +47,7 @@ """ from __future__ import annotations import logging -from typing import Optional +from typing import Any, Optional from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -102,7 +102,7 @@ class Light(MIoTServiceEntity, LightEntity): _prop_mode: Optional[MIoTSpecProperty] _brightness_scale: Optional[tuple[int, int]] - _mode_list: Optional[dict[any, any]] + _mode_list: Optional[dict[Any, Any]] def __init__( self, miot_device: MIoTDevice, entity_data: MIoTEntityData diff --git a/custom_components/xiaomi_home/miot/common.py b/custom_components/xiaomi_home/miot/common.py index 0b03f96..714f158 100644 --- a/custom_components/xiaomi_home/miot/common.py +++ b/custom_components/xiaomi_home/miot/common.py @@ -48,9 +48,9 @@ import json from os import path import random -from typing import Optional +from typing import Any, Optional import hashlib -from paho.mqtt.client import MQTTMatcher +from paho.mqtt.matcher import MQTTMatcher import yaml MIOT_ROOT_PATH: str = path.dirname(path.abspath(__file__)) @@ -87,7 +87,7 @@ def randomize_int(value: int, ratio: float) -> int: class MIoTMatcher(MQTTMatcher): """MIoT Pub/Sub topic matcher.""" - def iter_all_nodes(self) -> any: + def iter_all_nodes(self) -> Any: """Return an iterator on all nodes with their paths and contents.""" def rec(node, path_): # pylint: disable=protected-access @@ -97,7 +97,7 @@ def rec(node, path_): yield from rec(child, path_ + [part]) return rec(self._root, []) - def get(self, topic: str) -> Optional[any]: + def get(self, topic: str) -> Optional[Any]: try: return self[topic] except KeyError: diff --git a/custom_components/xiaomi_home/miot/miot_client.py b/custom_components/xiaomi_home/miot/miot_client.py index 7423e20..da8e2b6 100644 --- a/custom_components/xiaomi_home/miot/miot_client.py +++ b/custom_components/xiaomi_home/miot/miot_client.py @@ -1,7 +1,52 @@ # -*- coding: utf-8 -*- -"""MIoT client instance.""" +""" +Copyright (C) 2024 Xiaomi Corporation. + +The ownership and intellectual property rights of Xiaomi Home Assistant +Integration and related Xiaomi cloud service API interface provided under this +license, including source code and object code (collectively, "Licensed Work"), +are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi +hereby grants you a personal, limited, non-exclusive, non-transferable, +non-sublicensable, and royalty-free license to reproduce, use, modify, and +distribute the Licensed Work only for your use of Home Assistant for +non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize +you to use the Licensed Work for any other purpose, including but not limited +to use Licensed Work to develop applications (APP), Web services, and other +forms of software. + +You may reproduce and distribute copies of the Licensed Work, with or without +modifications, whether in source or object form, provided that you must give +any other recipients of the Licensed Work a copy of this License and retain all +copyright and disclaimers. + +Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied, including, without +limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR +OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or +FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible +for any direct, indirect, special, incidental, or consequential damages or +losses arising from the use or inability to use the Licensed Work. + +Xiaomi reserves all rights not expressly granted to you in this License. +Except for the rights expressly granted by Xiaomi under this License, Xiaomi +does not authorize you in any form to use the trademarks, copyrights, or other +forms of intellectual property rights of Xiaomi and its affiliates, including, +without limitation, without obtaining other written permission from Xiaomi, you +shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that +may make the public associate with Xiaomi in any form to publicize or promote +the software or hardware devices that use the Licensed Work. + +Xiaomi has the right to immediately terminate all your authorization under this +License in the event: +1. You assert patent invalidation, litigation, or other claims against patents +or other intellectual property rights of Xiaomi or its affiliates; or, +2. You make, have made, manufacture, sell, or offer to sell products that knock +off Xiaomi or its affiliates' products. + +MIoT client instance. +""" from copy import deepcopy -from typing import Callable, Optional, final +from typing import Any, Callable, Optional, final import asyncio import json import logging @@ -36,9 +81,9 @@ @dataclass class MIoTClientSub: """MIoT client subscription.""" - topic: str = None - handler: Callable[[dict, any], None] = None - handler_ctx: any = None + topic: Optional[str] + handler: Callable[[dict, Any], None] + handler_ctx: Any = None def __str__(self) -> str: return f'{self.topic}, {id(self.handler)}, {id(self.handler_ctx)}' @@ -345,6 +390,8 @@ async def deinit_async(self) -> None: if self._show_devices_changed_notify_timer: self._show_devices_changed_notify_timer.cancel() self._show_devices_changed_notify_timer = None + await self._oauth.deinit_async() + await self._http.deinit_async() # Remove notify self._persistence_notify( self.__gen_notify_key('dev_list_changed'), None, None) @@ -526,7 +573,7 @@ async def refresh_user_cert_async(self) -> bool: return False async def set_prop_async( - self, did: str, siid: int, piid: int, value: any + self, did: str, siid: int, piid: int, value: Any ) -> bool: if did not in self._device_list_cache: raise MIoTClientError(f'did not exist, {did}') @@ -612,7 +659,7 @@ def request_refresh_prop( 0.2, lambda: self._main_loop.create_task( self.__refresh_props_handler())) - async def get_prop_async(self, did: str, siid: int, piid: int) -> any: + async def get_prop_async(self, did: str, siid: int, piid: int) -> Any: if did not in self._device_list_cache: raise MIoTClientError(f'did not exist, {did}') @@ -717,8 +764,8 @@ async def action_async( return None def sub_prop( - self, did: str, handler: Callable[[dict, any], None], - siid: int = None, piid: int = None, handler_ctx: any = None + self, did: str, handler: Callable[[dict, Any], None], + siid: int = None, piid: int = None, handler_ctx: Any = None ) -> bool: if did not in self._device_list_cache: raise MIoTClientError(f'did not exist, {did}') @@ -741,8 +788,8 @@ def unsub_prop(self, did: str, siid: int = None, piid: int = None) -> bool: return True def sub_event( - self, did: str, handler: Callable[[dict, any], None], - siid: int = None, eiid: int = None, handler_ctx: any = None + self, did: str, handler: Callable[[dict, Any], None], + siid: int = None, eiid: int = None, handler_ctx: Any = None ) -> bool: if did not in self._device_list_cache: raise MIoTClientError(f'did not exist, {did}') @@ -764,8 +811,8 @@ def unsub_event(self, did: str, siid: int = None, eiid: int = None) -> bool: return True def sub_device_state( - self, did: str, handler: Callable[[str, MIoTDeviceState, any], None], - handler_ctx: any = None + self, did: str, handler: Callable[[str, MIoTDeviceState, Any], None], + handler_ctx: Any = None ) -> bool: """Call callback handler in main loop""" if did not in self._device_list_cache: @@ -1060,7 +1107,7 @@ async def __on_miot_lan_state_change(self, state: bool) -> None: @final def __on_cloud_device_state_changed( - self, did: str, state: MIoTDeviceState, ctx: any + self, did: str, state: MIoTDeviceState, ctx: Any ) -> None: _LOGGER.info('cloud device state changed, %s, %s', did, state) cloud_device = self._device_list_cloud.get(did, None) @@ -1110,7 +1157,7 @@ async def __on_gw_device_list_changed( @final async def __on_lan_device_state_changed( - self, did: str, state: dict, ctx: any + self, did: str, state: dict, ctx: Any ) -> None: _LOGGER.info('lan device state changed, %s, %s', did, state) lan_state_new: bool = state.get('online', False) @@ -1146,7 +1193,7 @@ async def __on_lan_device_state_changed( self.__request_show_devices_changed_notify() @final - def __on_prop_msg(self, params: dict, ctx: any) -> None: + def __on_prop_msg(self, params: dict, ctx: Any) -> None: """params MUST contain did, siid, piid, value""" # BLE device has no online/offline msg try: @@ -1158,7 +1205,7 @@ def __on_prop_msg(self, params: dict, ctx: any) -> None: _LOGGER.error('on prop msg error, %s, %s', params, err) @final - def __on_event_msg(self, params: dict, ctx: any) -> None: + def __on_event_msg(self, params: dict, ctx: Any) -> None: try: subs: list[MIoTClientSub] = list(self._sub_tree.iter_match( f'{params["did"]}/e/{params["siid"]}/{params["eiid"]}')) diff --git a/custom_components/xiaomi_home/miot/miot_cloud.py b/custom_components/xiaomi_home/miot/miot_cloud.py index 2d5c49c..036ea57 100644 --- a/custom_components/xiaomi_home/miot/miot_cloud.py +++ b/custom_components/xiaomi_home/miot/miot_cloud.py @@ -51,7 +51,7 @@ import logging import re import time -from typing import Optional +from typing import Any, Optional from urllib.parse import urlencode import aiohttp @@ -94,10 +94,11 @@ def __init__( self._oauth_host = DEFAULT_OAUTH2_API_HOST else: self._oauth_host = f'{cloud_server}.{DEFAULT_OAUTH2_API_HOST}' - self._session = aiohttp.ClientSession() + self._session = aiohttp.ClientSession(loop=self._main_loop) - def __del__(self): - self._session.close() + async def deinit_async(self) -> None: + if self._session and not self._session.closed: + await self._session.close() def set_redirect_url(self, redirect_url: str) -> None: if not isinstance(redirect_url, str) or redirect_url.strip() == '': @@ -250,10 +251,11 @@ def __init__( cloud_server=cloud_server, client_id=client_id, access_token=access_token) - self._session = aiohttp.ClientSession() + self._session = aiohttp.ClientSession(loop=self._main_loop) - def __del__(self): - self._session.close() + async def deinit_async(self) -> None: + if self._session and not self._session.closed: + await self._session.close() def update_http_header( self, cloud_server: Optional[str] = None, @@ -581,7 +583,7 @@ async def get_devices_async( self, home_ids: list[str] = None ) -> dict[str, dict]: homeinfos = await self.get_homeinfos_async() - homes: dict[str, dict[str, any]] = {} + homes: dict[str, dict[str, Any]] = {} devices: dict[str, dict] = {} for device_type in ['home_list', 'share_home_list']: homes.setdefault(device_type, {}) @@ -661,7 +663,7 @@ async def get_props_async(self, params: list) -> list: raise MIoTHttpError('invalid response result') return res_obj['result'] - async def __get_prop_async(self, did: str, siid: int, piid: int) -> any: + async def __get_prop_async(self, did: str, siid: int, piid: int) -> Any: results = await self.get_props_async( params=[{'did': did, 'siid': siid, 'piid': piid}]) if not results: @@ -722,7 +724,7 @@ async def __get_prop_handler(self) -> bool: async def get_prop_async( self, did: str, siid: int, piid: int, immediately: bool = False - ) -> any: + ) -> Any: if immediately: return await self.__get_prop_async(did, siid, piid) key: str = f'{did}.{siid}.{piid}' diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index fa48345..6144d57 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -47,7 +47,7 @@ """ import asyncio from abc import abstractmethod -from typing import Callable, Optional +from typing import Any, Callable, Optional import logging from homeassistant.helpers.entity import Entity @@ -103,7 +103,7 @@ class MIoTEntityData: """MIoT Entity Data.""" platform: str - device_class: any + device_class: Any spec: MIoTSpecInstance | MIoTSpecService props: set[MIoTSpecProperty] @@ -243,8 +243,8 @@ def unsub_device_state(self, key: str) -> bool: return True def sub_property( - self, handler: Callable[[dict, any], None], siid: int = None, - piid: int = None, handler_ctx: any = None + self, handler: Callable[[dict, Any], None], siid: int = None, + piid: int = None, handler_ctx: Any = None ) -> bool: return self.miot_client.sub_prop( did=self._did, handler=handler, siid=siid, piid=piid, @@ -254,8 +254,8 @@ def unsub_property(self, siid: int = None, piid: int = None) -> bool: return self.miot_client.unsub_prop(did=self._did, siid=siid, piid=piid) def sub_event( - self, handler: Callable[[dict, any], None], siid: int = None, - eiid: int = None, handler_ctx: any = None + self, handler: Callable[[dict, Any], None], siid: int = None, + eiid: int = None, handler_ctx: Any = None ) -> bool: return self.miot_client.sub_event( did=self._did, handler=handler, siid=siid, eiid=eiid, @@ -688,7 +688,7 @@ def icon_convert(self, spec_unit: str) -> Optional[str]: return None def __on_device_state_changed( - self, did: str, state: MIoTDeviceState, ctx: any + self, did: str, state: MIoTDeviceState, ctx: Any ) -> None: self._online = state for key, handler in self._device_state_sub_list.items(): @@ -704,11 +704,11 @@ class MIoTServiceEntity(Entity): entity_data: MIoTEntityData _main_loop: asyncio.AbstractEventLoop - _prop_value_map: dict[MIoTSpecProperty, any] + _prop_value_map: dict[MIoTSpecProperty, Any] _event_occurred_handler: Callable[[MIoTSpecEvent, dict], None] _prop_changed_subs: dict[ - MIoTSpecProperty, Callable[[MIoTSpecProperty, any], None]] + MIoTSpecProperty, Callable[[MIoTSpecProperty, Any], None]] _pending_write_ha_state_timer: Optional[asyncio.TimerHandle] @@ -759,7 +759,7 @@ def event_occurred_handler(self, func) -> None: def sub_prop_changed( self, prop: MIoTSpecProperty, - handler: Callable[[MIoTSpecProperty, any], None] + handler: Callable[[MIoTSpecProperty, Any], None] ) -> None: if not prop or not handler: _LOGGER.error( @@ -816,13 +816,13 @@ async def async_will_remove_from_hass(self) -> None: self.miot_device.unsub_event( siid=event.service.iid, eiid=event.iid) - def get_map_description(self, map_: dict[int, any], key: int) -> any: + def get_map_description(self, map_: dict[int, Any], key: int) -> Any: if map_ is None: return None return map_.get(key, None) def get_map_value( - self, map_: dict[int, any], description: any + self, map_: dict[int, Any], description: Any ) -> Optional[int]: if map_ is None: return None @@ -831,7 +831,7 @@ def get_map_value( return key return None - def get_prop_value(self, prop: MIoTSpecProperty) -> any: + def get_prop_value(self, prop: MIoTSpecProperty) -> Any: if not prop: _LOGGER.error( 'get_prop_value error, property is None, %s, %s', @@ -839,7 +839,7 @@ def get_prop_value(self, prop: MIoTSpecProperty) -> any: return None return self._prop_value_map.get(prop, None) - def set_prop_value(self, prop: MIoTSpecProperty, value: any) -> None: + def set_prop_value(self, prop: MIoTSpecProperty, value: Any) -> None: if not prop: _LOGGER.error( 'set_prop_value error, property is None, %s, %s', @@ -848,7 +848,7 @@ def set_prop_value(self, prop: MIoTSpecProperty, value: any) -> None: self._prop_value_map[prop] = value async def set_property_async( - self, prop: MIoTSpecProperty, value: any, update: bool = True + self, prop: MIoTSpecProperty, value: Any, update: bool = True ) -> bool: value = prop.value_format(value) if not prop: @@ -875,7 +875,7 @@ async def set_property_async( self.async_write_ha_state() return True - async def get_property_async(self, prop: MIoTSpecProperty) -> any: + async def get_property_async(self, prop: MIoTSpecProperty) -> Any: if not prop: _LOGGER.error( 'get property failed, property is None, %s, %s', @@ -914,7 +914,7 @@ async def action_async( f'{e}, {self.entity_id}, {self.name}, {action.name}') from e return True - def __on_properties_changed(self, params: dict, ctx: any) -> None: + def __on_properties_changed(self, params: dict, ctx: Any) -> None: _LOGGER.debug('properties changed, %s', params) for prop in self.entity_data.props: if ( @@ -922,7 +922,7 @@ def __on_properties_changed(self, params: dict, ctx: any) -> None: or prop.service.iid != params['siid'] ): continue - value: any = prop.value_format(params['value']) + value: Any = prop.value_format(params['value']) self._prop_value_map[prop] = value if prop in self._prop_changed_subs: self._prop_changed_subs[prop](prop, value) @@ -930,7 +930,7 @@ def __on_properties_changed(self, params: dict, ctx: any) -> None: if not self._pending_write_ha_state_timer: self.async_write_ha_state() - def __on_event_occurred(self, params: dict, ctx: any) -> None: + def __on_event_occurred(self, params: dict, ctx: Any) -> None: _LOGGER.debug('event occurred, %s', params) if self._event_occurred_handler is None: return @@ -988,9 +988,9 @@ class MIoTPropertyEntity(Entity): _main_loop: asyncio.AbstractEventLoop # {'min':int, 'max':int, 'step': int} _value_range: dict[str, int] - # {any: any} - _value_list: dict[any, any] - _value: any + # {Any: Any} + _value_list: dict[Any, Any] + _value: Any _pending_write_ha_state_timer: Optional[asyncio.TimerHandle] @@ -1054,12 +1054,12 @@ async def async_will_remove_from_hass(self) -> None: self.miot_device.unsub_property( siid=self.service.iid, piid=self.spec.iid) - def get_vlist_description(self, value: any) -> str: + def get_vlist_description(self, value: Any) -> str: if not self._value_list: return None return self._value_list.get(value, None) - def get_vlist_value(self, description: str) -> any: + def get_vlist_value(self, description: str) -> Any: if not self._value_list: return None for key, value in self._value_list.items(): @@ -1067,7 +1067,7 @@ def get_vlist_value(self, description: str) -> any: return key return None - async def set_property_async(self, value: any) -> bool: + async def set_property_async(self, value: Any) -> bool: if not self.spec.writable: raise RuntimeError( f'set property failed, not writable, ' @@ -1084,7 +1084,7 @@ async def set_property_async(self, value: any) -> bool: self.async_write_ha_state() return True - async def get_property_async(self) -> any: + async def get_property_async(self) -> Any: if not self.spec.readable: _LOGGER.error( 'get property failed, not readable, %s, %s', @@ -1095,7 +1095,7 @@ async def get_property_async(self) -> any: did=self.miot_device.did, siid=self.spec.service.iid, piid=self.spec.iid)) - def __on_value_changed(self, params: dict, ctx: any) -> None: + def __on_value_changed(self, params: dict, ctx: Any) -> None: _LOGGER.debug('property changed, %s', params) self._value = self.spec.value_format(params['value']) if not self._pending_write_ha_state_timer: @@ -1135,7 +1135,7 @@ class MIoTEventEntity(Entity): service: MIoTSpecService _main_loop: asyncio.AbstractEventLoop - _value: any + _value: Any _attr_event_types: list[str] _arguments_map: dict[int, str] @@ -1192,10 +1192,10 @@ async def async_will_remove_from_hass(self) -> None: @abstractmethod def on_event_occurred( - self, name: str, arguments: list[dict[int, any]] + self, name: str, arguments: list[dict[int, Any]] ): ... - def __on_event_occurred(self, params: dict, ctx: any) -> None: + def __on_event_occurred(self, params: dict, ctx: Any) -> None: _LOGGER.debug('event occurred, %s', params) trans_arg = {} try: diff --git a/custom_components/xiaomi_home/miot/miot_error.py b/custom_components/xiaomi_home/miot/miot_error.py index 1004a15..a85bfd4 100644 --- a/custom_components/xiaomi_home/miot/miot_error.py +++ b/custom_components/xiaomi_home/miot/miot_error.py @@ -46,6 +46,7 @@ MIoT error code and exception. """ from enum import Enum +from typing import Any class MIoTErrorCode(Enum): @@ -78,10 +79,10 @@ class MIoTErrorCode(Enum): class MIoTError(Exception): """MIoT error.""" code: MIoTErrorCode - message: any + message: Any def __init__( - self, message: any, code: MIoTErrorCode = MIoTErrorCode.CODE_UNKNOWN + self, message: Any, code: MIoTErrorCode = MIoTErrorCode.CODE_UNKNOWN ) -> None: self.message = message self.code = code diff --git a/custom_components/xiaomi_home/miot/miot_ev.py b/custom_components/xiaomi_home/miot/miot_ev.py index be4e684..c0cc97f 100644 --- a/custom_components/xiaomi_home/miot/miot_ev.py +++ b/custom_components/xiaomi_home/miot/miot_ev.py @@ -49,7 +49,7 @@ import heapq import time import traceback -from typing import Callable, TypeVar +from typing import Any, Callable, TypeVar import logging import threading @@ -64,17 +64,17 @@ class MIoTFdHandler: """File descriptor handler.""" fd: int - read_handler: Callable[[any], None] - read_handler_ctx: any - write_handler: Callable[[any], None] - write_handler_ctx: any + read_handler: Callable[[Any], None] + read_handler_ctx: Any + write_handler: Callable[[Any], None] + write_handler_ctx: Any def __init__( self, fd: int, - read_handler: Callable[[any], None] = None, - read_handler_ctx: any = None, - write_handler: Callable[[any], None] = None, - write_handler_ctx: any = None + read_handler: Callable[[Any], None] = None, + read_handler_ctx: Any = None, + write_handler: Callable[[Any], None] = None, + write_handler_ctx: Any = None ) -> None: self.fd = fd self.read_handler = read_handler @@ -87,13 +87,13 @@ class MIoTTimeout: """Timeout handler.""" key: TimeoutHandle target: int - handler: Callable[[any], None] - handler_ctx: any + handler: Callable[[Any], None] + handler_ctx: Any def __init__( self, key: str = None, target: int = None, - handler: Callable[[any], None] = None, - handler_ctx: any = None + handler: Callable[[Any], None] = None, + handler_ctx: Any = None ) -> None: self.key = key self.target = target @@ -185,8 +185,8 @@ def loop_stop(self) -> None: self._timer_handlers = {} def set_timeout( - self, timeout_ms: int, handler: Callable[[any], None], - handler_ctx: any = None + self, timeout_ms: int, handler: Callable[[Any], None], + handler_ctx: Any = None ) -> TimeoutHandle: """Set a timer.""" if timeout_ms is None or handler is None: @@ -211,7 +211,7 @@ def clear_timeout(self, timer_key: TimeoutHandle) -> None: heapq.heapify(self._timer_heap) def set_read_handler( - self, fd: int, handler: Callable[[any], None], handler_ctx: any = None + self, fd: int, handler: Callable[[Any], None], handler_ctx: Any = None ) -> bool: """Set a read handler for a file descriptor. @@ -222,7 +222,7 @@ def set_read_handler( fd, is_read=True, handler=handler, handler_ctx=handler_ctx) def set_write_handler( - self, fd: int, handler: Callable[[any], None], handler_ctx: any = None + self, fd: int, handler: Callable[[Any], None], handler_ctx: Any = None ) -> bool: """Set a write handler for a file descriptor. @@ -233,8 +233,8 @@ def set_write_handler( fd, is_read=False, handler=handler, handler_ctx=handler_ctx) def __set_handler( - self, fd, is_read: bool, handler: Callable[[any], None], - handler_ctx: any = None + self, fd, is_read: bool, handler: Callable[[Any], None], + handler_ctx: Any = None ) -> bool: """Set a handler.""" if fd is None: diff --git a/custom_components/xiaomi_home/miot/miot_mips.py b/custom_components/xiaomi_home/miot/miot_mips.py index cbe41cf..6c6b358 100644 --- a/custom_components/xiaomi_home/miot/miot_mips.py +++ b/custom_components/xiaomi_home/miot/miot_mips.py @@ -58,7 +58,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from enum import Enum, auto -from typing import Callable, Optional, final +from typing import Any, Callable, Optional, final from paho.mqtt.client import ( MQTT_ERR_SUCCESS, @@ -173,9 +173,9 @@ class MipsCmdType(Enum): class MipsCmd: """MIoT Pub/Sub command.""" type_: MipsCmdType - data: any + data: Any - def __init__(self, type_: MipsCmdType, data: any) -> None: + def __init__(self, type_: MipsCmdType, data: Any) -> None: self.type_ = type_ self.data = data @@ -184,8 +184,8 @@ def __init__(self, type_: MipsCmdType, data: any) -> None: class MipsRequest: """MIoT Pub/Sub request.""" mid: int = None - on_reply: Callable[[str, any], None] = None - on_reply_ctx: any = None + on_reply: Callable[[str, Any], None] = None + on_reply_ctx: Any = None timer: TimeoutHandle = None @@ -194,8 +194,8 @@ class MipsRequestData: """MIoT Pub/Sub request data.""" topic: str = None payload: str = None - on_reply: Callable[[str, any], None] = None - on_reply_ctx: any = None + on_reply: Callable[[str, Any], None] = None + on_reply_ctx: Any = None timeout_ms: int = None @@ -223,8 +223,8 @@ class MipsApi: param2: payload param3: handler_ctx """ - handler: Callable[[MipsIncomingApiCall, str, any], None] = None - handler_ctx: any = None + handler: Callable[[MipsIncomingApiCall, str, Any], None] = None + handler_ctx: Any = None class MipsRegApi(MipsApi): @@ -247,8 +247,8 @@ class MipsBroadcast: param 2: msg payload param 3: handle_ctx """ - handler: Callable[[str, str, any], None] = None - handler_ctx: any = None + handler: Callable[[str, str, Any], None] = None + handler_ctx: Any = None def __str__(self) -> str: return f'{self.topic}, {id(self.handler)}, {id(self.handler_ctx)}' @@ -265,7 +265,6 @@ class MipsState: """ str: key bool: mips connect state - any: ctx """ handler: Callable[[str, bool], asyncio.Future] = None @@ -288,10 +287,10 @@ class MipsDeviceState: """handler str: did MIoTDeviceState: online/offline/disable - any: ctx + Any: ctx """ - handler: Callable[[str, MIoTDeviceState, any], None] = None - handler_ctx: any = None + handler: Callable[[str, MIoTDeviceState, Any], None] = None + handler_ctx: Any = None class MipsRegDeviceState(MipsDeviceState): @@ -512,8 +511,8 @@ def unsub_mips_state(self, key: str) -> bool: @final def mev_set_timeout( - self, timeout_ms: int, handler: Callable[[any], None], - handler_ctx: any = None + self, timeout_ms: int, handler: Callable[[Any], None], + handler_ctx: Any = None ) -> Optional[TimeoutHandle]: """set timeout. NOTICE: Internal function, only mips threads are allowed to call @@ -534,7 +533,7 @@ def mev_clear_timeout(self, handle: TimeoutHandle) -> None: @final def mev_set_read_handler( - self, fd: int, handler: Callable[[any], None], handler_ctx: any + self, fd: int, handler: Callable[[Any], None], handler_ctx: Any ) -> bool: """set read handler. NOTICE: Internal function, only mips threads are allowed to call @@ -546,7 +545,7 @@ def mev_set_read_handler( @final def mev_set_write_handler( - self, fd: int, handler: Callable[[any], None], handler_ctx: any + self, fd: int, handler: Callable[[Any], None], handler_ctx: Any ) -> bool: """set write handler. NOTICE: Internal function, only mips threads are allowed to call @@ -604,8 +603,8 @@ def on_mips_disconnect(self, handler: Callable[[int, dict], None]) -> None: @abstractmethod def sub_prop( - self, did: str, handler: Callable[[dict, any], None], - siid: int = None, piid: int = None, handler_ctx: any = None + self, did: str, handler: Callable[[dict, Any], None], + siid: int = None, piid: int = None, handler_ctx: Any = None ) -> bool: ... @abstractmethod @@ -615,8 +614,8 @@ def unsub_prop( @abstractmethod def sub_event( - self, did: str, handler: Callable[[dict, any], None], - siid: int = None, eiid: int = None, handler_ctx: any = None + self, did: str, handler: Callable[[dict, Any], None], + siid: int = None, eiid: int = None, handler_ctx: Any = None ) -> bool: ... @abstractmethod @@ -632,11 +631,11 @@ async def get_dev_list_async( @abstractmethod async def get_prop_async( self, did: str, siid: int, piid: int, timeout_ms: int = 10000 - ) -> any: ... + ) -> Any: ... @abstractmethod async def set_prop_async( - self, did: str, siid: int, piid: int, value: any, + self, did: str, siid: int, piid: int, value: Any, timeout_ms: int = 10000 ) -> bool: ... @@ -709,7 +708,7 @@ def _mips_publish_internal( return False @final - def _mips_send_cmd(self, type_: MipsCmdType, data: any) -> bool: + def _mips_send_cmd(self, type_: MipsCmdType, data: Any) -> bool: if self._mips_queue is None or self._cmd_event_fd is None: raise MIoTMipsError('send mips cmd disable') # Put data to queue @@ -723,7 +722,7 @@ def __thread_check(self) -> None: if threading.current_thread() is not self._mips_thread: raise MIoTMipsError('illegal call') - def __mips_cmd_read_handler(self, ctx: any) -> None: + def __mips_cmd_read_handler(self, ctx: Any) -> None: fd_value = os.eventfd_read(self._cmd_event_fd) if fd_value == 0: return @@ -763,20 +762,20 @@ def __mips_cmd_read_handler(self, ctx: any) -> None: if self._on_mips_cmd: self._on_mips_cmd(mips_cmd=mips_cmd) - def __mqtt_read_handler(self, ctx: any) -> None: + def __mqtt_read_handler(self, ctx: Any) -> None: self.__mqtt_loop_handler(ctx=ctx) - def __mqtt_write_handler(self, ctx: any) -> None: + def __mqtt_write_handler(self, ctx: Any) -> None: self.mev_set_write_handler(self._mqtt_fd, None, None) self.__mqtt_loop_handler(ctx=ctx) - def __mqtt_timer_handler(self, ctx: any) -> None: + def __mqtt_timer_handler(self, ctx: Any) -> None: self.__mqtt_loop_handler(ctx=ctx) if self._mqtt: self._mqtt_timer = self.mev_set_timeout( self.MQTT_INTERVAL_MS, self.__mqtt_timer_handler, None) - def __mqtt_loop_handler(self, ctx: any) -> None: + def __mqtt_loop_handler(self, ctx: Any) -> None: try: if self._mqtt: self._mqtt.loop_read() @@ -896,7 +895,7 @@ def __mips_try_reconnect(self, immediately: bool = False) -> None: self._mips_reconnect_timer = self.mev_set_timeout( interval, self.__mips_connect, None) - def __mips_sub_internal_pending_handler(self, ctx: any) -> None: + def __mips_sub_internal_pending_handler(self, ctx: Any) -> None: subbed_count = 1 for topic in list(self._mips_sub_pending_map.keys()): if subbed_count > self.MIPS_SUB_PATCH: @@ -923,7 +922,7 @@ def __mips_sub_internal_pending_handler(self, ctx: any) -> None: else: self._mips_sub_pending_timer = None - def __mips_connect(self, ctx: any = None) -> None: + def __mips_connect(self, ctx: Any = None) -> None: result = MQTT_ERR_UNKNOWN if self._mips_reconnect_timer: self.mev_clear_timeout(self._mips_reconnect_timer) @@ -1034,8 +1033,8 @@ def update_access_token(self, access_token: str) -> bool: @final def sub_prop( - self, did: str, handler: Callable[[dict, any], None], - siid: int = None, piid: int = None, handler_ctx: any = None + self, did: str, handler: Callable[[dict, Any], None], + siid: int = None, piid: int = None, handler_ctx: Any = None ) -> bool: if not isinstance(did, str) or handler is None: raise MIoTMipsError('invalid params') @@ -1044,7 +1043,7 @@ def sub_prop( f'device/{did}/up/properties_changed/' f'{"#" if siid is None or piid is None else f"{siid}/{piid}"}') - def on_prop_msg(topic: str, payload: str, ctx: any) -> bool: + def on_prop_msg(topic: str, payload: str, ctx: Any) -> bool: try: msg: dict = json.loads(payload) except json.JSONDecodeError: @@ -1077,8 +1076,8 @@ def unsub_prop(self, did: str, siid: int = None, piid: int = None) -> bool: @final def sub_event( - self, did: str, handler: Callable[[dict, any], None], - siid: int = None, eiid: int = None, handler_ctx: any = None + self, did: str, handler: Callable[[dict, Any], None], + siid: int = None, eiid: int = None, handler_ctx: Any = None ) -> bool: if not isinstance(did, str) or handler is None: raise MIoTMipsError('invalid params') @@ -1087,7 +1086,7 @@ def sub_event( f'device/{did}/up/event_occured/' f'{"#" if siid is None or eiid is None else f"{siid}/{eiid}"}') - def on_event_msg(topic: str, payload: str, ctx: any) -> bool: + def on_event_msg(topic: str, payload: str, ctx: Any) -> bool: try: msg: dict = json.loads(payload) except json.JSONDecodeError: @@ -1122,15 +1121,15 @@ def unsub_event(self, did: str, siid: int = None, eiid: int = None) -> bool: @final def sub_device_state( - self, did: str, handler: Callable[[str, MIoTDeviceState, any], None], - handler_ctx: any = None + self, did: str, handler: Callable[[str, MIoTDeviceState, Any], None], + handler_ctx: Any = None ) -> bool: """subscribe online state.""" if not isinstance(did, str) or handler is None: raise MIoTMipsError('invalid params') topic: str = f'device/{did}/state/#' - def on_state_msg(topic: str, payload: str, ctx: any) -> None: + def on_state_msg(topic: str, payload: str, ctx: Any) -> None: msg: dict = json.loads(payload) # {"device_id":"xxxx","device_name":"米家智能插座3 ","event":"online", # "model": "cuco.plug.v3","timestamp":1709001070828,"uid":xxxx} @@ -1163,11 +1162,11 @@ async def get_dev_list_async( async def get_prop_async( self, did: str, siid: int, piid: int, timeout_ms: int = 10000 - ) -> any: + ) -> Any: raise NotImplementedError('please call in http client') async def set_prop_async( - self, did: str, siid: int, piid: int, value: any, + self, did: str, siid: int, piid: int, value: Any, timeout_ms: int = 10000 ) -> bool: raise NotImplementedError('please call in http client') @@ -1199,8 +1198,8 @@ def __on_mips_cmd_handler(self, mips_cmd: MipsCmd) -> None: self._mips_unsub_internal(topic=unreg_bc.topic) def __reg_broadcast( - self, topic: str, handler: Callable[[str, str, any], None], - handler_ctx: any = None + self, topic: str, handler: Callable[[str, str, Any], None], + handler_ctx: Any = None ) -> bool: return self._mips_send_cmd( type_=MipsCmdType.REG_BROADCAST, @@ -1259,7 +1258,7 @@ class MipsLocalClient(MipsClient): _device_state_sub_map: dict[str, MipsDeviceState] _get_prop_queue: dict[str, list] _get_prop_timer: asyncio.TimerHandle - _on_dev_list_changed: Callable[[any, list[str]], asyncio.Future] + _on_dev_list_changed: Callable[[Any, list[str]], asyncio.Future] def __init__( self, did: str, host: str, group_id: str, @@ -1347,14 +1346,14 @@ async def disconnect_async(self) -> None: @final def sub_prop( - self, did: str, handler: Callable[[dict, any], None], - siid: int = None, piid: int = None, handler_ctx: any = None + self, did: str, handler: Callable[[dict, Any], None], + siid: int = None, piid: int = None, handler_ctx: Any = None ) -> bool: topic: str = ( f'appMsg/notify/iot/{did}/property/' f'{"#" if siid is None or piid is None else f"{siid}.{piid}"}') - def on_prop_msg(topic: str, payload: str, ctx: any): + def on_prop_msg(topic: str, payload: str, ctx: Any): msg: dict = json.loads(payload) if ( msg is None @@ -1380,14 +1379,14 @@ def unsub_prop(self, did: str, siid: int = None, piid: int = None) -> bool: @final def sub_event( - self, did: str, handler: Callable[[dict, any], None], - siid: int = None, eiid: int = None, handler_ctx: any = None + self, did: str, handler: Callable[[dict, Any], None], + siid: int = None, eiid: int = None, handler_ctx: Any = None ) -> bool: topic: str = ( f'appMsg/notify/iot/{did}/event/' f'{"#" if siid is None or eiid is None else f"{siid}.{eiid}"}') - def on_event_msg(topic: str, payload: str, ctx: any): + def on_event_msg(topic: str, payload: str, ctx: Any): msg: dict = json.loads(payload) if ( msg is None @@ -1414,7 +1413,7 @@ def unsub_event(self, did: str, siid: int = None, eiid: int = None) -> bool: @final async def get_prop_safe_async( self, did: str, siid: int, piid: int, timeout_ms: int = 10000 - ) -> any: + ) -> Any: self._get_prop_queue.setdefault(did, []) fut: asyncio.Future = self.main_loop.create_future() self._get_prop_queue[did].append({ @@ -1434,7 +1433,7 @@ async def get_prop_safe_async( @final async def get_prop_async( self, did: str, siid: int, piid: int, timeout_ms: int = 10000 - ) -> any: + ) -> Any: result_obj = await self.__request_async( topic='proxy/get', payload=json.dumps({ @@ -1449,7 +1448,7 @@ async def get_prop_async( @final async def set_prop_async( - self, did: str, siid: int, piid: int, value: any, + self, did: str, siid: int, piid: int, value: Any, timeout_ms: int = 10000 ) -> dict: payload_obj: dict = { @@ -1580,13 +1579,13 @@ async def exec_action_group_list_async( @final @property - def on_dev_list_changed(self) -> Callable[[any, list[str]], asyncio.Future]: + def on_dev_list_changed(self) -> Callable[[Any, list[str]], asyncio.Future]: return self._on_dev_list_changed @final @on_dev_list_changed.setter def on_dev_list_changed( - self, func: Callable[[any, list[str]], asyncio.Future] + self, func: Callable[[Any, list[str]], asyncio.Future] ) -> None: """run in main loop.""" self._on_dev_list_changed = func @@ -1731,8 +1730,8 @@ def __mips_publish( def __request( self, topic: str, payload: str, - on_reply: Callable[[str, any], None], - on_reply_ctx: any = None, timeout_ms: int = 10000 + on_reply: Callable[[str, Any], None], + on_reply_ctx: Any = None, timeout_ms: int = 10000 ) -> bool: if topic is None or payload is None or on_reply is None: raise MIoTMipsError('invalid params') @@ -1745,8 +1744,8 @@ def __request( return self._mips_send_cmd(type_=MipsCmdType.CALL_API, data=req_data) def __reg_broadcast( - self, topic: str, handler: Callable[[str, str, any], None], - handler_ctx: any + self, topic: str, handler: Callable[[str, str, Any], None], + handler_ctx: Any ) -> bool: return self._mips_send_cmd( type_=MipsCmdType.REG_BROADCAST, @@ -1764,7 +1763,7 @@ async def __request_async( ) -> dict: fut_handler: asyncio.Future = self.main_loop.create_future() - def on_msg_reply(payload: str, ctx: any): + def on_msg_reply(payload: str, ctx: Any): fut: asyncio.Future = ctx if fut: self.main_loop.call_soon_threadsafe(fut.set_result, payload) diff --git a/custom_components/xiaomi_home/miot/miot_spec.py b/custom_components/xiaomi_home/miot/miot_spec.py index 3df70f1..bae7e79 100644 --- a/custom_components/xiaomi_home/miot/miot_spec.py +++ b/custom_components/xiaomi_home/miot/miot_spec.py @@ -49,7 +49,7 @@ import json import platform import time -from typing import Optional +from typing import Any, Optional from urllib.parse import urlencode from urllib.request import Request, urlopen import logging @@ -78,9 +78,9 @@ class MIoTSpecBase: # External params platform: str - device_class: any + device_class: Any icon: str - external_unit: any + external_unit: Any spec_id: str @@ -166,7 +166,7 @@ def readable(self) -> bool: def notifiable(self): return self._notifiable - def value_format(self, value: any) -> any: + def value_format(self, value: Any) -> Any: if value is None: return None if self.format_ == 'int': @@ -296,7 +296,7 @@ class MIoTSpecInstance: # External params platform: str - device_class: any + device_class: Any icon: str def __init__( diff --git a/custom_components/xiaomi_home/miot/miot_storage.py b/custom_components/xiaomi_home/miot/miot_storage.py index dfd6b28..85b25c9 100644 --- a/custom_components/xiaomi_home/miot/miot_storage.py +++ b/custom_components/xiaomi_home/miot/miot_storage.py @@ -56,7 +56,7 @@ from datetime import datetime, timezone from enum import Enum, auto from pathlib import Path -from typing import Optional, Union +from typing import Any, Optional, Union import logging from urllib.request import Request, urlopen from cryptography.hazmat.primitives import serialization @@ -419,7 +419,7 @@ async def clear_async(self) -> bool: return await fut def update_user_config( - self, uid: str, cloud_server: str, config: Optional[dict[str, any]], + self, uid: str, cloud_server: str, config: Optional[dict[str, Any]], replace: bool = False ) -> bool: if config is not None and len(config) == 0: @@ -443,7 +443,7 @@ def update_user_config( domain=config_domain, name=config_name, data=local_config) async def update_user_config_async( - self, uid: str, cloud_server: str, config: Optional[dict[str, any]], + self, uid: str, cloud_server: str, config: Optional[dict[str, Any]], replace: bool = False ) -> bool: """Update user configuration. @@ -480,7 +480,7 @@ async def update_user_config_async( def load_user_config( self, uid: str, cloud_server: str, keys: Optional[list[str]] = None - ) -> dict[str, any]: + ) -> dict[str, Any]: if keys is not None and len(keys) == 0: # Do nothing return {} @@ -494,7 +494,7 @@ def load_user_config( async def load_user_config_async( self, uid: str, cloud_server: str, keys: Optional[list[str]] = None - ) -> dict[str, any]: + ) -> dict[str, Any]: """Load user configuration. Args: @@ -503,7 +503,7 @@ async def load_user_config_async( query key list, return all config item if keys is None Returns: - dict[str, any]: query result + dict[str, Any]: query result """ if keys is not None and len(keys) == 0: # Do nothing diff --git a/custom_components/xiaomi_home/sensor.py b/custom_components/xiaomi_home/sensor.py index 7d2e074..b5ff00d 100644 --- a/custom_components/xiaomi_home/sensor.py +++ b/custom_components/xiaomi_home/sensor.py @@ -47,6 +47,7 @@ """ from __future__ import annotations import logging +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -107,7 +108,7 @@ def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None: self._attr_icon = spec.icon @property - def native_value(self) -> any: + def native_value(self) -> Any: """Return the current value of the sensor.""" if self._value_range and isinstance(self._value, (int, float)): if ( diff --git a/custom_components/xiaomi_home/water_heater.py b/custom_components/xiaomi_home/water_heater.py index ee6a91d..aa7fe67 100644 --- a/custom_components/xiaomi_home/water_heater.py +++ b/custom_components/xiaomi_home/water_heater.py @@ -47,7 +47,7 @@ """ from __future__ import annotations import logging -from typing import Optional +from typing import Any, Optional from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -93,7 +93,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): _prop_target_temp: Optional[MIoTSpecProperty] _prop_mode: Optional[MIoTSpecProperty] - _mode_list: Optional[dict[any, any]] + _mode_list: Optional[dict[Any, Any]] def __init__( self, miot_device: MIoTDevice, entity_data: MIoTEntityData @@ -164,7 +164,7 @@ async def async_turn_off(self) -> None: """Turn the water heater off.""" await self.set_property_async(prop=self._prop_on, value=False) - async def async_set_temperature(self, **kwargs: any) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set the temperature the water heater should heat water to.""" await self.set_property_async( prop=self._prop_target_temp, value=kwargs[ATTR_TEMPERATURE]) diff --git a/test/test_lan.py b/test/test_lan.py index 4f96725..a6051c0 100755 --- a/test/test_lan.py +++ b/test/test_lan.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Unit test for miot_lan.py.""" +from typing import Any import pytest import asyncio from zeroconf import IPVersion @@ -79,7 +80,7 @@ async def test_lan_async(test_devices: dict): evt_push_unavailable = asyncio.Event() await miot_lan.vote_for_lan_ctrl_async(key='test', vote=True) - async def device_state_change(did: str, state: dict, ctx: any): + async def device_state_change(did: str, state: dict, ctx: Any): print('device state change, ', did, state) if did != test_did: return From 9af59e28bdada6231720cf3d127ae8f5cac30b8f Mon Sep 17 00:00:00 2001 From: Li Shuzhen Date: Mon, 23 Dec 2024 22:40:57 +0800 Subject: [PATCH 10/17] docs: specify the download process in HACS installation (#371) * docs: specify the download process in HACS installation * docs: revoke login authorization --- README.md | 4 +++- doc/README_zh.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2268b9d..96bba84 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ git checkout v1.0.0 ### Method 2: [HACS](https://hacs.xyz/) -HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category: Integration > ADD +HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category: Integration > ADD > Xiaomi Home in New or Available for download section of HACS > DOWNLOAD > Xiaomi Home has not been added to the HACS store as a default yet. It's coming soon. @@ -76,6 +76,8 @@ Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.ho Xiaomi Home Integration and the affiliated cloud interface is provided by Xiaomi officially. You need to use your Xiaomi account to login to get your device list. Xiaomi Home Integration implements OAuth 2.0 login process, which does not keep your account password in the Home Assistant application. However, due to the limitation of the Home Assistant platform, the user information (including device information, certificates, tokens, etc.) of your Xiaomi account will be saved in the Home Assistant configuration file in clear text after successful login. You need to ensure that your Home Assistant configuration file is properly stored. The exposure of your configuration file may result in others logging in with your identity. +> If you suspect that your OAuth 2.0 token has been leaked, you can revoke the login authorization of your Xiaomi account by the following steps: Xiaomi Home APP -> Profile -> Click your username and get into Xiaomi Account management page -> Basic info: Apps -> Xiaomi Home (Home Assistant Integration) -> Remove + ## FAQ - Does Xiaomi Home Integration support all Xiaomi Home devices? diff --git a/doc/README_zh.md b/doc/README_zh.md index 7e1a48b..e5905fe 100644 --- a/doc/README_zh.md +++ b/doc/README_zh.md @@ -32,7 +32,7 @@ git checkout v1.0.0 ### 方法 2: [HACS](https://hacs.xyz/) -HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category: Integration > ADD +HACS > 右上角三个点 > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category: Integration > ADD > 点击 HACS 的 New 或 Available for download 分类下的 Xiaomi Home ,进入集成详情页 > DOWNLOAD > 米家集成暂未添加到 HACS 商店,敬请期待。 @@ -76,6 +76,8 @@ HACS > Overflow Menu > Custom repositories > Repository: https://github.com/Xiao 米家集成及其使用的云端接口由小米官方提供。您需要使用小米账号登录以获取设备列表。米家集成使用 OAuth 2.0 的登录方式,不会在 Home Assistant 中保存您的小米账号密码。但由于 Home Assistant 平台的限制,登录成功后,您的小米用户信息(包括设备信息、证书、 token 等)会明文保存在 Home Assistant 的配置文件中。因此,您需要保管好自己 Home Assistant 配置文件。一旦该文件泄露,其他人可能会冒用您的身份登录。 +> 如果您怀疑您的 OAuth 2.0 令牌已泄露,您可以通过以下步骤取消小米账号的登录授权: 米家 APP -> 我的 -> 点击用户名进入小米账号页面 -> 应用授权 -> Xiaomi Home (Home Assistant Integration) -> 取消授权 + ## 常见问题 - 米家集成是否支持所有的小米米家设备? From ea9aa082b79852ad85a8b7efa68cc493c29567ff Mon Sep 17 00:00:00 2001 From: ZnDong <81907400+ZnDong@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:02:17 +0800 Subject: [PATCH 11/17] docs: amend HACS installation (#404) * docs: amend HACS installation * docs: amend HACS installation --- README.md | 2 +- doc/README_zh.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 96bba84..872808a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ git checkout v1.0.0 ### Method 2: [HACS](https://hacs.xyz/) -HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category: Integration > ADD > Xiaomi Home in New or Available for download section of HACS > DOWNLOAD +HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category or Type: Integration > ADD > Xiaomi Home in New or Available for download section of HACS > DOWNLOAD > Xiaomi Home has not been added to the HACS store as a default yet. It's coming soon. diff --git a/doc/README_zh.md b/doc/README_zh.md index e5905fe..b237b8b 100644 --- a/doc/README_zh.md +++ b/doc/README_zh.md @@ -32,7 +32,7 @@ git checkout v1.0.0 ### 方法 2: [HACS](https://hacs.xyz/) -HACS > 右上角三个点 > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category: Integration > ADD > 点击 HACS 的 New 或 Available for download 分类下的 Xiaomi Home ,进入集成详情页 > DOWNLOAD +HACS > 右上角三个点 > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category or Type: Integration > ADD > 点击 HACS 的 New 或 Available for download 分类下的 Xiaomi Home ,进入集成详情页 > DOWNLOAD > 米家集成暂未添加到 HACS 商店,敬请期待。 From 0f5da18108c3534d00e327471be4e76724c9e0e7 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:02:36 +0800 Subject: [PATCH 12/17] fix: fix unit convert attribute error (#396) --- custom_components/xiaomi_home/miot/miot_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index 6144d57..103a7c3 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -628,7 +628,7 @@ def unit_convert(self, spec_unit: str) -> Optional[str]: # pylint: disable=import-outside-toplevel from homeassistant.const import UnitOfConductivity unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM - except ImportError: + except Exception: # pylint: disable=broad-except unit_map['μS/cm'] = 'μS/cm' return unit_map.get(spec_unit, None) From 7654e5e5184360a0a347e6be08d6f6a79c70246f Mon Sep 17 00:00:00 2001 From: RangerCD Date: Tue, 24 Dec 2024 21:03:11 +0800 Subject: [PATCH 13/17] fix: ignore undefined piid and keep processing following arguments (#377) --- custom_components/xiaomi_home/miot/miot_device.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index 103a7c3..47e2bc1 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -1198,8 +1198,8 @@ def on_event_occurred( def __on_event_occurred(self, params: dict, ctx: Any) -> None: _LOGGER.debug('event occurred, %s', params) trans_arg = {} - try: - for item in params['arguments']: + for item in params['arguments']: + try: if 'value' not in item: continue if 'piid' in item: @@ -1215,10 +1215,10 @@ def __on_event_occurred(self, params: dict, ctx: Any) -> None: for index, prop in enumerate(self.spec.argument) } break - except KeyError as error: - _LOGGER.error( - 'on event msg, invalid args, %s, %s, %s', - self.entity_id, params, error) + except KeyError as error: + _LOGGER.debug( + 'on event msg, invalid args, %s, %s, %s', + self.entity_id, params, error) self.on_event_occurred( name=self.spec.description_trans, arguments=trans_arg) self.async_write_ha_state() From 78461cbd8a80fc1de0636e81049633f11ccd0a27 Mon Sep 17 00:00:00 2001 From: Li Shuzhen Date: Tue, 24 Dec 2024 21:18:33 +0800 Subject: [PATCH 14/17] fix: lumi.switch.acn040 identify service translation of zh-Hans (#412) * fix: lumi.switch.acn040 identify service translation of zh-Hans * fix: remove useless part * fix: lumi.switch.acn040 identify service translation of en --- .../xiaomi_home/miot/specs/multi_lang.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/custom_components/xiaomi_home/miot/specs/multi_lang.json b/custom_components/xiaomi_home/miot/specs/multi_lang.json index 80cb8b8..5e48bcc 100644 --- a/custom_components/xiaomi_home/miot/specs/multi_lang.json +++ b/custom_components/xiaomi_home/miot/specs/multi_lang.json @@ -154,5 +154,19 @@ "service:004:event:001": "虛擬事件發生", "service:004:property:001": "事件名稱" } + }, + "urn:miot-spec-v2:device:switch:0000A003:lumi-acn040:1": { + "en": { + "service:011": "Right Button On and Off", + "service:011:property:001": "Right Button On and Off", + "service:015:action:001": "Left Button Identify", + "service:016:action:001": "Middle Button Identify", + "service:017:action:001": "Right Button Identify" + }, + "zh-Hans": { + "service:015:action:001": "左键确认", + "service:016:action:001": "中键确认", + "service:017:action:001": "右键确认" + } } } \ No newline at end of file From 29b7489ac702c9819194a8b6f13a655d7f5e1573 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:28:50 +0800 Subject: [PATCH 15/17] feat: support devices filter & devices changed notify (#332) * feat: add devices filter page * doc: update translations and i18n * fix: cancel miot http timer * feat: improve devices filter * doc: update translations/de * fix: i18n type error * feat: config flow support device filter * fix: fix mdns type error * fix: fix miot client i18n logic * feat: add connect type * doc: update translations and i18n * feat: update auth info save logic * feat: improve config flow devices filter * fix: fix some type error * doc: update translations for option flow * fix: fix option flow type error * feat: support option flow devices filter * doc: update option flow tranlations * feat: update webhook handle func to private * feat: update config flow translations * doc: update tranlations text * feat: custom display device changed notify * feat: device changed notify logical refinement * doc: update translations content --- custom_components/xiaomi_home/config_flow.py | 875 +++++++++++++----- .../xiaomi_home/miot/i18n/de.json | 43 +- .../xiaomi_home/miot/i18n/en.json | 43 +- .../xiaomi_home/miot/i18n/es.json | 43 +- .../xiaomi_home/miot/i18n/fr.json | 43 +- .../xiaomi_home/miot/i18n/ja.json | 43 +- .../xiaomi_home/miot/i18n/nl.json | 45 +- .../xiaomi_home/miot/i18n/pt-BR.json | 43 +- .../xiaomi_home/miot/i18n/pt.json | 43 +- .../xiaomi_home/miot/i18n/ru.json | 43 +- .../xiaomi_home/miot/i18n/zh-Hans.json | 43 +- .../xiaomi_home/miot/i18n/zh-Hant.json | 43 +- .../xiaomi_home/miot/miot_client.py | 58 +- .../xiaomi_home/miot/miot_cloud.py | 7 + .../xiaomi_home/miot/miot_i18n.py | 4 +- .../xiaomi_home/miot/miot_mdns.py | 23 +- .../xiaomi_home/translations/de.json | 59 +- .../xiaomi_home/translations/en.json | 63 +- .../xiaomi_home/translations/es.json | 65 +- .../xiaomi_home/translations/fr.json | 67 +- .../xiaomi_home/translations/ja.json | 67 +- .../xiaomi_home/translations/nl.json | 65 +- .../xiaomi_home/translations/pt-BR.json | 65 +- .../xiaomi_home/translations/pt.json | 63 +- .../xiaomi_home/translations/ru.json | 65 +- .../xiaomi_home/translations/zh-Hans.json | 55 +- .../xiaomi_home/translations/zh-Hant.json | 55 +- 27 files changed, 1739 insertions(+), 392 deletions(-) diff --git a/custom_components/xiaomi_home/config_flow.py b/custom_components/xiaomi_home/config_flow.py index 36f9815..167e2e5 100644 --- a/custom_components/xiaomi_home/config_flow.py +++ b/custom_components/xiaomi_home/config_flow.py @@ -50,7 +50,7 @@ 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 @@ -101,34 +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] - - _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] + _miot_network: MIoTNetwork + _mips_service: MipsService + _miot_storage: MIoTStorage + _miot_i18n: MIoTI18n + + _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 - _auth_info: Optional[dict] - _nick_name: Optional[str] - _home_selected: Optional[dict] - _home_info_buffer: Optional[dict[str, str | dict[str, dict]]] - _home_list: Optional[dict] - - _cloud_server: Optional[str] - _oauth_redirect_url: Optional[str] + _display_devices_changed_notify: list[str] + + _auth_info: dict + _nick_name: str + _home_selected: 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 _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] @@ -136,68 +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._auth_info = None - self._nick_name = None + self._display_devices_changed_notify = ['add', 'del', 'offline'] + self._auth_info = {} + self._nick_name = DEFAULT_NICK_NAME self._home_selected = {} - self._home_info_buffer = None - self._home_list = 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) @@ -209,7 +211,9 @@ async def async_step_user(self, user_input=None): 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() @@ -220,16 +224,18 @@ async def __display_eula(self, reason: str): 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( @@ -237,7 +243,7 @@ async def async_step_auth_config(self, user_input=None): 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) @@ -252,33 +258,38 @@ async def async_step_auth_config(self, user_input=None): 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( @@ -288,12 +299,12 @@ async def async_step_oauth(self, user_input=None): domain=DOMAIN, name='oauth redirect url webhook', webhook_id=self._virtual_did, - handler=handle_oauth_webhook, + handler=_handle_oauth_webhook, allowed_methods=(METH_GET,), ) 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 @@ -332,11 +343,16 @@ async def async_step_oauth(self, user_input=None): 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: @@ -358,7 +374,7 @@ async def __check_oauth_async(self) -> None: 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') @@ -369,12 +385,20 @@ async def __check_oauth_async(self) -> None: # 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) 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()) @@ -420,9 +444,9 @@ async def __check_oauth_async(self) -> None: home_info['central_did'] = mips_list[group_id].get('did', None) home_list[home_id] = ( f'{home_info["home_name"]} ' - f'[ {len(dev_list)} {tip_devices}{tip_central} ]') + 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: @@ -443,6 +467,8 @@ async def __check_oauth_async(self) -> None: 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') @@ -481,70 +507,47 @@ async def async_step_oauth_error(self, user_input=None): 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: - return await self.display_homes_select_form('') + if not user_input: + 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') - self._ctrl_mode = user_input.get('ctrl_mode') for home_id, home_info in self._home_info_buffer[ '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._action_debug = user_input.get( - 'action_debug', self._action_debug) - self._hide_non_standard_entities = user_input.get( - 'hide_non_standard_entities', self._hide_non_standard_entities) + 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 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') - devices_list_sort = dict(sorted( + 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', ''))) + if not await self._miot_storage.save_async( domain='miot_devices', name=f'{self._uid}_{self._cloud_server}', - data=devices_list_sort): + data=self._device_list_sorted): _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 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') - return self.async_create_entry( - title=( - f'{self._nick_name}: {self._uid} ' - f'[{CLOUD_SERVERS[self._cloud_server]}]'), - data={ - 'virtual_did': self._virtual_did, - 'uuid': self._uuid, - 'integration_language': self._integration_language, - 'storage_path': self._storage_path, - 'uid': self._uid, - 'nick_name': self._nick_name, - 'cloud_server': self._cloud_server, - 'oauth_redirect_url': self._oauth_redirect_url, - 'ctrl_mode': self._ctrl_mode, - 'home_selected': self._home_selected, - 'area_name_rule': self._area_name_rule, - 'action_debug': self._action_debug, - 'hide_non_standard_entities': - self._hide_non_standard_entities, - }) + if user_input.get('advanced_options', False): + return await self.async_step_advanced_options() + return await self.config_flow_done() except Exception as err: _LOGGER.error( 'async_step_homes_select, %s, %s', @@ -555,19 +558,20 @@ async def async_step_homes_select(self, user_input=None): '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({ - vol.Required('ctrl_mode', default=DEFAULT_CTRL_MODE): vol.In( - self._miot_i18n.translate(key='config.control_mode')), - 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('action_debug', default=self._action_debug): bool, + vol.Required('home_infos'): cv.multi_select( + self._home_list_show), vol.Required( - 'hide_non_standard_entities', - default=self._hide_non_standard_entities): bool, + '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={ @@ -576,8 +580,207 @@ async def display_homes_select_form(self, reason: str): last_step=False, ) - @staticmethod - @callback + 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( + 'action_debug', self._action_debug) + self._hide_non_standard_entities = user_input.get( + 'hide_non_standard_entities', self._hide_non_standard_entities) + self._display_devices_changed_notify = user_input.get( + 'display_devices_changed_notify', + self._display_devices_changed_notify) + # Device filter + if user_input.get('devices_filter', False): + return await self.async_step_devices_filter() + return await self.config_flow_done() + return self.async_show_form( + step_id='advanced_options', + data_schema=vol.Schema({ + 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 # type: ignore + ): bool, + vol.Required( + 'display_devices_changed_notify', + default=self._display_devices_changed_notify # type: ignore + ): cv.multi_select( + self._miot_i18n.translate(key='config.device_state')), + }), + last_step=False, + ) + + async def async_step_devices_filter( + self, user_input: Optional[dict] = None + ): + if user_input: + # Room filter + include_items: dict = {} + exclude_items: dict = {} + room_list_in: list = user_input.get('room_list', []) + if room_list_in: + 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: + 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: + 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: + if user_input.get( + 'devices_filter_mode', 'include') == 'include': + include_items['did'] = device_list_in + else: + exclude_items['did'] = device_list_in + device_filter_list = _handle_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( + 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): + 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') # type: ignore + tip_without_room: str = self._miot_i18n.translate( + key='config.other.without_room') # type: ignore + trans_statistics_logic: dict = self._miot_i18n.translate( + key='config.statistics_logic') # type: ignore + trans_filter_mode: dict = self._miot_i18n.translate( + key='config.filter_mode') # type: ignore + trans_connect_type: dict = self._miot_i18n.translate( + key='config.connect_type') # type: ignore + + 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.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' # type: ignore + ): vol.In(trans_filter_mode), + vol.Optional('room_list'): cv.multi_select(room_list), + 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' # 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' # 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' # type: ignore + ): vol.In(trans_statistics_logic), + }), + errors={'base': reason}, + last_step=False + ) + + async def config_flow_done(self): + return self.async_create_entry( + title=( + f'{self._nick_name}: {self._uid} ' + f'[{CLOUD_SERVERS[self._cloud_server]}]'), + data={ + 'virtual_did': self._virtual_did, + 'uuid': self._uuid, + 'integration_language': self._integration_language, + 'storage_path': self._storage_path, + 'uid': self._uid, + 'nick_name': self._nick_name, + 'cloud_server': self._cloud_server, + '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, + 'action_debug': self._action_debug, + 'hide_non_standard_entities': + self._hide_non_standard_entities, + 'display_devices_changed_notify': + self._display_devices_changed_notify + }) + + @ staticmethod + @ callback def async_get_options_flow( config_entry: config_entries.ConfigEntry, ) -> config_entries.OptionsFlow: @@ -590,34 +793,37 @@ 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] - _mips_service: Optional[MipsService] - _miot_oauth: Optional[MIoTOauthClient] - _miot_http: Optional[MIoTHttpClient] - _miot_i18n: Optional[MIoTI18n] - _miot_lan: Optional[MIoTLan] + _miot_network: MIoTNetwork + _miot_storage: MIoTStorage + _mips_service: MipsService + _miot_oauth: MIoTOauthClient + _miot_http: MIoTHttpClient + _miot_i18n: MIoTI18n + _miot_lan: MIoTLan _entry_data: dict - _virtual_did: Optional[str] - _uid: Optional[str] - _storage_path: Optional[str] - _cloud_server: Optional[str] - _oauth_redirect_url: Optional[str] - _integration_language: Optional[str] - _ctrl_mode: Optional[str] - _nick_name: Optional[str] - _home_selected_list: Optional[list] + _virtual_did: str + _uid: str + _storage_path: str + _cloud_server: str + + _integration_language: str + _ctrl_mode: str + _nick_name: str + _home_selected_list: list _action_debug: bool _hide_non_standard_entities: bool - - _auth_info: Optional[dict] - _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]] + _display_devs_notify: list[str] + + _oauth_redirect_url_full: str + _auth_info: dict + _home_selected: dict + _home_info_buffer: dict + _home_list_show: dict + _device_list_sorted: dict + _devices_local: dict _devices_add: list[str] _devices_remove: list[str] @@ -626,7 +832,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): _config_error_reason: Optional[str] _fut_oauth_code: Optional[asyncio.Future] # Config options - _lang_new: Optional[str] + _lang_new: str _nick_name_new: Optional[str] _action_debug_new: bool _hide_non_standard_entities_new: bool @@ -641,37 +847,32 @@ class OptionsFlowHandler(config_entries.OptionsFlow): def __init__(self, config_entry: config_entries.ConfigEntry): self._config_entry = config_entry - self._main_loop = None - self._miot_client = None - - self._miot_network = None - self._miot_storage = None - self._mips_service = None - self._miot_oauth = None - self._miot_http = None - self._miot_i18n = None - self._miot_lan = None + self._main_loop = asyncio.get_event_loop() self._entry_data = dict(config_entry.data) self._virtual_did = self._entry_data['virtual_did'] 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._ctrl_mode = self._entry_data['ctrl_mode'] - self._integration_language = self._entry_data['integration_language'] - self._nick_name = self._entry_data['nick_name'] + self._ctrl_mode = self._entry_data.get('ctrl_mode', DEFAULT_CTRL_MODE) + self._integration_language = self._entry_data.get( + 'integration_language', DEFAULT_INTEGRATION_LANGUAGE) + self._nick_name = self._entry_data.get('nick_name', DEFAULT_NICK_NAME) self._action_debug = self._entry_data.get('action_debug', False) self._hide_non_standard_entities = self._entry_data.get( 'hide_non_standard_entities', False) + self._display_devs_notify = self._entry_data.get( + 'display_devices_changed_notify', ['add', 'del', 'offline']) self._home_selected_list = list( self._entry_data['home_selected'].keys()) - self._auth_info = None - self._home_selected_dict = {} - self._home_info_buffer = None - self._home_list = None - self._device_list = None + self._oauth_redirect_url_full = '' + self._auth_info = {} + self._home_selected = {} + self._home_info_buffer = {} + self._home_list_show = {} + self._device_list_sorted = {} + self._devices_local = {} self._devices_add = [] self._devices_remove = [] @@ -680,7 +881,7 @@ def __init__(self, config_entry: config_entries.ConfigEntry): self._config_error_reason = None self._fut_oauth_code = None - self._lang_new = None + self._lang_new = self._integration_language self._nick_name_new = None self._action_debug_new = False self._hide_non_standard_entities_new = False @@ -701,8 +902,6 @@ async def async_step_init(self, user_input=None): self.hass.data.setdefault(DOMAIN, {}) self.hass.data[DOMAIN].setdefault(self._virtual_did, {}) try: - # main loop - self._main_loop = asyncio.get_running_loop() # MIoT client self._miot_client: MIoTClient = await get_miot_instance_async( hass=self.hass, entry_id=self._config_entry.entry_id) @@ -765,7 +964,7 @@ async def async_step_auth_config(self, user_input=None): 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( @@ -773,7 +972,8 @@ async def async_step_auth_config(self, user_input=None): data_schema=vol.Schema({ 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]), }), description_placeholders={ 'cloud_server': CLOUD_SERVERS[self._cloud_server], @@ -787,9 +987,9 @@ async def async_step_oauth(self, user_input=None): 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) @@ -800,7 +1000,7 @@ async def async_step_oauth(self, user_input=None): domain=DOMAIN, name='oauth redirect url webhook', webhook_id=self._virtual_did, - handler=handle_oauth_webhook, + handler=_handle_oauth_webhook, allowed_methods=(METH_GET,), ) self._fut_oauth_code = self.hass.data[DOMAIN][ @@ -844,11 +1044,15 @@ async def async_step_oauth(self, user_input=None): async def __check_oauth_async(self) -> None: # Get oauth code - oauth_code: Optional[str] = await self._fut_oauth_code + if not self._fut_oauth_code: + raise MIoTConfigError('oauth_code_fut_error') + oauth_code: str = await self._fut_oauth_code + if not oauth_code: + raise MIoTConfigError('oauth_code_error') _LOGGER.debug('options flow __check_oauth_async, %s', oauth_code) # Get access_token and user_info from miot_oauth - if self._auth_info is None: - auth_info: dict = None + if not self._auth_info: + auth_info: dict = {} try: auth_info = await self._miot_oauth.get_access_token_async( code=oauth_code) @@ -903,24 +1107,37 @@ async def async_step_config_options(self, user_input=None): data_schema=vol.Schema({ vol.Required( 'integration_language', - default=self._integration_language + default=self._integration_language # type: ignore ): vol.In(INTEGRATION_LANGUAGES), vol.Required( 'update_user_info', - default=self._update_user_info): bool, + default=self._update_user_info # type: ignore + ): bool, vol.Required( - 'update_devices', default=self._update_devices): bool, + 'update_devices', + default=self._update_devices # type: ignore + ): bool, vol.Required( - 'action_debug', default=self._action_debug): bool, + '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_devs_notify # type: ignore + ): cv.multi_select( + self._miot_i18n.translate('config.device_state')), vol.Required( 'update_trans_rules', - default=self._update_trans_rules): bool, + default=self._update_trans_rules # type: ignore + ): bool, vol.Required( 'update_lan_ctrl_config', - default=self._update_lan_ctrl_config): bool + default=self._update_lan_ctrl_config # type: ignore + ): bool }), errors={}, description_placeholders={ @@ -944,6 +1161,8 @@ async def async_step_config_options(self, user_input=None): 'action_debug', self._action_debug) self._hide_non_standard_entities_new = user_input.get( 'hide_non_standard_entities', self._hide_non_standard_entities) + self._display_devs_notify = user_input.get( + 'display_devices_changed_notify', self._display_devs_notify) self._update_trans_rules = user_input.get( 'update_trans_rules', self._update_trans_rules) self._update_lan_ctrl_config = user_input.get( @@ -972,7 +1191,9 @@ async def async_step_update_user_info(self, user_input=None): 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: @@ -1017,67 +1238,210 @@ async def async_step_homes_select(self, user_input=None): 'did', None) home_list[home_id] = ( f'{home_info["home_name"]} ' - f'[ {len(did_list)} {tip_devices}{tip_central} ]') + f'[ {len(did_list)} {tip_devices} {tip_central} ]') # Remove deleted item self._home_selected_list = [ home_id for home_id in self._home_selected_list if home_id in home_list] + self._home_list_show = dict(sorted(home_list.items())) + # Get local devices + self._devices_local: dict = await self._miot_storage.load_async( + domain='miot_devices', + name=f'{self._uid}_{self._cloud_server}', + type_=dict) or {} # type: ignore - 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') - self._ctrl_mode = user_input.get('ctrl_mode') - self._home_selected_dict = {} + return await self.__display_homes_select_form('no_family_selected') + self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode) + self._home_selected = {} for home_id, home_info in self._home_info_buffer[ 'homes']['home_list'].items(): if home_id in self._home_selected_list: - self._home_selected_dict[home_id] = home_info + self._home_selected[home_id] = home_info # Get device list - self._device_list: dict[str, dict] = { + device_list: dict = { did: dev_info 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') - # 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._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 display_homes_select_form(self, reason: str): + if not device_list: + return await self.__display_homes_select_form('no_devices') + self._device_list_sorted = dict(sorted( + device_list.items(), key=lambda item: + item[1].get('home_id', '')+item[1].get('room_id', ''))) + + 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): + devices_local_count: str = str(len(self._devices_local)) 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), + default=self._home_selected_list # type: ignore + ): cv.multi_select(self._home_list_show), + vol.Required( + 'devices_filter', default=False # type: ignore + ): bool, + vol.Required( + 'ctrl_mode', default=self._ctrl_mode # type: ignore + ): vol.In(self._miot_i18n.translate(key='config.control_mode')), }), errors={'base': reason}, description_placeholders={ - 'nick_name': self._nick_name + 'local_count': devices_local_count }, last_step=False ) + async def async_step_devices_filter( + self, user_input: Optional[dict] = None + ): + if user_input: + # Room filter + include_items: dict = {} + exclude_items: dict = {} + room_list_in: list = user_input.get('room_list', []) + if room_list_in: + 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: + 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: + 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: + if user_input.get( + 'devices_filter_mode', 'include') == 'include': + include_items['did'] = device_list_in + else: + exclude_items['did'] = device_list_in + device_filter_list = _handle_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( + device_filter_list.items(), key=lambda item: + item[1].get('home_id', '')+item[1].get('room_id', ''))) + 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') # type: ignore + tip_without_room: str = self._miot_i18n.translate( + key='config.other.without_room') # type: ignore + trans_statistics_logic: dict = self._miot_i18n.translate( + key='config.statistics_logic') # type: ignore + trans_filter_mode: dict = self._miot_i18n.translate( + key='config.filter_mode') # type: ignore + trans_connect_type: dict = self._miot_i18n.translate( + key='config.connect_type') # type: ignore + + 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.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' # type: ignore + ): vol.In(trans_filter_mode), + vol.Optional('room_list'): cv.multi_select(room_list), + 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' # 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' # 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' # type: ignore + ): 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 = [] + + self._devices_add = [ + did for did in list(self._device_list_sorted.keys()) + if did not in self._devices_local] + self._devices_remove = [ + did for did in self._devices_local.keys() + if did not in self._device_list_sorted] + _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() @@ -1090,10 +1454,12 @@ async def async_step_update_trans_rules(self, user_input=None): return self.async_show_form( step_id='update_trans_rules', data_schema=vol.Schema({ - vol.Required('confirm', default=False): bool + vol.Required( + 'confirm', default=False # type: ignore + ): bool }), description_placeholders={ - 'urn_count': self._trans_rules_count, + 'urn_count': str(self._trans_rules_count), }, last_step=False ) @@ -1133,8 +1499,8 @@ async def async_step_update_lan_ctrl_config(self, user_input=None): f'{if_name} ({info.ip}/{info.netmask})') net_segs.add(info.net_seg) if len(net_segs) != len(net_info): - notice_net_dup = self._miot_i18n.translate( - key='config.lan_ctrl_config.notice_net_dup') + notice_net_dup: str = self._miot_i18n.translate( + key='config.lan_ctrl_config.notice_net_dup') # type: ignore return self.async_show_form( step_id='update_lan_ctrl_config', data_schema=vol.Schema({ @@ -1181,10 +1547,13 @@ async def async_step_config_confirm(self, user_input=None): key='config.option_status.enable') disable_text = self._miot_i18n.translate( key='config.option_status.disable') + trans_devs_display: dict = self._miot_i18n.translate( + key='config.device_state') return self.async_show_form( step_id='config_confirm', data_schema=vol.Schema({ - vol.Required('confirm', default=False): bool + vol.Required( + 'confirm', default=False): bool # type: ignore }), description_placeholders={ 'nick_name': self._nick_name, @@ -1201,12 +1570,18 @@ async def async_step_config_confirm(self, user_input=None): 'hide_non_standard_entities': ( enable_text if self._hide_non_standard_entities_new else disable_text), - }, + 'display_devices_changed_notify': (' '.join( + trans_devs_display[key] + for key in self._display_devs_notify + if key in trans_devs_display) + if self._display_devs_notify + else self._miot_i18n.translate( + key='config.other.no_display')) + }, # type: ignore errors={'base': 'not_confirm'} if user_input else {}, 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 @@ -1214,14 +1589,11 @@ async def async_step_config_confirm(self, user_input=None): self._entry_data['nick_name'] = self._nick_name_new if self._update_devices: self._entry_data['ctrl_mode'] = self._ctrl_mode - self._entry_data['home_selected'] = self._home_selected_dict - devices_list_sort = dict(sorted( - self._device_list.items(), key=lambda item: - item[1].get('home_id', '')+item[1].get('room_id', ''))) + self._entry_data['home_selected'] = self._home_selected if not await self._miot_storage.save_async( domain='miot_devices', name=f'{self._uid}_{self._cloud_server}', - data=devices_list_sort): + data=self._device_list_sorted): _LOGGER.error( 'save devices async failed, %s, %s', self._uid, self._cloud_server) @@ -1241,6 +1613,11 @@ async def async_step_config_confirm(self, user_input=None): self._entry_data['hide_non_standard_entities'] = ( self._hide_non_standard_entities_new) self._need_reload = True + # Update display_devices_changed_notify + self._entry_data['display_devices_changed_notify'] = ( + self._display_devs_notify) + self._miot_client.display_devices_changed_notify = ( + self._display_devs_notify) if ( self._devices_remove and not await self._miot_storage.update_user_config_async( @@ -1266,7 +1643,8 @@ async def async_step_config_confirm(self, user_input=None): return self.async_create_entry(title='', data={}) -async def handle_oauth_webhook(hass, webhook_id, request): +async def _handle_oauth_webhook(hass, webhook_id, request): + """Webhook to handle oauth2 callback.""" # pylint: disable=inconsistent-quotes try: data = dict(request.query) @@ -1292,3 +1670,38 @@ async def handle_oauth_webhook(hass, webhook_id, request): return web.Response( body=oauth_redirect_page(hass.config.language, 'fail'), content_type='text/html') + + +def _handle_devices_filter( + devices: dict, logic_or: bool, item_in: dict, item_ex: dict +) -> dict: + """Private method to filter devices.""" + 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} diff --git a/custom_components/xiaomi_home/miot/i18n/de.json b/custom_components/xiaomi_home/miot/i18n/de.json index afcea1d..81fb203 100644 --- a/custom_components/xiaomi_home/miot/i18n/de.json +++ b/custom_components/xiaomi_home/miot/i18n/de.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "Geräte", - "found_central_gateway": ", lokales zentrales Gateway gefunden" + "found_central_gateway": ", lokales zentrales Gateway gefunden", + "without_room": "Kein Raum zugewiesen", + "no_display": "nicht anzeigen" }, "control_mode": { "auto": "automatisch", "cloud": "Cloud" }, + "statistics_logic": { + "or": "ODER-Logik", + "and": "UND-Logik" + }, + "filter_mode": { + "exclude": "ausschließen", + "include": "einschließen" + }, + "connect_type": { + "0": "WiFi", + "1": "yunyi-Gerät", + "2": "Cloud-Gerät", + "3": "ZigBee", + "4": "webSocket", + "5": "virtuelles Gerät", + "6": "BLE", + "7": "lokaler AP", + "8": "WiFi+BLE", + "9": "andere", + "10": "Funktions-Plug-in", + "11": "Zellnetz", + "12": "Kabel", + "13": "NB-IoT", + "14": "Drittanbieter-Cloud-Zugriff", + "15": "Infrarot-Fernbedienungsgerät", + "16": "BLE-Mesh", + "17": "virtuelle Gerätegruppe", + "18": "Gateway-Untergerät", + "19": "Sicherheitsstufe Gateway-Untergerät", + "22": "PLC", + "23": "nur Kabel", + "24": "Matter", + "25": "WiFi+Zellnetz" + }, "room_name_rule": { "none": "nicht synchronisieren", "home_room": "Hausname und Raumname (Xiaomi Home Schlafzimmer)", @@ -18,6 +54,11 @@ "enable": "aktivieren", "disable": "deaktivieren" }, + "device_state": { + "add": "hinzufügen", + "del": "nicht verfügbar", + "offline": "offline" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[Hinweis]** Es wurden mehrere Netzwerkkarten erkannt, die möglicherweise mit demselben Netzwerk verbunden sind. Bitte achten Sie auf die Auswahl.", "net_unavailable": "Schnittstelle nicht verfügbar" diff --git a/custom_components/xiaomi_home/miot/i18n/en.json b/custom_components/xiaomi_home/miot/i18n/en.json index 49430b3..219b276 100644 --- a/custom_components/xiaomi_home/miot/i18n/en.json +++ b/custom_components/xiaomi_home/miot/i18n/en.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "Devices", - "found_central_gateway": ", Found Local Central Hub Gateway" + "found_central_gateway": ", Found Local Central Hub Gateway", + "without_room": "No room assigned", + "no_display": "Do not display" }, "control_mode": { "auto": "Auto", "cloud": "Cloud" }, + "statistics_logic": { + "or": "OR logic", + "and": "AND logic" + }, + "filter_mode": { + "exclude": "Exclude", + "include": "Include" + }, + "connect_type": { + "0": "WiFi", + "1": "yunyi device", + "2": "Cloud device", + "3": "ZigBee", + "4": "webSocket", + "5": "Virtual device", + "6": "BLE", + "7": "Local AP", + "8": "WiFi+BLE", + "9": "Other", + "10": "Function plug-in", + "11": "Cellular network", + "12": "Cable", + "13": "NB-IoT", + "14": "Third-party cloud access", + "15": "Infrared remote control device", + "16": "BLE-Mesh", + "17": "Virtual device group", + "18": "Gateway sub-device", + "19": "Security level gateway sub-device", + "22": "PLC", + "23": "Cable only", + "24": "Matter", + "25": "WiFi+Cellular network" + }, "room_name_rule": { "none": "Do not synchronize", "home_room": "Home Name and Room Name (Xiaomi Home Bedroom)", @@ -18,6 +54,11 @@ "enable": "Enable", "disable": "Disable" }, + "device_state": { + "add": "Add", + "del": "Unavailable", + "offline": "Offline" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[Notice]** Multiple network cards detected that may be connected to the same network. Please pay attention to the selection.", "net_unavailable": "Interface unavailable" diff --git a/custom_components/xiaomi_home/miot/i18n/es.json b/custom_components/xiaomi_home/miot/i18n/es.json index 5ca9862..49a6ea6 100644 --- a/custom_components/xiaomi_home/miot/i18n/es.json +++ b/custom_components/xiaomi_home/miot/i18n/es.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "dispositivos", - "found_central_gateway": ", se encontró la puerta de enlace central local" + "found_central_gateway": ", se encontró la puerta de enlace central local", + "without_room": "Sin habitación asignada", + "no_display": "no mostrar" }, "control_mode": { "auto": "automático", "cloud": "nube" }, + "statistics_logic": { + "or": "lógica OR", + "and": "lógica AND" + }, + "filter_mode": { + "exclude": "excluir", + "include": "incluir" + }, + "connect_type": { + "0": "WiFi", + "1": "dispositivo yunyi", + "2": "dispositivo de nube", + "3": "ZigBee", + "4": "webSocket", + "5": "dispositivo virtual", + "6": "BLE", + "7": "AP local", + "8": "WiFi+BLE", + "9": "otro", + "10": "complemento de función", + "11": "red celular", + "12": "cable", + "13": "NB-IoT", + "14": "acceso a la nube de terceros", + "15": "dispositivo de control remoto infrarrojo", + "16": "BLE-Mesh", + "17": "grupo de dispositivos virtuales", + "18": "subdispositivo de puerta de enlace", + "19": "subdispositivo de puerta de enlace de nivel de seguridad", + "22": "PLC", + "23": "solo cable", + "24": "Matter", + "25": "WiFi+red celular" + }, "room_name_rule": { "none": "no sincronizar", "home_room": "nombre de la casa y nombre de la habitación (Xiaomi Home Dormitorio)", @@ -18,6 +54,11 @@ "enable": "habilitar", "disable": "deshabilitar" }, + "device_state": { + "add": "agregar", + "del": "no disponible", + "offline": "fuera de línea" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[Aviso]** Se detectaron varias tarjetas de red que pueden estar conectadas a la misma red. Por favor, preste atención a la selección.", "net_unavailable": "Interfaz no disponible" diff --git a/custom_components/xiaomi_home/miot/i18n/fr.json b/custom_components/xiaomi_home/miot/i18n/fr.json index 677bf27..40feb65 100644 --- a/custom_components/xiaomi_home/miot/i18n/fr.json +++ b/custom_components/xiaomi_home/miot/i18n/fr.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "appareils", - "found_central_gateway": ", passerelle centrale locale trouvée" + "found_central_gateway": ", passerelle centrale locale trouvée", + "without_room": "Aucune pièce attribuée", + "no_display": "ne pas afficher" }, "control_mode": { "auto": "automatique", "cloud": "cloud" }, + "statistics_logic": { + "or": "ou logique", + "and": "et logique" + }, + "filter_mode": { + "exclude": "exclure", + "include": "inclure" + }, + "connect_type": { + "0": "WiFi", + "1": "appareil yunyi", + "2": "appareil cloud", + "3": "ZigBee", + "4": "webSocket", + "5": "appareil virtuel", + "6": "BLE", + "7": "AP local", + "8": "WiFi+BLE", + "9": "autre", + "10": "plug-in de fonction", + "11": "réseau cellulaire", + "12": "câble", + "13": "NB-IoT", + "14": "accès cloud tiers", + "15": "appareil de télécommande infrarouge", + "16": "BLE-Mesh", + "17": "groupe d'appareils virtuels", + "18": "sous-appareil de passerelle", + "19": "niveau de sécurité sous-appareil de passerelle", + "22": "PLC", + "23": "câble uniquement", + "24": "Matter", + "25": "WiFi+réseau cellulaire" + }, "room_name_rule": { "none": "ne pas synchroniser", "home_room": "nom de la maison et nom de la pièce (Xiaomi Home Chambre)", @@ -18,6 +54,11 @@ "enable": "activer", "disable": "désactiver" }, + "device_state": { + "add": "Ajouter", + "del": "Supprimer", + "offline": "hors ligne" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[Remarque]** Plusieurs cartes réseau détectées qui peuvent être connectées au même réseau. Veuillez faire attention à la sélection.", "net_unavailable": "Interface non disponible" diff --git a/custom_components/xiaomi_home/miot/i18n/ja.json b/custom_components/xiaomi_home/miot/i18n/ja.json index 3980c1e..3ffc22a 100644 --- a/custom_components/xiaomi_home/miot/i18n/ja.json +++ b/custom_components/xiaomi_home/miot/i18n/ja.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "デバイス", - "found_central_gateway": "、ローカル中央ゲートウェイが見つかりました" + "found_central_gateway": "、ローカル中央ゲートウェイが見つかりました", + "without_room": "部屋が割り当てられていません", + "no_display": "表示しない" }, "control_mode": { "auto": "自動", "cloud": "クラウド" }, + "statistics_logic": { + "or": "ORロジック", + "and": "ANDロジック" + }, + "filter_mode": { + "exclude": "除外", + "include": "含む" + }, + "connect_type": { + "0": "WiFi", + "1": "yunyiデバイス", + "2": "クラウドデバイス", + "3": "ZigBee", + "4": "webSocket", + "5": "仮想デバイス", + "6": "BLE", + "7": "ローカルAP", + "8": "WiFi+BLE", + "9": "その他", + "10": "機能プラグイン", + "11": "セルラーネットワーク", + "12": "ケーブル", + "13": "NB-IoT", + "14": "サードパーティクラウドアクセス", + "15": "赤外線リモコンデバイス", + "16": "BLE-Mesh", + "17": "仮想デバイスグループ", + "18": "ゲートウェイサブデバイス", + "19": "セキュリティレベルゲートウェイサブデバイス", + "22": "PLC", + "23": "ケーブルのみ", + "24": "Matter", + "25": "WiFi+セルラーネットワーク" + }, "room_name_rule": { "none": "同期しない", "home_room": "家の名前と部屋の名前 (Xiaomi Home 寝室)", @@ -18,6 +54,11 @@ "enable": "有効", "disable": "無効" }, + "device_state": { + "add": "追加", + "del": "利用不可", + "offline": "オフライン" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[注意]** 複数のネットワークカードが同じネットワークに接続されている可能性があります。選択に注意してください。", "net_unavailable": "インターフェースが利用できません" diff --git a/custom_components/xiaomi_home/miot/i18n/nl.json b/custom_components/xiaomi_home/miot/i18n/nl.json index 03dfc32..101ff3a 100644 --- a/custom_components/xiaomi_home/miot/i18n/nl.json +++ b/custom_components/xiaomi_home/miot/i18n/nl.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "Apparaten", - "found_central_gateway": ", Lokale centrale hub-gateway gevonden" + "found_central_gateway": ", Lokale centrale hub-gateway gevonden", + "without_room": "Niet toegewezen kamer", + "no_display": "Niet weergeven" }, "control_mode": { "auto": "Automatisch", "cloud": "Cloud" }, + "statistics_logic": { + "or": "OF-logica", + "and": "EN-logica" + }, + "filter_mode": { + "exclude": "Uitsluiten", + "include": "Inclusief" + }, + "connect_type": { + "0": "WiFi", + "1": "yunyi-apparaat", + "2": "Cloudapparaat", + "3": "ZigBee", + "4": "webSocket", + "5": "Virtueel apparaat", + "6": "BLE", + "7": "Lokaal AP", + "8": "WiFi+BLE", + "9": "Andere", + "10": "Functieplug-in", + "11": "Cellulair netwerk", + "12": "Kabel", + "13": "NB-IoT", + "14": "Toegang van derden tot de cloud", + "15": "Infrarood afstandsbedieningsapparaat", + "16": "BLE-Mesh", + "17": "Virtuele apparaatgroep", + "18": "Gateway sub-apparaat", + "19": "Beveiligingsniveau gateway sub-apparaat", + "22": "PLC", + "23": "Alleen kabel", + "24": "Matter", + "25": "WiFi+Cellulair netwerk" + }, "room_name_rule": { "none": "Niet synchroniseren", "home_room": "Huisnaam en Kamernaam (Xiaomi Home Slaapkamer)", @@ -18,6 +54,11 @@ "enable": "Inschakelen", "disable": "Uitschakelen" }, + "device_state": { + "add": "Toevoegen", + "del": "Niet beschikbaar", + "offline": "Offline" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[Let op]** Meerdere netwerkkaarten gedetecteerd die mogelijk zijn verbonden met hetzelfde netwerk. Let op bij de selectie.", "net_unavailable": "Interface niet beschikbaar" @@ -92,4 +133,4 @@ "-706014006": "Apparaatbeschrijving niet gevonden" } } -} +} \ No newline at end of file diff --git a/custom_components/xiaomi_home/miot/i18n/pt-BR.json b/custom_components/xiaomi_home/miot/i18n/pt-BR.json index 8df352b..8e37ecb 100644 --- a/custom_components/xiaomi_home/miot/i18n/pt-BR.json +++ b/custom_components/xiaomi_home/miot/i18n/pt-BR.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "dispositivos", - "found_central_gateway": "encontrado o gateway central local" + "found_central_gateway": "encontrado o gateway central local", + "without_room": "sem quarto atribuído", + "no_display": "não exibir" }, "control_mode": { "auto": "automático", "cloud": "nuvem" }, + "statistics_logic": { + "or": "lógica OU", + "and": "lógica E" + }, + "filter_mode": { + "exclude": "excluir", + "include": "incluir" + }, + "connect_type": { + "0": "WiFi", + "1": "dispositivo yunyi", + "2": "dispositivo de nuvem", + "3": "ZigBee", + "4": "webSocket", + "5": "dispositivo virtual", + "6": "BLE", + "7": "AP local", + "8": "WiFi+BLE", + "9": "outro", + "10": "plug-in de função", + "11": "rede celular", + "12": "cabo", + "13": "NB-IoT", + "14": "acesso à nuvem de terceiros", + "15": "dispositivo de controle remoto infravermelho", + "16": "BLE-Mesh", + "17": "grupo de dispositivos virtuais", + "18": "subdispositivo de gateway", + "19": "subdispositivo de gateway de nível de segurança", + "22": "PLC", + "23": "somente cabo", + "24": "Matter", + "25": "WiFi+rede celular" + }, "room_name_rule": { "none": "não sincronizado", "home_room": "Nome da casa e nome do quarto (Xiaomi Home Quarto)", @@ -18,6 +54,11 @@ "enable": "habilitado", "disable": "desabilitado" }, + "device_state": { + "add": "adicionar", + "del": "indisponível", + "offline": "offline" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[Aviso]** Detectado múltiplas interfaces de rede que podem estar conectando à mesma rede, por favor, selecione a correta.", "net_unavailable": "Interface indisponível" diff --git a/custom_components/xiaomi_home/miot/i18n/pt.json b/custom_components/xiaomi_home/miot/i18n/pt.json index dd30774..08afe4d 100644 --- a/custom_components/xiaomi_home/miot/i18n/pt.json +++ b/custom_components/xiaomi_home/miot/i18n/pt.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "dispositivos", - "found_central_gateway": ", encontrou a central de gateway local" + "found_central_gateway": ", encontrou a central de gateway local", + "without_room": "Sem quarto atribuído", + "no_display": "Não exibir" }, "control_mode": { "auto": "Automático", "cloud": "Nuvem" }, + "statistics_logic": { + "or": "Ou lógica", + "and": "E lógica" + }, + "filter_mode": { + "exclude": "Excluir", + "include": "Incluir" + }, + "connect_type": { + "0": "WiFi", + "1": "dispositivo yunyi", + "2": "dispositivo de nuvem", + "3": "ZigBee", + "4": "webSocket", + "5": "dispositivo virtual", + "6": "BLE", + "7": "AP local", + "8": "WiFi+BLE", + "9": "outro", + "10": "plug-in de função", + "11": "rede celular", + "12": "cabo", + "13": "NB-IoT", + "14": "acesso à nuvem de terceiros", + "15": "dispositivo de controle remoto infravermelho", + "16": "BLE-Mesh", + "17": "grupo de dispositivos virtuais", + "18": "subdispositivo de gateway", + "19": "subdispositivo de gateway de nível de segurança", + "22": "PLC", + "23": "somente cabo", + "24": "Matter", + "25": "WiFi+rede celular" + }, "room_name_rule": { "none": "Não sincronizar", "home_room": "Nome da casa e Nome do quarto (Xiaomi Home Quarto)", @@ -18,6 +54,11 @@ "enable": "Habilitar", "disable": "Desabilitar" }, + "device_state": { + "add": "Adicionar", + "del": "Indisponível", + "offline": "Offline" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[Aviso]** Detectado que várias interfaces podem estar conectadas à mesma rede, escolha com cuidado.", "net_unavailable": "Interface indisponível" diff --git a/custom_components/xiaomi_home/miot/i18n/ru.json b/custom_components/xiaomi_home/miot/i18n/ru.json index 18f8490..d018603 100644 --- a/custom_components/xiaomi_home/miot/i18n/ru.json +++ b/custom_components/xiaomi_home/miot/i18n/ru.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "устройства", - "found_central_gateway": ", найден локальный центральный шлюз" + "found_central_gateway": ", найден локальный центральный шлюз", + "without_room": "без комнаты", + "no_display": "не отображать" }, "control_mode": { "auto": "автоматический", "cloud": "облако" }, + "statistics_logic": { + "or": "логика ИЛИ", + "and": "логика И" + }, + "filter_mode": { + "exclude": "исключить", + "include": "включить" + }, + "connect_type": { + "0": "WiFi", + "1": "yunyi устройство", + "2": "Облачное устройство", + "3": "ZigBee", + "4": "webSocket", + "5": "Виртуальное устройство", + "6": "BLE", + "7": "Локальный AP", + "8": "WiFi+BLE", + "9": "Другое", + "10": "Плагин функции", + "11": "Сотовая сеть", + "12": "Кабель", + "13": "NB-IoT", + "14": "Доступ к стороннему облаку", + "15": "Устройство с ИК-пультом дистанционного управления", + "16": "BLE-Mesh", + "17": "Группа виртуальных устройств", + "18": "Подустройство шлюза", + "19": "Подустройство шлюза уровня безопасности", + "22": "PLC", + "23": "Только кабель", + "24": "Matter", + "25": "WiFi+Сотовая сеть" + }, "room_name_rule": { "none": "не синхронизировать", "home_room": "название дома и название комнаты (Xiaomi Home Спальня)", @@ -18,6 +54,11 @@ "enable": "Включить", "disable": "Отключить" }, + "device_state": { + "add": "Добавить", + "del": "Недоступно", + "offline": "Не в сети" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[Уведомление]** Обнаружено несколько сетевых карт, которые могут быть подключены к одной и той же сети. Пожалуйста, обратите внимание на выбор.", "net_unavailable": "Интерфейс недоступен" diff --git a/custom_components/xiaomi_home/miot/i18n/zh-Hans.json b/custom_components/xiaomi_home/miot/i18n/zh-Hans.json index 1607a5e..d8f7c8a 100644 --- a/custom_components/xiaomi_home/miot/i18n/zh-Hans.json +++ b/custom_components/xiaomi_home/miot/i18n/zh-Hans.json @@ -2,7 +2,9 @@ "config": { "other": { "devices": "个设备", - "found_central_gateway": ",发现本地中枢网关" + "found_central_gateway": ",发现本地中枢网关", + "without_room": "未分配房间", + "no_display": "不显示" }, "control_mode": { "auto": "自动", @@ -14,10 +16,49 @@ "room": "房间名 (卧室)", "home": "家庭名 (米家)" }, + "statistics_logic": { + "or": "或逻辑", + "and": "与逻辑" + }, + "filter_mode": { + "exclude": "排除", + "include": "包含" + }, + "connect_type": { + "0": "WiFi", + "1": "yunyi设备", + "2": "云接入设备", + "3": "ZigBee", + "4": "webSocket", + "5": "虚拟设备", + "6": "BLE", + "7": "本地AP", + "8": "WiFi+BLE", + "9": "其他", + "10": "功能插件", + "11": "蜂窝网", + "12": "网线", + "13": "NB-IoT", + "14": "第三方云接入", + "15": "红外遥控器子设备", + "16": "BLE-Mesh", + "17": "虚拟设备组", + "18": "代理网关子设备", + "19": "安全级代理网关子设备", + "22": "PLC", + "23": "仅网线", + "24": "Matter", + "25": "WiFi+蜂窝网" + }, "option_status": { "enable": "启用", "disable": "禁用" }, + "device_state": { + "add": "新增", + "del": "不可用", + "offline": "离线" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[提示]** 检测到多个网卡可能连接同一个网络,请注意选择。", "net_unavailable": "接口不可用" diff --git a/custom_components/xiaomi_home/miot/i18n/zh-Hant.json b/custom_components/xiaomi_home/miot/i18n/zh-Hant.json index e62b4f9..73bfa98 100644 --- a/custom_components/xiaomi_home/miot/i18n/zh-Hant.json +++ b/custom_components/xiaomi_home/miot/i18n/zh-Hant.json @@ -2,12 +2,48 @@ "config": { "other": { "devices": "個設備", - "found_central_gateway": ",發現本地中樞網關" + "found_central_gateway": ",發現本地中樞網關", + "without_room": "未分配房間", + "no_display": "不顯示" }, "control_mode": { "auto": "自動", "cloud": "雲端" }, + "statistics_logic": { + "or": "或邏輯", + "and": "與邏輯" + }, + "filter_mode": { + "exclude": "排除", + "include": "包含" + }, + "connect_type": { + "0": "WiFi", + "1": "yunyi設備", + "2": "雲接入設備", + "3": "ZigBee", + "4": "webSocket", + "5": "虛擬設備", + "6": "BLE", + "7": "本地AP", + "8": "WiFi+BLE", + "9": "其他", + "10": "功能插件", + "11": "蜂窩網", + "12": "網線", + "13": "NB-IoT", + "14": "第三方雲接入", + "15": "紅外遙控器子設備", + "16": "BLE-Mesh", + "17": "虛擬設備組", + "18": "代理網關子設備", + "19": "安全級代理網關子設備", + "22": "PLC", + "23": "僅網線", + "24": "Matter", + "25": "WiFi+蜂窩網" + }, "room_name_rule": { "none": "不同步", "home_room": "家庭名 和 房間名 (米家 臥室)", @@ -18,6 +54,11 @@ "enable": "啟用", "disable": "禁用" }, + "device_state": { + "add": "新增", + "del": "不可用", + "offline": "離線" + }, "lan_ctrl_config": { "notice_net_dup": "\r\n**[提示]** 檢測到多個網卡可能連接同一個網絡,請注意選擇。", "net_unavailable": "接口不可用" diff --git a/custom_components/xiaomi_home/miot/miot_client.py b/custom_components/xiaomi_home/miot/miot_client.py index da8e2b6..9c57c34 100644 --- a/custom_components/xiaomi_home/miot/miot_client.py +++ b/custom_components/xiaomi_home/miot/miot_client.py @@ -164,9 +164,11 @@ class MIoTClient: _refresh_props_retry_count: int # Persistence notify handler, params: notify_id, title, message - _persistence_notify: Callable[[str, str, str], None] + _persistence_notify: Callable[[str, Optional[str], Optional[str]], None] # Device list changed notify _show_devices_changed_notify_timer: Optional[asyncio.TimerHandle] + # Display devices changed notify + _display_devs_notify: list[str] def __init__( self, @@ -231,6 +233,9 @@ def __init__( self._persistence_notify = None self._show_devices_changed_notify_timer = None + self._display_devs_notify = entry_data.get( + 'display_devices_changed_notify', ['add', 'del', 'offline']) + async def init_async(self) -> None: # Load user config and check self._user_config = await self._storage.load_user_config_async( @@ -239,14 +244,14 @@ async def init_async(self) -> None: # Integration need to be add again raise MIoTClientError('load_user_config_async error') _LOGGER.debug('user config, %s', json.dumps(self._user_config)) - # Load cache device list - await self.__load_cache_device_async() # MIoT i18n client self._i18n = MIoTI18n( lang=self._entry_data.get( 'integration_language', DEFAULT_INTEGRATION_LANGUAGE), loop=self._main_loop) await self._i18n.init_async() + # Load cache device list + await self.__load_cache_device_async() # MIoT oauth client instance self._oauth = MIoTOauthClient( client_id=OAUTH2_CLIENT_ID, @@ -459,6 +464,21 @@ def hide_non_standard_entities(self) -> bool: return self._entry_data.get( 'hide_non_standard_entities', False) + @property + def display_devices_changed_notify(self) -> list[str]: + return self._display_devs_notify + + @display_devices_changed_notify.setter + def display_devices_changed_notify(self, value: list[str]) -> None: + if set(value) == set(self._display_devs_notify): + return + self._display_devs_notify = value + if value: + self.__request_show_devices_changed_notify() + else: + self._persistence_notify( + self.__gen_notify_key('dev_list_changed'), None, None) + @property def device_list(self) -> dict: return self._device_list_cache @@ -1716,15 +1736,16 @@ def __show_devices_changed_notify(self) -> None: count_offline: int = 0 # New devices - for did, info in { - **self._device_list_gateway, **self._device_list_cloud - }.items(): - if did in self._device_list_cache: - continue - count_add += 1 - message_add += ( - f'- {info.get("name", "unknown")} ({did}, ' - f'{info.get("model", "unknown")})\n') + if 'add' in self._display_devs_notify: + for did, info in { + **self._device_list_gateway, **self._device_list_cloud + }.items(): + if did in self._device_list_cache: + continue + count_add += 1 + message_add += ( + f'- {info.get("name", "unknown")} ({did}, ' + f'{info.get("model", "unknown")})\n') # Get unavailable and offline devices home_name_del: Optional[str] = None home_name_offline: Optional[str] = None @@ -1734,7 +1755,7 @@ def __show_devices_changed_notify(self) -> None: if online: # Skip online device continue - if online is None: + if 'del' in self._display_devs_notify and online is None: # Device not exist if home_name_del != home_name_new: message_del += f'\n[{home_name_new}]\n' @@ -1743,7 +1764,8 @@ def __show_devices_changed_notify(self) -> None: message_del += ( f'- {info.get("name", "unknown")} ({did}, ' f'{info.get("room_name", "unknown")})\n') - else: + continue + if 'offline' in self._display_devs_notify: # Device offline if home_name_offline != home_name_new: message_offline += f'\n[{home_name_new}]\n' @@ -1754,19 +1776,19 @@ def __show_devices_changed_notify(self) -> None: f'{info.get("room_name", "unknown")})\n') message = '' - if count_add: + if 'add' in self._display_devs_notify and count_add: message += self._i18n.translate( key='miot.client.device_list_add', replace={ 'count': count_add, 'message': message_add}) - if count_del: + if 'del' in self._display_devs_notify and count_del: message += self._i18n.translate( key='miot.client.device_list_del', replace={ 'count': count_del, 'message': message_del}) - if count_offline: + if 'offline' in self._display_devs_notify and count_offline: message += self._i18n.translate( key='miot.client.device_list_offline', replace={ @@ -1801,6 +1823,8 @@ def __show_devices_changed_notify(self) -> None: def __request_show_devices_changed_notify( self, delay_sec: float = 6 ) -> None: + if not self._display_devs_notify: + return if not self._mips_cloud and not self._mips_local and not self._miot_lan: return if self._show_devices_changed_notify_timer: diff --git a/custom_components/xiaomi_home/miot/miot_cloud.py b/custom_components/xiaomi_home/miot/miot_cloud.py index 036ea57..4632a6c 100644 --- a/custom_components/xiaomi_home/miot/miot_cloud.py +++ b/custom_components/xiaomi_home/miot/miot_cloud.py @@ -254,6 +254,13 @@ def __init__( self._session = aiohttp.ClientSession(loop=self._main_loop) async def deinit_async(self) -> None: + if self._get_prop_timer: + 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() + self._get_prop_list.clear() if self._session and not self._session.closed: await self._session.close() diff --git a/custom_components/xiaomi_home/miot/miot_i18n.py b/custom_components/xiaomi_home/miot/miot_i18n.py index 5f11694..152bc08 100644 --- a/custom_components/xiaomi_home/miot/miot_i18n.py +++ b/custom_components/xiaomi_home/miot/miot_i18n.py @@ -71,7 +71,7 @@ def __init__( ) -> None: self._main_loop = loop or asyncio.get_event_loop() self._lang = lang - self._data = None + self._data = {} async def init_async(self) -> None: if self._data: @@ -94,7 +94,7 @@ async def init_async(self) -> None: self._data = data async def deinit_async(self) -> None: - self._data = None + self._data = {} def translate( self, key: str, replace: Optional[dict[str, str]] = None diff --git a/custom_components/xiaomi_home/miot/miot_mdns.py b/custom_components/xiaomi_home/miot/miot_mdns.py index 4f13649..a6b3002 100644 --- a/custom_components/xiaomi_home/miot/miot_mdns.py +++ b/custom_components/xiaomi_home/miot/miot_mdns.py @@ -50,7 +50,7 @@ import binascii import copy from enum import Enum -from typing import Callable, Optional +from typing import Callable, Coroutine, Optional import logging from zeroconf import ( @@ -98,8 +98,8 @@ class MipsServiceData: def __init__(self, service_info: AsyncServiceInfo) -> None: if service_info is None: raise MipsServiceError('invalid params') - properties = service_info.decoded_properties - if properties is None: + properties: dict = service_info.decoded_properties + if not properties: raise MipsServiceError('invalid service properties') self.profile = properties.get('profile', None) if self.profile is None: @@ -111,9 +111,11 @@ def __init__(self, service_info: AsyncServiceInfo) -> None: if not self.addresses: raise MipsServiceError('invalid addresses') self.addresses.sort() + if not service_info.port: + raise MipsServiceError('invalid port') self.port = service_info.port self.type = service_info.type - self.server = service_info.server + self.server = service_info.server or '' # Parse profile self.did = str(int.from_bytes(self.profile_bin[1:9])) self.group_id = binascii.hexlify( @@ -150,8 +152,8 @@ class MipsService: _aio_browser: AsyncServiceBrowser _services: dict[str, dict] # key = (key, group_id) - _sub_list: dict[(str, str), Callable[[ - str, MipsServiceState, dict], asyncio.Future]] + _sub_list: dict[tuple[str, str], Callable[[ + str, MipsServiceState, dict], Coroutine]] def __init__( self, aiozc: AsyncZeroconf, @@ -159,7 +161,6 @@ def __init__( ) -> None: self._aiozc = aiozc self._main_loop = loop or asyncio.get_running_loop() - self._aio_browser = None self._services = {} self._sub_list = {} @@ -207,7 +208,7 @@ def get_services(self, group_id: Optional[str] = None) -> dict[str, dict]: def sub_service_change( self, key: str, group_id: str, - handler: Callable[[str, MipsServiceState, dict], asyncio.Future] + handler: Callable[[str, MipsServiceState, dict], Coroutine] ) -> None: if key is None or group_id is None or handler is None: raise MipsServiceError('invalid params') @@ -232,7 +233,7 @@ def __on_service_state_change( for item in list(self._services.values()): if item['name'] != name: continue - service_data = self._services.pop(item['group_id'], None) + service_data = self._services.pop(item['group_id'], {}) self.__call_service_change( state=MipsServiceState.REMOVED, data=service_data) return @@ -275,10 +276,10 @@ async def __request_service_info_async( _LOGGER.error('invalid mips service, %s, %s', error, info) def __call_service_change( - self, state: MipsServiceState, data: dict = None + self, state: MipsServiceState, data: dict ) -> None: _LOGGER.info('call service change, %s, %s', state, data) for keys in list(self._sub_list.keys()): - if keys[1] in [data['group_id'], '*']: + if keys[1] in [data.get('group_id', None), '*']: self._main_loop.create_task( self._sub_list[keys](data['group_id'], state, data)) diff --git a/custom_components/xiaomi_home/translations/de.json b/custom_components/xiaomi_home/translations/de.json index b06c460..c027233 100644 --- a/custom_components/xiaomi_home/translations/de.json +++ b/custom_components/xiaomi_home/translations/de.json @@ -24,13 +24,37 @@ }, "homes_select": { "title": "Familie und Geräte auswählen", - "description": "## Gebrauchsanweisung\r\n### Steuerungsmodus\r\n- Automatisch: Wenn im lokalen Netzwerk ein verfügbarer Xiaomi-Zentralgateway vorhanden ist, wird Home Assistant bevorzugt Steuerbefehle über den Zentralgateway senden, um eine lokale Steuerung zu ermöglichen. Wenn im lokalen Netzwerk kein Zentralgateway vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden, um eine lokale Steuerung zu ermöglichen. Nur wenn die oben genannten Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Steuerbefehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden nur über die Cloud gesendet.\r\n### Familienimport für importierte Geräte\r\nDie Integration fügt Geräte aus den ausgewählten Familien hinzu.\r\n### Raumnamensynchronisationsmodus\r\nWenn Geräte von der Xiaomi Home App zu Home Assistant synchronisiert werden, wird die Bezeichnung des Bereichs, in dem sich die Geräte in Home Assistant befinden, nach folgenden Regeln benannt. Beachten Sie, dass das Synchronisieren von Geräten den von Xiaomi Home App festgelegten Familien- und Raum-Einstellungen nicht ändert.\r\n- Nicht synchronisieren: Das Gerät wird keinem Bereich hinzugefügt.\r\n- Andere Optionen: Der Bereich, in den das Gerät aufgenommen wird, wird nach dem Namen der Familie oder des Raums in der Xiaomi Home App benannt.\r\n### Action-Debug-Modus\r\nFür von MIoT-Spec-V2 definierte Gerätemethoden wird neben der Benachrichtigungs-Entität auch eine Texteingabe-Entität generiert. Damit können Sie bei der Fehlerbehebung Steuerbefehle an das Gerät senden.\r\n### Verstecke Nicht-Standard-Entitäten\r\nVerstecke Entitäten, die von nicht standardmäßigen MIoT-Spec-V2-Instanzen mit einem Namen beginnen, der mit einem \"*\" beginnt.\r\n\r\n \r\n### Hallo {nick_name}! Bitte wählen Sie den Steuerungsmodus der Integration sowie die Familie aus, in der sich die hinzuzufügenden Geräte befinden.", + "description": "## Gebrauchsanweisung\r\n### Familienimport für importierte Geräte\r\nDie Integration fügt Geräte aus den ausgewählten Familien hinzu.\r\n### Raumnamensynchronisationsmodus\r\nWenn Geräte von der Xiaomi Home App zu Home Assistant synchronisiert werden, wird die Bezeichnung des Bereichs, in dem sich die Geräte in Home Assistant befinden, nach folgenden Regeln benannt. Beachten Sie, dass das Synchronisieren von Geräten den von Xiaomi Home App festgelegten Familien- und Raum-Einstellungen nicht ändert.\r\n- Nicht synchronisieren: Das Gerät wird keinem Bereich hinzugefügt.\r\n- Andere Optionen: Der Bereich, in den das Gerät aufgenommen wird, wird nach dem Namen der Familie oder des Raums in der Xiaomi Home App benannt.\r\n### Erweiterte Einstellungen\r\nZeigen Sie erweiterte Einstellungen an, um die professionellen Konfigurationsoptionen der Integration zu ändern.\r\n\r\n \r\n### {nick_name} Hallo! Bitte wählen Sie die Familie aus, zu der Sie das Gerät hinzufügen möchten.", "data": { - "ctrl_mode": "Steuerungsmodus", "home_infos": "Familienimport für importierte Geräte", "area_name_rule": "Raumnamensynchronisationsmodus", + "advanced_options": "Erweiterte Einstellungen" + } + }, + "advanced_options": { + "title": "Erweiterte Einstellungen", + "description": "## Gebrauchsanweisung\r\n### Wenn Sie die Bedeutung der folgenden Optionen nicht genau kennen, belassen Sie sie bitte auf den Standardeinstellungen.\r\n### Geräte filtern\r\nUnterstützt das Filtern von Geräten nach Raumnamen und Gerätetypen sowie das Filtern nach Gerätedimensionen.\r\n\r\n### Steuerungsmodus\r\n- Automatisch: Wenn ein verfügbarer Xiaomi-Hub im lokalen Netzwerk vorhanden ist, priorisiert Home Assistant das Senden von Steuerbefehlen über den Hub, um eine lokale Steuerung zu ermöglichen. Wenn kein Hub vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden. Nur wenn diese Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Befehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden ausschließlich über die Cloud gesendet.\r\n### Action-Debug-Modus\r\nFür Methoden, die von MIoT-Spec-V2-Geräten definiert werden, wird neben der Benachrichtigungsentität auch eine Texteingabe-Entität erstellt, mit der Sie während des Debuggens Steuerbefehle an das Gerät senden können.\r\n### Nicht standardmäßige Entitäten ausblenden\r\nBlendet Entitäten aus, die von nicht-standardmäßigen MIoT-Spec-V2-Instanzen generiert werden und deren Name mit „*“ beginnt.\r\n### Gerätestatusänderungen anzeigen\r\nDetaillierte Anzeige von Gerätestatusänderungen, es werden nur die ausgewählten Benachrichtigungen angezeigt.", + "data": { + "devices_filter": "Geräte filtern", + "ctrl_mode": "Steuerungsmodus", "action_debug": "Action-Debug-Modus", - "hide_non_standard_entities": "Verstecke Nicht-Standard-Entitäten" + "hide_non_standard_entities": "Nicht standardmäßige Entitäten ausblenden", + "display_devices_changed_notify": "Gerätestatusänderungen anzeigen" + } + }, + "devices_filter": { + "title": "Geräte filtern", + "description": "## Gebrauchsanweisung\r\nUnterstützt das Filtern von Geräten nach Raumnamen, Gerätezugriffstyp und Gerätemodell, und unterstützt auch das Filtern nach Gerätegröße. Die Filterlogik ist wie folgt:\r\n- Zuerst wird gemäß der statistischen Logik die Vereinigung oder der Schnittpunkt aller eingeschlossenen Elemente ermittelt, dann der Schnittpunkt oder die Vereinigung der ausgeschlossenen Elemente, und schließlich wird das [eingeschlossene Gesamtergebnis] vom [ausgeschlossenen Gesamtergebnis] subtrahiert, um das [Filterergebnis] zu erhalten.\r\n- Wenn keine eingeschlossenen Elemente ausgewählt sind, bedeutet dies, dass alle eingeschlossen sind.\r\n### Filtermodus\r\n- Ausschließen: Unerwünschte Elemente entfernen.\r\n- Einschließen: Gewünschte Elemente einschließen.\r\n### Statistische Logik\r\n- UND-Logik: Den Schnittpunkt aller Elemente im gleichen Modus nehmen.\r\n- ODER-Logik: Die Vereinigung aller Elemente im gleichen Modus nehmen.\r\n\r\nSie können auch zur Seite [Konfiguration > Geräteliste aktualisieren] des Integrationselements gehen, [Geräte filtern] auswählen, um erneut zu filtern.", + "data": { + "room_filter_mode": "Familienraum filtern", + "room_list": "Familienraum", + "type_filter_mode": "Gerätetyp filtern", + "type_list": "Gerätetyp", + "model_filter_mode": "Gerätemodell filtern", + "model_list": "Gerätemodell", + "devices_filter_mode": "Geräte filtern", + "device_list": "Geräteliste", + "statistics_logic": "Statistiklogik" } } }, @@ -76,8 +100,9 @@ "update_devices": "Geräteliste aktualisieren", "action_debug": "Action-Debug-Modus", "hide_non_standard_entities": "Verstecke Nicht-Standard-Entitäten", - "update_trans_rules": "Entitätskonvertierungsregeln aktualisieren (globale konfiguration)", - "update_lan_ctrl_config": "LAN-Steuerungskonfiguration aktualisieren (globale Konfiguration)" + "display_devices_changed_notify": "Gerätestatusänderungen anzeigen", + "update_trans_rules": "Entitätskonvertierungsregeln aktualisieren", + "update_lan_ctrl_config": "LAN-Steuerungskonfiguration aktualisieren" } }, "update_user_info": { @@ -89,10 +114,26 @@ }, "homes_select": { "title": "Familie und Geräte neu auswählen", - "description": "## Gebrauchsanweisung\r\n### Steuerungsmodus\r\n- Automatisch: Wenn im lokalen Netzwerk ein verfügbarer Xiaomi-Zentralgateway vorhanden ist, wird Home Assistant bevorzugt Steuerbefehle über den Zentralgateway senden, um eine lokale Steuerung zu ermöglichen. Wenn im lokalen Netzwerk kein Zentralgateway vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden, um eine lokale Steuerung zu ermöglichen. Nur wenn die oben genannten Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Steuerbefehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden nur über die Cloud gesendet.\r\n### Familienimport für importierte Geräte\r\nDie Integration fügt Geräte aus den ausgewählten Familien hinzu.\r\n \r\n### Hallo {nick_name}! Bitte wählen Sie den Steuerungsmodus der Integration sowie die Familie aus, in der sich die hinzuzufügenden Geräte befinden.", + "description": "## Gebrauchsanweisung\r\n### Familienimport für importierte Geräte\r\nDie Integration fügt Geräte aus den ausgewählten Familien hinzu.\r\n### Geräte filtern\r\nUnterstützt das Filtern von Geräten nach Raumnamen, Gerätezugriffstyp und Gerätemodell, und unterstützt auch das Filtern nach Gerätegröße. Es wurden **{local_count}** Geräte gefiltert.\r\n### Steuerungsmodus\r\n- Automatisch: Wenn im lokalen Netzwerk ein verfügbarer Xiaomi-Zentralgateway vorhanden ist, wird Home Assistant bevorzugt Steuerbefehle über den Zentralgateway senden, um eine lokale Steuerung zu ermöglichen. Wenn im lokalen Netzwerk kein Zentralgateway vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden, um eine lokale Steuerung zu ermöglichen. Nur wenn die oben genannten Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Steuerbefehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden nur über die Cloud gesendet.", "data": { - "ctrl_mode": "Steuerungsmodus", - "home_infos": "Familienimport für importierte Geräte" + "home_infos": "Familienimport für importierte Geräte", + "devices_filter": "Geräte filtern", + "ctrl_mode": "Steuerungsmodus" + } + }, + "devices_filter": { + "title": "Geräte filtern", + "description": "## Gebrauchsanweisung\r\nUnterstützt das Filtern von Geräten nach Raumnamen, Gerätezugriffstyp und Gerätemodell, und unterstützt auch das Filtern nach Gerätegröße. Die Filterlogik ist wie folgt:\r\n- Zuerst wird gemäß der statistischen Logik die Vereinigung oder der Schnittpunkt aller eingeschlossenen Elemente ermittelt, dann der Schnittpunkt oder die Vereinigung der ausgeschlossenen Elemente, und schließlich wird das [eingeschlossene Gesamtergebnis] vom [ausgeschlossenen Gesamtergebnis] subtrahiert, um das [Filterergebnis] zu erhalten.\r\n- Wenn keine eingeschlossenen Elemente ausgewählt sind, bedeutet dies, dass alle eingeschlossen sind.\r\n### Filtermodus\r\n- Ausschließen: Unerwünschte Elemente entfernen.\r\n- Einschließen: Gewünschte Elemente einschließen.\r\n### Statistische Logik\r\n- UND-Logik: Den Schnittpunkt aller Elemente im gleichen Modus nehmen.\r\n- ODER-Logik: Die Vereinigung aller Elemente im gleichen Modus nehmen.\r\n\r\nSie können auch zur Seite [Konfiguration > Geräteliste aktualisieren] des Integrationselements gehen, [Geräte filtern] auswählen, um erneut zu filtern.", + "data": { + "room_filter_mode": "Familienraum filtern", + "room_list": "Familienraum", + "type_filter_mode": "Gerätetyp filtern", + "type_list": "Gerätetyp", + "model_filter_mode": "Gerätemodell filtern", + "model_list": "Gerätemodell", + "devices_filter_mode": "Geräte filtern", + "device_list": "Geräteliste", + "statistics_logic": "Statistiklogik" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "Bestätigen Sie die Konfiguration", - "description": "**{nick_name}**, bitte bestätigen Sie die neuesten Konfigurationsinformationen und klicken Sie dann auf \"Senden\". Die Integration wird mit den aktualisierten Konfigurationen erneut geladen.\r\n\r\nIntegrationsprache:\t{lang_new}\r\nBenutzername:\t{nick_name_new}\r\nAction-Debug-Modus:\t{action_debug}\r\nVerstecke Nicht-Standard-Entitäten:\t{hide_non_standard_entities}\r\nGeräteänderungen:\t{devices_add} neue Geräte hinzufügen, {devices_remove} Geräte entfernen\r\nKonvertierungsregeländerungen:\tInsgesamt {trans_rules_count} Regeln, aktualisiert {trans_rules_count_success} Regeln", + "description": "**{nick_name}**, bitte bestätigen Sie die neuesten Konfigurationsinformationen und klicken Sie dann auf \"Senden\". Die Integration wird mit den aktualisierten Konfigurationen erneut geladen.\r\n\r\nIntegrationsprache:\t{lang_new}\r\nBenutzername:\t{nick_name_new}\r\nAction-Debug-Modus:\t{action_debug}\r\nVerstecke Nicht-Standard-Entitäten:\t{hide_non_standard_entities}\r\nGerätestatusänderungen anzeigen:\t{display_devices_changed_notify}\r\nGeräteänderungen:\t{devices_add} neue Geräte hinzufügen, {devices_remove} Geräte entfernen\r\nKonvertierungsregeländerungen:\tInsgesamt {trans_rules_count} Regeln, aktualisiert {trans_rules_count_success} Regeln", "data": { "confirm": "Änderungen bestätigen" } diff --git a/custom_components/xiaomi_home/translations/en.json b/custom_components/xiaomi_home/translations/en.json index ddb424a..67fe41c 100644 --- a/custom_components/xiaomi_home/translations/en.json +++ b/custom_components/xiaomi_home/translations/en.json @@ -23,14 +23,38 @@ "description": "Click NEXT to try again." }, "homes_select": { - "title": "Select Home and Devices", - "description": "## Usage Instructions\r\n### Control mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi LAN control function. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Import devices from home\r\nThe integration will add devices from the selected homes.\n### Room name synchronizing mode\nWhen importing devices from Xiaomi Home APP to Home Assistant, the naming convention of the area where the device is added to is as follows. Note that the device synchronizing process does not change the home or room settings in Xiaomi Home APP.\r\n- Do not synchronize: The device will not be added to any area.\r\n- Other options: The device will be added to an area named as the home and/or room name that already exists in Xiaomi Home APP.\r\n### Debug mode for action\r\nFor the action defined in MIoT-Spec-V2 of the device, a Text entity along with a Notify entity will be created, in which you can send control commands to the device for debugging.\r\n### Hide non-standard created entities\r\nHide the entities generated from non-standard MIoT-Spec-V2 instances, whose names begin with \"*\".\r\n\r\n \r\n### Hello {nick_name}, please select the integration control mode and the home where the device you want to import.", + "title": "Select Family and Device", + "description": "## Introduction\r\n### Import Device's Family\r\nThe integration will add devices from the selected family.\r\n### Room Name Sync Mode\r\nWhen syncing devices from the Mi Home APP to Home Assistant, the naming of the area in Home Assistant will follow the rules below. Note that the sync process will not change the family and room settings in the Mi Home APP.\r\n- Do not sync: The device will not be added to any area.\r\n- Other options: The area to which the device is added will be named after the family or room name in the Mi Home APP.\r\n### Advanced Settings\r\nShow advanced settings to modify the professional configuration options of the integration.\r\n\r\n \r\n### {nick_name} Hello! Please select the family to which you want to add the device.", "data": { - "ctrl_mode": "Control mode", - "home_infos": "Import devices from home", - "area_name_rule": "Room name synchronizing mode", - "action_debug": "Debug mode for action", - "hide_non_standard_entities": "Hide non-standard created entities" + "home_infos": "Import Device's Family", + "area_name_rule": "Room Name Sync Mode", + "advanced_options": "Advanced Settings" + } + }, + "advanced_options": { + "title": "Advanced Settings", + "description": "## Introduction\r\n### Unless you are very clear about the meaning of the following options, please keep the default settings.\r\n### Filter Devices\r\nSupports filtering devices by room name and device type, and also supports device dimension filtering.\r\n### Control Mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi OT protocol to achieve local control. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Action Debug Mode\r\nFor the methods defined by the device MIoT-Spec-V2, in addition to generating notification entities, a text input box entity will also be generated. You can use it to send control commands to the device during debugging.\r\n### Hide Non-Standard Generated Entities\r\nHide entities generated by non-standard MIoT-Spec-V2 instances with names starting with \"*\".\r\n### Display Device Status Change Notifications\r\nDisplay detailed device status change notifications, only showing the selected notifications.", + "data": { + "devices_filter": "Filter Devices", + "ctrl_mode": "Control Mode", + "action_debug": "Action Debug Mode", + "hide_non_standard_entities": "Hide Non-Standard Generated Entities", + "display_devices_changed_notify": "Display Device Status Change Notifications" + } + }, + "devices_filter": { + "title": "Filter Devices", + "description": "## Usage Instructions\r\nSupports filtering devices by home room name, device access type, and device model, and also supports device dimension filtering. The filtering logic is as follows:\r\n- First, according to the statistical logic, get the union or intersection of all included items, then get the intersection or union of the excluded items, and finally subtract the [included summary result] from the [excluded summary result] to get the [filter result].\r\n- If no included items are selected, it means all are included.\r\n### Filter Mode\r\n- Exclude: Remove unwanted items.\r\n- Include: Include desired items.\r\n### Statistical Logic\r\n- AND logic: Take the intersection of all items in the same mode.\r\n- OR logic: Take the union of all items in the same mode.\r\n\r\nYou can also go to the [Configuration > Update Device List] page of the integration item, check [Filter Devices] to re-filter.", + "data": { + "room_filter_mode": "Filter Family Rooms", + "room_list": "Family Rooms", + "type_filter_mode": "Filter Device Connect Type", + "type_list": "Device Connect Type", + "model_filter_mode": "Filter Device Model", + "model_list": "Device Model", + "devices_filter_mode": "Filter Devices", + "device_list": "Device List", + "statistics_logic": "Statistics Logic" } } }, @@ -76,6 +100,7 @@ "update_devices": "Update device list", "action_debug": "Debug mode for action", "hide_non_standard_entities": "Hide non-standard created entities", + "display_devices_changed_notify": "Display device status change notifications", "update_trans_rules": "Update entity conversion rules", "update_lan_ctrl_config": "Update LAN control configuration" } @@ -89,10 +114,26 @@ }, "homes_select": { "title": "Re-select Home and Devices", - "description": "## Usage Instructions\r\n### Control mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi LAN control function. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Import devices from home\r\nThe integration will add devices from the selected homes.\r\n \r\n### Hello {nick_name}, please select the integration control mode and the home where the device you want to import.", + "description": "## Usage Instructions\r\n### Import devices from home\r\nThe integration will add devices from the selected homes.\r\n### Filter Devices\r\nSupports filtering devices by home room name, device access type, and device model, and also supports device dimension filtering. **{local_count}** devices have been filtered.\r\n### Control mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi LAN control function. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.", + "data": { + "home_infos": "Import devices from home", + "devices_filter": "Filter devices", + "ctrl_mode": "Control mode" + } + }, + "devices_filter": { + "title": "Filter Devices", + "description": "## Usage Instructions\r\nSupports filtering devices by home room name, device access type, and device model, and also supports device dimension filtering. The filtering logic is as follows:\r\n- First, according to the statistical logic, get the union or intersection of all included items, then get the intersection or union of the excluded items, and finally subtract the [included summary result] from the [excluded summary result] to get the [filter result].\r\n- If no included items are selected, it means all are included.\r\n### Filter Mode\r\n- Exclude: Remove unwanted items.\r\n- Include: Include desired items.\r\n### Statistical Logic\r\n- AND logic: Take the intersection of all items in the same mode.\r\n- OR logic: Take the union of all items in the same mode.\r\n\r\nYou can also go to the [Configuration > Update Device List] page of the integration item, check [Filter Devices] to re-filter.", "data": { - "ctrl_mode": "Control mode", - "home_infos": "Import devices from home" + "room_filter_mode": "Filter Family Rooms", + "room_list": "Family Rooms", + "type_filter_mode": "Filter Device Connect Type", + "type_list": "Device Connect Type", + "model_filter_mode": "Filter Device Model", + "model_list": "Device Model", + "devices_filter_mode": "Filter Devices", + "device_list": "Device List", + "statistics_logic": "Statistics Logic" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "Confirm Configuration", - "description": "Hello **{nick_name}**, please confirm the latest configuration information and then Click SUBMIT.\r\nThe integration will reload using the updated configuration.\r\n\r\nIntegration Language: \t{lang_new}\r\nNickname: \t{nick_name_new}\r\nDebug mode for action: \t{action_debug}\r\nHide non-standard created entities: \t{hide_non_standard_entities}\r\nDevice Changes: \tAdd **{devices_add}** devices, Remove **{devices_remove}** devices\r\nTransformation rules change: \tThere are a total of **{trans_rules_count}** rules, and updated **{trans_rules_count_success}** rules", + "description": "Hello **{nick_name}**, please confirm the latest configuration information and then Click SUBMIT.\r\nThe integration will reload using the updated configuration.\r\n\r\nIntegration Language: \t{lang_new}\r\nNickname: \t{nick_name_new}\r\nDebug mode for action: \t{action_debug}\r\nHide non-standard created entities: \t{hide_non_standard_entities}\r\nDisplay device status change notifications:\t{display_devices_changed_notify}\r\nDevice Changes: \tAdd **{devices_add}** devices, Remove **{devices_remove}** devices\r\nTransformation rules change: \tThere are a total of **{trans_rules_count}** rules, and updated **{trans_rules_count_success}** rules", "data": { "confirm": "Confirm the change" } diff --git a/custom_components/xiaomi_home/translations/es.json b/custom_components/xiaomi_home/translations/es.json index 23ee109..a402eaa 100644 --- a/custom_components/xiaomi_home/translations/es.json +++ b/custom_components/xiaomi_home/translations/es.json @@ -23,14 +23,38 @@ "description": "Haga clic en \"Siguiente\" para volver a intentarlo" }, "homes_select": { - "title": "Seleccionar hogares y dispositivos", - "description": "## Instrucciones de uso\r\n### Modo de control\r\n- Automático: Cuando hay un gateway central de Xiaomi disponible en la red local, Home Assistant priorizará el envío de comandos de control de dispositivos a través del gateway central para lograr un control localizado. Si no hay un gateway central en la red local, intentará enviar comandos de control a través del protocolo Xiaomi OT para lograr un control localizado. Solo cuando no se cumplan las condiciones anteriores de control localizado, los comandos de control del dispositivo se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Hogares de dispositivos importados\r\nLa integración agregará los dispositivos en los hogares seleccionados.\r\n### Modo de sincronización del nombre de la habitación\r\nCuando se sincronizan los dispositivos desde la aplicación Xiaomi Home a Home Assistant, los nombres de las áreas donde se encuentran los dispositivos en Home Assistant seguirán las reglas de nomenclatura a continuación. Tenga en cuenta que el proceso de sincronización de dispositivos no cambiará la configuración de hogares y habitaciones en la aplicación Xiaomi Home.\r\n- Sin sincronización: el dispositivo no se agregará a ninguna área.\r\n- Otras opciones: la zona donde se agrega el dispositivo tendrá el mismo nombre que el hogar o la habitación en la aplicación Xiaomi Home.\r\n### Modo de depuración de Action\r\nPara los métodos definidos por MIoT-Spec-V2, además de generar una entidad de notificación, también se generará una entidad de cuadro de entrada de texto que se puede utilizar para enviar comandos de control al dispositivo durante la depuración.\r\n### Ocultar entidades generadas no estándar\r\nOcultar las entidades generadas por la instancia no estándar MIoT-Spec-V2 que comienzan con \"*\".\r\n\r\n \r\n### ¡Hola, {nick_name}! Seleccione el modo de control de integración y el hogar donde se encuentran los dispositivos que desea agregar.", + "title": "Seleccionar familia y dispositivo", + "description": "## Introducción\r\n### Importar la familia del dispositivo\r\nLa integración añadirá dispositivos de la familia seleccionada.\r\n### Modo de sincronización del nombre de la habitación\r\nAl sincronizar dispositivos desde la APP Mi Home a Home Assistant, el nombre del área en Home Assistant seguirá las siguientes reglas. Tenga en cuenta que el proceso de sincronización no cambiará la configuración de la familia y la habitación en la APP Mi Home.\r\n- No sincronizar: El dispositivo no se añadirá a ninguna área.\r\n- Otras opciones: El área a la que se añade el dispositivo se nombrará según el nombre de la familia o la habitación en la APP Mi Home.\r\n### Configuración avanzada\r\nMostrar configuración avanzada para modificar las opciones de configuración profesional de la integración.\r\n\r\n \r\n### {nick_name} ¡Hola! Por favor, seleccione la familia a la que desea añadir el dispositivo.", "data": { - "ctrl_mode": "Modo de control", - "home_infos": "Hogares de dispositivos importados", + "home_infos": "Importar la familia del dispositivo", "area_name_rule": "Modo de sincronización del nombre de la habitación", - "action_debug": "Modo de depuración de Action", - "hide_non_standard_entities": "Ocultar entidades generadas no estándar" + "advanced_options": "Configuración avanzada" + } + }, + "advanced_options": { + "title": "Opciones Avanzadas", + "description": "## Introducción\r\n### A menos que entienda claramente el significado de las siguientes opciones, manténgalas en su configuración predeterminada.\r\n### Filtrar dispositivos\r\nAdmite 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### Modo de Control\r\n- Automático: Cuando hay una puerta de enlace central de Xiaomi disponible en la red local, Home Assistant enviará comandos de control de dispositivos a través de la puerta de enlace central para lograr la función de control local. Cuando no hay una puerta de enlace central en la red local, intentará enviar comandos de control a través del protocolo OT de Xiaomi para lograr la función de control local. Solo cuando no se cumplan las condiciones de control local anteriores, los comandos de control de dispositivos se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Modo de Depuración de Acciones\r\nPara los métodos definidos por el dispositivo MIoT-Spec-V2, además de generar una entidad de notificación, también se generará una entidad de cuadro de texto que se puede utilizar para enviar comandos de control al dispositivo durante la depuración.\r\n### Ocultar Entidades Generadas No Estándar\r\nOcultar entidades generadas por instancias MIoT-Spec-V2 no estándar que comienzan con \"*\".\r\n### Mostrar notificaciones de cambio de estado del dispositivo\r\nMostrar notificaciones detalladas de cambio de estado del dispositivo, mostrando solo las notificaciones seleccionadas.", + "data": { + "devices_filter": "Filtrar Dispositivos", + "ctrl_mode": "Modo de Control", + "action_debug": "Modo de Depuración de Acciones", + "hide_non_standard_entities": "Ocultar Entidades Generadas No Estándar", + "display_devices_changed_notify": "Mostrar notificaciones de cambio de estado del dispositivo" + } + }, + "devices_filter": { + "title": "Filtrar Dispositivos", + "description": "## Instrucciones de uso\r\nAdmite la filtración de dispositivos por nombre de habitación, tipo de acceso del dispositivo y modelo de dispositivo, y también admite la filtración por dimensión del dispositivo. La lógica de filtración es la siguiente:\r\n- Primero, de acuerdo con la lógica estadística, obtenga la unión o intersección de todos los elementos incluidos, luego obtenga la intersección o unión de los elementos excluidos, y finalmente reste el [resultado del resumen incluido] del [resultado del resumen excluido] para obtener el [resultado del filtro].\r\n- Si no se seleccionan elementos incluidos, significa que todos están incluidos.\r\n### Modo de Filtración\r\n- Excluir: Eliminar elementos no deseados.\r\n- Incluir: Incluir elementos deseados.\r\n### Lógica Estadística\r\n- Lógica Y: Tomar la intersección de todos los elementos en el mismo modo.\r\n- Lógica O: Tomar la unión de todos los elementos en el mismo modo.\r\n\r\nTambién puede ir a la página [Configuración > Actualizar lista de dispositivos] del elemento de integración, marcar [Filtrar dispositivos] para volver a filtrar.", + "data": { + "room_filter_mode": "Filtrar Habitaciones de la Familia", + "room_list": "Habitaciones de la Familia", + "type_filter_mode": "Filtrar Tipo de Dispositivo", + "type_list": "Tipo de Dispositivo", + "model_filter_mode": "Filtrar Modelo de Dispositivo", + "model_list": "Modelo de Dispositivo", + "devices_filter_mode": "Filtrar Dispositivos", + "device_list": "Lista de Dispositivos", + "statistics_logic": "Lógica de Estadísticas" } } }, @@ -76,8 +100,9 @@ "update_devices": "Actualizar lista de dispositivos", "action_debug": "Modo de depuración de Action", "hide_non_standard_entities": "Ocultar entidades generadas no estándar", - "update_trans_rules": "Actualizar reglas de conversión de entidad (configuración global)", - "update_lan_ctrl_config": "Actualizar configuración de control LAN (configuración global)" + "display_devices_changed_notify": "Mostrar notificaciones de cambio de estado del dispositivo", + "update_trans_rules": "Actualizar reglas de conversión de entidad", + "update_lan_ctrl_config": "Actualizar configuración de control LAN" } }, "update_user_info": { @@ -89,10 +114,26 @@ }, "homes_select": { "title": "Recomendar hogares y dispositivos", - "description": "## Instrucciones de uso\r\n### Modo de control\r\n- Automático: Cuando hay un gateway central de Xiaomi disponible en la red local, Home Assistant priorizará el envío de comandos de control de dispositivos a través del gateway central para lograr un control localizado. Si no hay un gateway central en la red local, intentará enviar comandos de control a través del protocolo Xiaomi OT para lograr un control localizado. Solo cuando no se cumplan las condiciones anteriores de control localizado, los comandos de control del dispositivo se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Hogares de dispositivos importados\r\nLa integración agregará los dispositivos en los hogares seleccionados.\r\n \r\n### ¡Hola, {nick_name}! Seleccione el modo de control de integración y el hogar donde se encuentran los dispositivos que desea agregar.", + "description": "## Instrucciones de uso\r\n### Hogares de dispositivos importados\r\nLa integración agregará los dispositivos en los hogares seleccionados.\r\n### Filtrar dispositivos\r\nAdmite la filtración de dispositivos por nombre de habitación, tipo de acceso del dispositivo y modelo de dispositivo, y también admite la filtración por dimensión del dispositivo. Se han filtrado **{local_count}** dispositivos.\r\n### Modo de control\r\n- Automático: Cuando hay un gateway central de Xiaomi disponible en la red local, Home Assistant priorizará el envío de comandos de control de dispositivos a través del gateway central para lograr un control localizado. Si no hay un gateway central en la red local, intentará enviar comandos de control a través del protocolo Xiaomi OT para lograr un control localizado. Solo cuando no se cumplan las condiciones anteriores de control localizado, los comandos de control del dispositivo se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.", + "data": { + "home_infos": "Hogares de dispositivos importados", + "devices_filter": "Filtrar dispositivos", + "ctrl_mode": "Modo de control" + } + }, + "devices_filter": { + "title": "Filtrar Dispositivos", + "description": "## Instrucciones de uso\r\nAdmite la filtración de dispositivos por nombre de habitación, tipo de acceso del dispositivo y modelo de dispositivo, y también admite la filtración por dimensión del dispositivo. La lógica de filtración es la siguiente:\r\n- Primero, de acuerdo con la lógica estadística, obtenga la unión o intersección de todos los elementos incluidos, luego obtenga la intersección o unión de los elementos excluidos, y finalmente reste el [resultado del resumen incluido] del [resultado del resumen excluido] para obtener el [resultado del filtro].\r\n- Si no se seleccionan elementos incluidos, significa que todos están incluidos.\r\n### Modo de Filtración\r\n- Excluir: Eliminar elementos no deseados.\r\n- Incluir: Incluir elementos deseados.\r\n### Lógica Estadística\r\n- Lógica Y: Tomar la intersección de todos los elementos en el mismo modo.\r\n- Lógica O: Tomar la unión de todos los elementos en el mismo modo.\r\n\r\nTambién puede ir a la página [Configuración > Actualizar lista de dispositivos] del elemento de integración, marcar [Filtrar dispositivos] para volver a filtrar.", "data": { - "ctrl_mode": "Modo de control", - "home_infos": "Hogares de dispositivos importados" + "room_filter_mode": "Filtrar Habitaciones de la Familia", + "room_list": "Habitaciones de la Familia", + "type_filter_mode": "Filtrar Tipo de Dispositivo", + "type_list": "Tipo de Dispositivo", + "model_filter_mode": "Filtrar Modelo de Dispositivo", + "model_list": "Modelo de Dispositivo", + "devices_filter_mode": "Filtrar Dispositivos", + "device_list": "Lista de Dispositivos", + "statistics_logic": "Lógica de Estadísticas" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "Confirmar configuración", - "description": "¡Hola, **{nick_name}**! Por favor, confirme la última información de configuración y haga clic en \"Enviar\" para finalizar la configuración.\r\nLa integración se volverá a cargar con la nueva configuración.\r\n\r\nIdioma de la integración:\t{lang_new}\r\nApodo de usuario:\t{nick_name_new}\r\nModo de depuración de Action:\t{action_debug}\r\nOcultar entidades generadas no estándar:\t{hide_non_standard_entities}\r\nCambios de dispositivos:\t{devices_add} dispositivos agregados, {devices_remove} dispositivos eliminados\r\nCambios en las reglas de conversión:\t{trans_rules_count} reglas en total, {trans_rules_count_success} reglas actualizadas", + "description": "¡Hola, **{nick_name}**! Por favor, confirme la última información de configuración y haga clic en \"Enviar\" para finalizar la configuración.\r\nLa integración se volverá a cargar con la nueva configuración.\r\n\r\nIdioma de la integración:\t{lang_new}\r\nApodo de usuario:\t{nick_name_new}\r\nModo de depuración de Action:\t{action_debug}\r\nOcultar entidades generadas no estándar:\t{hide_non_standard_entities}\r\nMostrar notificaciones de cambio de estado del dispositivo:\t{display_devices_changed_notify}\r\nCambios de dispositivos:\t{devices_add} dispositivos agregados, {devices_remove} dispositivos eliminados\r\nCambios en las reglas de conversión:\t{trans_rules_count} reglas en total, {trans_rules_count_success} reglas actualizadas", "data": { "confirm": "Confirmar modificación" } diff --git a/custom_components/xiaomi_home/translations/fr.json b/custom_components/xiaomi_home/translations/fr.json index 82c8908..eed4c6a 100644 --- a/custom_components/xiaomi_home/translations/fr.json +++ b/custom_components/xiaomi_home/translations/fr.json @@ -23,14 +23,38 @@ "description": "Cliquez sur \"Suivant\" pour réessayer" }, "homes_select": { - "title": "Sélectionner une maison et des appareils", - "description": "## Instructions d'utilisation\r\n### Mode de contrôle\r\n- Automatique: Lorsqu'il y a une passerelle centrale Xiaomi disponible dans le réseau local, Home Assistant priorisera l'envoi des commandes de contrôle des appareils via la passerelle centrale pour réaliser un contrôle localisé. S'il n'y a pas de passerelle centrale dans le réseau local, il tentera d'envoyer des commandes de contrôle via le protocole Xiaomi OT pour réaliser un contrôle localisé. Ce n'est que lorsque les conditions de contrôle localisé ci-dessus ne sont pas remplies que les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud: Les commandes de contrôle ne sont envoyées que via le cloud.\r\n### Importer une maison pour les appareils\r\nL'intégration ajoutera les appareils de la maison sélectionnée.\r\n### Mode de synchronisation des noms de pièces\r\nLors de la synchronisation des appareils de Xiaomi Home à Home Assistant, le nom de la pièce où se trouve l'appareil sera nommé selon les règles suivantes. Notez que le processus de synchronisation des appareils n'affecte pas les paramètres de la maison et de la pièce dans Xiaomi Home APP.\r\n- Ne pas synchroniser: L'appareil ne sera ajouté à aucune zone.\r\n- Autre option: La zone dans laquelle l'appareil est ajouté est nommée en fonction du nom de la maison ou de la pièce de Xiaomi Home APP.\r\n### Mode de débogage d'action\r\nPour les méthodes définies par MIoT-Spec-V2, en plus de générer une entité de notification, une entité de zone de texte sera également générée pour que vous puissiez envoyer des commandes de contrôle à l'appareil lors du débogage.\r\n### Masquer les entités générées non standard\r\nMasquer les entités générées non standard de MIoT-Spec-V2 commençant par \"*\".\r\n\r\n \r\n### {nick_name} Bonjour ! Veuillez sélectionner le mode de contrôle de l'intégration et la maison où se trouvent les appareils à ajouter.", + "title": "Sélectionner la famille et l'appareil", + "description": "## Introduction\r\n### Importer la famille de l'appareil\r\nL'intégration ajoutera des appareils de la famille sélectionnée.\r\n### Mode de synchronisation du nom de la pièce\r\nLors de la synchronisation des appareils de l'application Mi Home avec Home Assistant, le nom de la zone dans Home Assistant suivra les règles ci-dessous. Notez que le processus de synchronisation ne modifiera pas les paramètres de la famille et de la pièce dans l'application Mi Home.\r\n- Ne pas synchroniser : L'appareil ne sera ajouté à aucune zone.\r\n- Autres options : La zone à laquelle l'appareil est ajouté sera nommée d'après le nom de la famille ou de la pièce dans l'application Mi Home.\r\n### Paramètres avancés\r\nAfficher les paramètres avancés pour modifier les options de configuration professionnelle de l'intégration.\r\n\r\n \r\n### {nick_name} Bonjour ! Veuillez sélectionner la famille à laquelle vous souhaitez ajouter l'appareil.", "data": { - "ctrl_mode": "Mode de contrôle", - "home_infos": "Importer une maison pour les appareils", - "area_name_rule": "Mode de synchronisation des noms de pièces", - "action_debug": "Mode de débogage d'action", - "hide_non_standard_entities": "Masquer les entités générées non standard" + "home_infos": "Importer la famille de l'appareil", + "area_name_rule": "Mode de synchronisation du nom de la pièce", + "advanced_options": "Paramètres avancés" + } + }, + "advanced_options": { + "title": "Paramètres Avancés", + "description": "## Introduction\r\n### Sauf si vous comprenez très bien la signification des options suivantes, veuillez les laisser par défaut.\r\n### Filtrer les appareils\r\nPrend 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### Mode de Contrôle\r\n- Automatique : Lorsqu'une passerelle Xiaomi est disponible dans le réseau local, Home Assistant enverra les commandes de contrôle des appareils via la passerelle pour permettre le contrôle local. Si aucune passerelle n'est disponible dans le réseau local, Home Assistant essaiera d'envoyer les commandes de contrôle des appareils via le protocole OT Xiaomi pour permettre le contrôle local. Seules si les conditions de contrôle local ci-dessus ne sont pas remplies, les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud : Les commandes de contrôle des appareils sont envoyées uniquement via le cloud.\r\n### Mode de Débogage d’Actions\r\nPour les méthodes définies par les appareils MIoT-Spec-V2, en plus de générer une entité de notification, une entité de champ de texte sera également générée pour vous permettre d'envoyer des commandes de contrôle aux appareils lors du débogage.\r\n### Masquer les Entités Non Standard\r\nMasquer les entités générées par des instances MIoT-Spec-V2 non standard et commençant par \"*\".\r\n### Afficher les notifications de changement d'état de l'appareil\r\nAfficher les notifications détaillées de changement d'état de l'appareil, en affichant uniquement les notifications sélectionnées.", + "data": { + "devices_filter": "Filtrer les Appareils", + "ctrl_mode": "Mode de Contrôle", + "action_debug": "Mode de Débogage d’Actions", + "hide_non_standard_entities": "Masquer les Entités Non Standard", + "display_devices_changed_notify": "Afficher les notifications de changement d'état de l'appareil" + } + }, + "devices_filter": { + "title": "Filtrer les Appareils", + "description": "## Instructions d'utilisation\r\nPrend en charge le filtrage des appareils par nom de pièce de la maison, type d'accès de l'appareil et modèle d'appareil, et prend également en charge le filtrage par dimension de l'appareil. La logique de filtrage est la suivante :\r\n- Tout d'abord, selon la logique statistique, obtenez l'union ou l'intersection de tous les éléments inclus, puis obtenez l'intersection ou l'union des éléments exclus, et enfin soustrayez le [résultat du résumé inclus] du [résultat du résumé exclu] pour obtenir le [résultat du filtre].\r\n- Si aucun élément inclus n'est sélectionné, cela signifie que tous sont inclus.\r\n### Mode de Filtrage\r\n- Exclure : Supprimer les éléments indésirables.\r\n- Inclure : Inclure les éléments souhaités.\r\n### Logique Statistique\r\n- Logique ET : Prendre l'intersection de tous les éléments dans le même mode.\r\n- Logique OU : Prendre l'union de tous les éléments dans le même mode.\r\n\r\nVous pouvez également aller à la page [Configuration > Mettre à jour la liste des appareils] de l'élément d'intégration, cocher [Filtrer les appareils] pour re-filtrer.", + "data": { + "room_filter_mode": "Filtrer les Pièces", + "room_list": "Pièces", + "type_filter_mode": "Filtrer les Types d'Appareils", + "type_list": "Types d'Appareils", + "model_filter_mode": "Filtrer les Modèles d'Appareils", + "model_list": "Modèles d'Appareils", + "devices_filter_mode": "Filtrer les Appareils", + "device_list": "Liste des Appareils", + "statistics_logic": "Logique de Statistiques" } } }, @@ -76,8 +100,9 @@ "update_devices": "Mettre à jour la liste des appareils", "action_debug": "Mode de débogage d'action", "hide_non_standard_entities": "Masquer les entités générées non standard", - "update_trans_rules": "Mettre à jour les règles de conversion d'entités (configuration globale)", - "update_lan_ctrl_config": "Mettre à jour la configuration de contrôle LAN (configuration globale)" + "display_devices_changed_notify": "Afficher les notifications de changement d'état de l'appareil", + "update_trans_rules": "Mettre à jour les règles de conversion d'entités", + "update_lan_ctrl_config": "Mettre à jour la configuration de contrôle LAN" } }, "update_user_info": { @@ -89,10 +114,26 @@ }, "homes_select": { "title": "Re-sélectionner une maison et des appareils", - "description": "## Instructions d'utilisation\r\n### Mode de contrôle\r\n- Automatique: Lorsqu'il y a une passerelle centrale Xiaomi disponible dans le réseau local, Home Assistant priorisera l'envoi des commandes de contrôle des appareils via la passerelle centrale pour réaliser un contrôle localisé. S'il n'y a pas de passerelle centrale dans le réseau local, il tentera d'envoyer des commandes de contrôle via le protocole Xiaomi OT pour réaliser un contrôle localisé. Ce n'est que lorsque les conditions de contrôle localisé ci-dessus ne sont pas remplies que les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud: Les commandes de contrôle ne sont envoyées que via le cloud.\r\n### Importer une maison pour les appareils\r\nL'intégration ajoutera les appareils de la maison sélectionnée.\r\n \r\n### {nick_name} Bonjour ! Veuillez sélectionner le mode de contrôle de l'intégration et la maison où se trouvent les appareils à ajouter.", + "description": "## Instructions d'utilisation\r\n### Importer une maison pour les appareils\r\nL'intégration ajoutera les appareils de la maison sélectionnée.\r\n### Filtrer les appareils\r\nPrend en charge le filtrage des appareils par nom de pièce de la maison, type d'accès de l'appareil et modèle d'appareil, et prend également en charge le filtrage par dimension de l'appareil. **{local_count}** appareils ont été filtrés.\r\n### Mode de contrôle\r\n- Automatique: Lorsqu'il y a une passerelle centrale Xiaomi disponible dans le réseau local, Home Assistant priorisera l'envoi des commandes de contrôle des appareils via la passerelle centrale pour réaliser un contrôle localisé. S'il n'y a pas de passerelle centrale dans le réseau local, il tentera d'envoyer des commandes de contrôle via le protocole Xiaomi OT pour réaliser un contrôle localisé. Ce n'est que lorsque les conditions de contrôle localisé ci-dessus ne sont pas remplies que les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud: Les commandes de contrôle ne sont envoyées que via le cloud.", + "data": { + "home_infos": "Importer une maison pour les appareils", + "devices_filter": "Filtrer les Appareils", + "ctrl_mode": "Mode de contrôle" + } + }, + "devices_filter": { + "title": "Filtrer les Appareils", + "description": "## Instructions d'utilisation\r\nPrend en charge le filtrage des appareils par nom de pièce de la maison, type d'accès de l'appareil et modèle d'appareil, et prend également en charge le filtrage par dimension de l'appareil. La logique de filtrage est la suivante :\r\n- Tout d'abord, selon la logique statistique, obtenez l'union ou l'intersection de tous les éléments inclus, puis obtenez l'intersection ou l'union des éléments exclus, et enfin soustrayez le [résultat du résumé inclus] du [résultat du résumé exclu] pour obtenir le [résultat du filtre].\r\n- Si aucun élément inclus n'est sélectionné, cela signifie que tous sont inclus.\r\n### Mode de Filtrage\r\n- Exclure : Supprimer les éléments indésirables.\r\n- Inclure : Inclure les éléments souhaités.\r\n### Logique Statistique\r\n- Logique ET : Prendre l'intersection de tous les éléments dans le même mode.\r\n- Logique OU : Prendre l'union de tous les éléments dans le même mode.\r\n\r\nVous pouvez également aller à la page [Configuration > Mettre à jour la liste des appareils] de l'élément d'intégration, cocher [Filtrer les appareils] pour re-filtrer.", "data": { - "ctrl_mode": "Mode de contrôle", - "home_infos": "Importer une maison pour les appareils" + "room_filter_mode": "Filtrer les Pièces", + "room_list": "Pièces", + "type_filter_mode": "Filtrer les Types d'Appareils", + "type_list": "Types d'Appareils", + "model_filter_mode": "Filtrer les Modèles d'Appareils", + "model_list": "Modèles d'Appareils", + "devices_filter_mode": "Filtrer les Appareils", + "device_list": "Liste des Appareils", + "statistics_logic": "Logique de Statistiques" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "Confirmer la configuration", - "description": "**{nick_name}** Bonjour ! Veuillez confirmer les dernières informations de configuration et cliquer sur \"Soumettre\".\r\nL'intégration rechargera avec la nouvelle configuration.\r\n\r\nLangue d'intégration : {lang_new}\r\nPseudo utilisateur : {nick_name_new}\r\nMode de débogage d'action : {action_debug}\r\nMasquer les entités générées non standard : {hide_non_standard_entities}\r\nModifications des appareils : Ajouter **{devices_add}** appareils, supprimer **{devices_remove}** appareils\r\nModifications des règles de conversion : **{trans_rules_count}** règles au total, mise à jour de **{trans_rules_count_success}** règles", + "description": "**{nick_name}** Bonjour ! Veuillez confirmer les dernières informations de configuration et cliquer sur \"Soumettre\".\r\nL'intégration rechargera avec la nouvelle configuration.\r\n\r\nLangue d'intégration : {lang_new}\r\nPseudo utilisateur : {nick_name_new}\r\nMode de débogage d'action : {action_debug}\r\nMasquer les entités générées non standard : {hide_non_standard_entities}\r\nAfficher les notifications de changement d'état de l'appareil:\t{display_devices_changed_notify}\r\nModifications des appareils : Ajouter **{devices_add}** appareils, supprimer **{devices_remove}** appareils\r\nModifications des règles de conversion : **{trans_rules_count}** règles au total, mise à jour de **{trans_rules_count_success}** règles", "data": { "confirm": "Confirmer la modification" } diff --git a/custom_components/xiaomi_home/translations/ja.json b/custom_components/xiaomi_home/translations/ja.json index a8d6b36..5efe18a 100644 --- a/custom_components/xiaomi_home/translations/ja.json +++ b/custom_components/xiaomi_home/translations/ja.json @@ -23,14 +23,38 @@ "description": "「次へ」をクリックして再試行してください" }, "homes_select": { - "title": "ホームとデバイスを選択", - "description": "## 使用方法\r\n### 制御モード\r\n- 自動: ローカルエリアネットワーク内に利用可能なXiaomi中央ゲートウェイが存在する場合、Home Assistantは中央ゲートウェイを介してデバイス制御コマンドを優先的に送信し、ローカル制御機能を実現します。ローカルエリアネットワーク内に中央ゲートウェイが存在しない場合、Xiaomi OTプロトコルを介して制御コマンドを送信し、ローカル制御機能を実現しようとします。上記のローカル制御条件が満たされない場合にのみ、デバイス制御コマンドはクラウドを介して送信されます。\r\n- クラウド: 制御コマンドはクラウドを介してのみ送信されます。\r\n### 導入されたデバイスのホーム\r\n統合は、選択された家庭にあるデバイスを追加します。\r\n### 部屋名同期モード\r\nXiaomi Home アプリから Home Assistant に同期されるデバイスの場合、デバイスが Home Assistant 内でどのような領域にあるかを示す名前の命名方式は、以下のルールに従います。ただし、デバイスの同期プロセスは、Xiaomi Home アプリで家庭および部屋の設定を変更しないことに注意してください。\r\n- 同期しない:デバイスはどの領域にも追加されません。\r\n- その他のオプション:デバイスが追加される領域は、Xiaomi Home アプリの家庭または部屋の名前に従って命名されます。\r\n### Action デバッグモード\r\nデバイスが MIoT-Spec-V2 で定義された方法を実行する場合、通知エンティティの生成に加えて、テキスト入力ボックスエンティティも生成されます。これを使用して、デバイスに制御命令を送信することができます。\r\n### 非標準生成エンティティを非表示にする\r\n「*」で始まる名前の非標準 MIoT-Spec-V2 インスタンスによって生成されたエンティティを非表示にします。\r\n\r\n \r\n### {nick_name} さん、こんにちは! 統合制御モードと追加するデバイスがあるホームを選択してください。", + "title": "家族とデバイスを選択", + "description": "## 紹介\r\n### デバイスの家族をインポート\r\n統合は選択された家族のデバイスを追加します。\r\n### 部屋の名前の同期モード\r\nMi HomeアプリからHome Assistantにデバイスを同期する際、Home Assistantのエリアの名前は以下のルールに従います。同期プロセスはMi Homeアプリの家族と部屋の設定を変更しないことに注意してください。\r\n- 同期しない:デバイスはどのエリアにも追加されません。\r\n- その他のオプション:デバイスが追加されるエリアはMi Homeアプリの家族または部屋の名前にちなんで命名されます。\r\n### 高度な設定\r\n統合のプロフェッショナルな設定オプションを変更するために高度な設定を表示します。\r\n\r\n \r\n### {nick_name} こんにちは!デバイスを追加したい家族を選択してください。", "data": { - "ctrl_mode": "制御モード", - "home_infos": "導入されたデバイスのホーム", - "area_name_rule": "部屋名同期モード", - "action_debug": "Action デバッグモード", - "hide_non_standard_entities": "非標準生成エンティティを非表示にする" + "home_infos": "デバイスの家族をインポート", + "area_name_rule": "部屋の名前の同期モード", + "advanced_options": "高度な設定" + } + }, + "advanced_options": { + "title": "高度な設定オプション", + "description": "## 紹介\r\n### 以下のオプションの意味がよくわからない場合は、デフォルトのままにしてください。\r\n### デバイスのフィルタリング\r\n部屋名とデバイスタイプでデバイスをフィルタリングすることができます。デバイスの次元でフィルタリングすることもできます。\r\n### コントロールモード\r\n- 自動:ローカルネットワーク内に利用可能なXiaomi中央ゲートウェイがある場合、Home Assistantはデバイス制御命令を送信するために優先的に中央ゲートウェイを使用します。ローカルネットワークに中央ゲートウェイがない場合、Xiaomi OTプロトコルを使用してデバイス制御命令を送信し、ローカル制御機能を実現します。上記のローカル制御条件が満たされない場合のみ、デバイス制御命令はクラウドを介して送信されます。\r\n- クラウド:制御命令はクラウドを介してのみ送信されます。\r\n### Actionデバッグモード\r\nデバイスが定義するMIoT-Spec-V2のメソッドに対して、通知エンティティを生成するだけでなく、デバイスに制御命令を送信するためのテキスト入力ボックスエンティティも生成されます。デバッグ時にデバイスに制御命令を送信するために使用できます。\r\n### 非標準生成エンティティを隠す\r\n「*」で始まる名前の非標準MIoT-Spec-V2インスタンスによって生成されたエンティティを非表示にします。\r\n### デバイスの状態変化通知を表示\r\nデバイスの状態変化通知を詳細に表示し、選択された通知のみを表示します。", + "data": { + "devices_filter": "デバイスをフィルタリング", + "ctrl_mode": "コントロールモード", + "action_debug": "Actionデバッグモード", + "hide_non_standard_entities": "非標準生成エンティティを隠す", + "display_devices_changed_notify": "デバイスの状態変化通知を表示" + } + }, + "devices_filter": { + "title": "デバイスをフィルタリング", + "description": "## 使用方法\r\n家庭の部屋の名前、デバイスの接続タイプ、デバイスのモデルでデバイスをフィルタリングすることをサポートし、デバイスの次元フィルタリングもサポートします。フィルタリングロジックは次のとおりです:\r\n- まず、統計ロジックに従って、すべての含まれる項目の和集合または交差を取得し、次に除外される項目の交差または和集合を取得し、最後に[含まれる集計結果]から[除外される集計結果]を引いて[フィルタ結果]を取得します。\r\n- 含まれる項目が選択されていない場合、すべてが含まれることを意味します。\r\n### フィルターモード\r\n- 除外:不要な項目を削除します。\r\n- 含む:必要な項目を含めます。\r\n### 統計ロジック\r\n- ANDロジック:同じモードのすべての項目の交差を取ります。\r\n- ORロジック:同じモードのすべての項目の和集合を取ります。\r\n\r\n統合項目の[設定 > デバイスリストの更新]ページに移動し、[デバイスをフィルタリング]を選択して再フィルタリングすることもできます。", + "data": { + "room_filter_mode": "家族の部屋をフィルタリング", + "room_list": "家族の部屋", + "type_filter_mode": "デバイスタイプをフィルタリング", + "type_list": "デバイスタイプ", + "model_filter_mode": "デバイスモデルをフィルタリング", + "model_list": "デバイスモデル", + "devices_filter_mode": "デバイスをフィルタリング", + "device_list": "デバイスリスト", + "statistics_logic": "統計ロジック" } } }, @@ -76,8 +100,9 @@ "update_devices": "デバイスリストを更新する", "action_debug": "Action デバッグモード", "hide_non_standard_entities": "非標準生成エンティティを非表示にする", - "update_trans_rules": "エンティティ変換ルールを更新する (グローバル設定)", - "update_lan_ctrl_config": "LAN制御構成を更新する(グローバル設定)" + "display_devices_changed_notify": "デバイスの状態変化通知を表示", + "update_trans_rules": "エンティティ変換ルールを更新する", + "update_lan_ctrl_config": "LAN制御構成を更新する" } }, "update_user_info": { @@ -89,10 +114,26 @@ }, "homes_select": { "title": "ホームとデバイスを再度選択", - "description": "## 使用方法\r\n### 制御モード\r\n- 自動: ローカルエリアネットワーク内に利用可能なXiaomi中央ゲートウェイが存在する場合、Home Assistantは中央ゲートウェイを介してデバイス制御コマンドを優先的に送信し、ローカル制御機能を実現します。ローカルエリアネットワーク内に中央ゲートウェイが存在しない場合、Xiaomi OTプロトコルを介して制御コマンドを送信し、ローカル制御機能を実現しようとします。上記のローカル制御条件が満たされない場合にのみ、デバイス制御コマンドはクラウドを介して送信されます。\r\n- クラウド: 制御コマンドはクラウドを介してのみ送信されます。\r\n### 導入されたデバイスのホーム\r\n統合は、選択された家庭にあるデバイスを追加します。\r\n \r\n### {nick_name} さん、こんにちは! 統合制御モードと追加するデバイスがあるホームを選択してください。", + "description": "## 使用方法\r\n### 導入されたデバイスのホーム\r\n統合は、選択された家庭にあるデバイスを追加します。\r\n### デバイスをフィルタリング\r\n家庭の部屋の名前、デバイスの接続タイプ、デバイスのモデルでデバイスをフィルタリングすることをサポートし、デバイスの次元フィルタリングもサポートします。**{local_count}** 個のデバイスがフィルタリングされました。\r\n### 制御モード\r\n- 自動: ローカルエリアネットワーク内に利用可能なXiaomi中央ゲートウェイが存在する場合、Home Assistantは中央ゲートウェイを介してデバイス制御コマンドを優先的に送信し、ローカル制御機能を実現します。ローカルエリアネットワーク内に中央ゲートウェイが存在しない場合、Xiaomi OTプロトコルを介して制御コマンドを送信し、ローカル制御機能を実現しようとします。上記のローカル制御条件が満たされない場合にのみ、デバイス制御コマンドはクラウドを介して送信されます。\r\n- クラウド: 制御コマンドはクラウドを介してのみ送信されます。", + "data": { + "home_infos": "導入されたデバイスのホーム", + "devices_filter": "デバイスをフィルタリング", + "ctrl_mode": "制御モード" + } + }, + "devices_filter": { + "title": "デバイスをフィルタリング", + "description": "## 使用方法\r\n家庭の部屋の名前、デバイスの接続タイプ、デバイスのモデルでデバイスをフィルタリングすることをサポートし、デバイスの次元フィルタリングもサポートします。フィルタリングロジックは次のとおりです:\r\n- まず、統計ロジックに従って、すべての含まれる項目の和集合または交差を取得し、次に除外される項目の交差または和集合を取得し、最後に[含まれる集計結果]から[除外される集計結果]を引いて[フィルタ結果]を取得します。\r\n- 含まれる項目が選択されていない場合、すべてが含まれることを意味します。\r\n### フィルターモード\r\n- 除外:不要な項目を削除します。\r\n- 含む:必要な項目を含めます。\r\n### 統計ロジック\r\n- ANDロジック:同じモードのすべての項目の交差を取ります。\r\n- ORロジック:同じモードのすべての項目の和集合を取ります。\r\n\r\n統合項目の[設定 > デバイスリストの更新]ページに移動し、[デバイスをフィルタリング]を選択して再フィルタリングすることもできます。", "data": { - "ctrl_mode": "制御モード", - "home_infos": "導入されたデバイスのホーム" + "room_filter_mode": "家族の部屋をフィルタリング", + "room_list": "家族の部屋", + "type_filter_mode": "デバイスタイプをフィルタリング", + "type_list": "デバイスタイプ", + "model_filter_mode": "デバイスモデルをフィルタリング", + "model_list": "デバイスモデル", + "devices_filter_mode": "デバイスをフィルタリング", + "device_list": "デバイスリスト", + "statistics_logic": "統計ロジック" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "構成を確認する", - "description": "**{nick_name}** さん、こんにちは! 最新の構成情報を確認してください。[送信] をクリックして、更新された構成を使用して再度読み込みます。\r\n\r\n統合言語:\t{lang_new}\r\nユーザー名:\t{nick_name_new}\r\nAction デバッグモード:\t{action_debug}\r\n非標準生成エンティティを非表示にする:\t{hide_non_standard_entities}\r\nデバイス変更:\t追加 **{devices_add}** 個のデバイス、削除 **{devices_remove}** 個のデバイス\r\n変換ルール変更:\t合計 **{trans_rules_count}** 個の規則、更新 **{trans_rules_count_success}** 個の規則", + "description": "**{nick_name}** さん、こんにちは! 最新の構成情報を確認してください。[送信] をクリックして、更新された構成を使用して再度読み込みます。\r\n\r\n統合言語:\t{lang_new}\r\nユーザー名:\t{nick_name_new}\r\nAction デバッグモード:\t{action_debug}\r\n非標準生成エンティティを非表示にする:\t{hide_non_standard_entities}\r\nデバイスの状態変化通知を表示:\t{display_devices_changed_notify}\r\nデバイス変更:\t追加 **{devices_add}** 個のデバイス、削除 **{devices_remove}** 個のデバイス\r\n変換ルール変更:\t合計 **{trans_rules_count}** 個の規則、更新 **{trans_rules_count_success}** 個の規則", "data": { "confirm": "変更を確認する" } diff --git a/custom_components/xiaomi_home/translations/nl.json b/custom_components/xiaomi_home/translations/nl.json index a01f845..a36d30b 100644 --- a/custom_components/xiaomi_home/translations/nl.json +++ b/custom_components/xiaomi_home/translations/nl.json @@ -23,14 +23,38 @@ "description": "Klik OP VOLGENDE om het opnieuw te proberen." }, "homes_select": { - "title": "Selecteer Huis en Apparaten", - "description": "## Gebruiksinstructies\r\n### Controlemodus\r\n- Auto: Wanneer er een beschikbare Xiaomi centrale hubgateway in het lokale netwerk is, geeft Home Assistant de voorkeur aan het verzenden van apparaatbedieningscommando's via de centrale hubgateway om lokale controle te bereiken. Als er geen centrale hubgateway in het lokale netwerk is, zal het proberen bedieningscommando's te verzenden via de Xiaomi LAN-controlefunctie. Alleen wanneer de bovenstaande lokale controlevoorwaarden niet zijn vervuld, worden de apparaatbedieningscommando's via de cloud verzonden.\r\n- Cloud: Alle bedieningscommando's worden via de cloud verzonden.\r\n### Apparaten importeren vanuit huis\r\nDe integratie voegt apparaten toe van de geselecteerde huizen.\n### Ruimtenaamsynchronisatiemodus\nBij het importeren van apparaten vanuit de Xiaomi Home APP naar Home Assistant is de naamgevingsconventie van het gebied waarin het apparaat wordt toegevoegd als volgt. Opmerking: het synchronisatieproces van het apparaat verandert de huis- of ruimte-instellingen in de Xiaomi Home APP niet.\r\n- Niet synchroniseren: Het apparaat wordt aan geen enkel gebied toegevoegd.\r\n- Andere opties: Het apparaat wordt toegevoegd aan een gebied dat is genoemd naar de huis- en/of ruimtenamen die al bestaan in de Xiaomi Home APP.\r\n### Debugmodus voor actie\r\nVoor de actie gedefinieerd in MIoT-Spec-V2 van het apparaat, wordt er een Tekstentiteit samen met een Notificatie-entiteit aangemaakt, waarin u bedieningscommando's naar het apparaat kunt sturen voor debugging.\r\n### Verberg niet-standaard gemaakte entiteiten\r\nVerberg de entiteiten die zijn gegenereerd vanuit niet-standaard MIoT-Spec-V2-instanties, waarvan de namen beginnen met \"*\".\r\n\r\n \r\n### Hallo {nick_name}, selecteer alstublieft de integratie controlemethodiek en het huis waar het apparaat dat u wilt importeren zich bevindt.", + "title": "Selecteer Familie en Apparaat", + "description": "## Inleiding\r\n### Importeer de familie van het apparaat\r\nDe integratie voegt apparaten toe uit de geselecteerde familie.\r\n### Kamernaam Synchronisatiemodus\r\nBij het synchroniseren van apparaten van de Mi Home-app naar Home Assistant, wordt de naam van het gebied in Home Assistant volgens de volgende regels genoemd. Houd er rekening mee dat het synchronisatieproces de instellingen van familie en kamer in de Mi Home-app niet zal wijzigen.\r\n- Niet synchroniseren: Het apparaat wordt niet aan een gebied toegevoegd.\r\n- Andere opties: Het gebied waaraan het apparaat wordt toegevoegd, wordt genoemd naar de familie- of kamernaam in de Mi Home-app.\r\n### Geavanceerde instellingen\r\nToon geavanceerde instellingen om de professionele configuratie-opties van de integratie te wijzigen.\r\n\r\n \r\n### {nick_name} Hallo! Selecteer de familie waaraan u het apparaat wilt toevoegen.", "data": { - "ctrl_mode": "Controlemodus", - "home_infos": "Importeer apparaten uit huis", - "area_name_rule": "Ruimtenaamsynchronisatiemodus", - "action_debug": "Debugmodus voor actie", - "hide_non_standard_entities": "Verberg niet-standaard gemaakte entiteiten" + "home_infos": "Importeer de familie van het apparaat", + "area_name_rule": "Kamernaam Synchronisatiemodus", + "advanced_options": "Geavanceerde instellingen" + } + }, + "advanced_options": { + "title": "Geavanceerde Instellingen", + "description": "## Inleiding\r\n### Tenzij u zeer goed op de hoogte bent van de betekenis van de volgende opties, houdt u de standaardinstellingen.\r\n### Apparaten filteren\r\nOndersteunt het filteren van apparaten op basis van kamer- en apparaattypen, en ondersteunt ook apparaatdimensiefiltering.\r\n### Besturingsmodus\r\n- Automatisch: Wanneer er een beschikbare Xiaomi centrale hubgateway in het lokale netwerk is, zal Home Assistant eerst apparaatbesturingsinstructies via de centrale hubgateway verzenden om lokale controlefunctionaliteit te bereiken. Als er geen centrale hub in het lokale netwerk is, zal het proberen om besturingsinstructies via het Xiaomi OT-protocol te verzenden om lokale controlefunctionaliteit te bereiken. Alleen als de bovenstaande lokale controlevoorwaarden niet worden vervuld, worden apparaatbesturingsinstructies via de cloud verzonden.\r\n- Cloud: Besturingsinstructies worden alleen via de cloud verzonden.\r\n### Actie-debugmodus\r\nVoor methoden die zijn gedefinieerd in de MIoT-Spec-V2 van het apparaat, wordt naast het genereren van een meldingsentiteit ook een tekstinvoerveldentiteit gegenereerd. U kunt dit gebruiken om besturingsinstructies naar het apparaat te sturen tijdens het debuggen.\r\n### Niet-standaard entiteiten verbergen\r\nVerberg entiteiten die zijn gegenereerd door niet-standaard MIoT-Spec-V2-instanties die beginnen met \"*\".\r\n### Apparaatstatuswijzigingen weergeven\r\nGedetailleerde apparaatstatuswijzigingen weergeven, alleen de geselecteerde meldingen weergeven.", + "data": { + "devices_filter": "Apparaten filteren", + "ctrl_mode": "Besturingsmodus", + "action_debug": "Actie-debugmodus", + "hide_non_standard_entities": "Niet-standaard entiteiten verbergen", + "display_devices_changed_notify": "Apparaatstatuswijzigingen weergeven" + } + }, + "devices_filter": { + "title": "Apparaten filteren", + "description": "## Gebruiksinstructies\r\nOndersteunt het filteren van apparaten op kamernaam, apparaattoegangstype en apparaattype, en ondersteunt ook het filteren op apparaatdimensie. De filterlogica is als volgt:\r\n- Eerst, volgens de statistische logica, de vereniging of doorsnede van alle opgenomen items verkrijgen, vervolgens de doorsnede of vereniging van de uitgesloten items verkrijgen, en ten slotte het [opgenomen samenvattingsresultaat] aftrekken van het [uitgesloten samenvattingsresultaat] om het [filterresultaat] te verkrijgen.\r\n- Als er geen opgenomen items zijn geselecteerd, betekent dit dat alles is opgenomen.\r\n### Filtermodus\r\n- Uitsluiten: Ongewenste items verwijderen.\r\n- Opnemen: Gewenste items opnemen.\r\n### Statistische Logica\r\n- EN-logica: Neem de doorsnede van alle items in dezelfde modus.\r\n- OF-logica: Neem de vereniging van alle items in dezelfde modus.\r\n\r\nU kunt ook naar de pagina [Configuratie > Apparaatlijst bijwerken] van het integratie-item gaan, [Apparaten filteren] aanvinken om opnieuw te filteren.", + "data": { + "room_filter_mode": "Kamerfiltermodus", + "room_list": "Kamers", + "type_filter_mode": "Apparaattypen filteren", + "type_list": "Apparaattypen", + "model_filter_mode": "Apparaatmodel filteren", + "model_list": "Apparaatmodellen", + "devices_filter_mode": "Apparaten filteren", + "device_list": "Apparaatlijst", + "statistics_logic": "Statistische logica" } } }, @@ -76,6 +100,7 @@ "update_devices": "Werk apparatenlijst bij", "action_debug": "Debugmodus voor actie", "hide_non_standard_entities": "Verberg niet-standaard gemaakte entiteiten", + "display_devices_changed_notify": "Apparaatstatuswijzigingen weergeven", "update_trans_rules": "Werk entiteitsconversieregels bij", "update_lan_ctrl_config": "Werk LAN controleconfiguratie bij" } @@ -89,10 +114,26 @@ }, "homes_select": { "title": "Huis en Apparaten opnieuw selecteren", - "description": "## Gebruiksinstructies\r\n### Controlemodus\r\n- Auto: Wanneer er een beschikbare Xiaomi centrale hubgateway in het lokale netwerk is, geeft Home Assistant de voorkeur aan het verzenden van apparaatbedieningscommando's via de centrale hubgateway om lokale controle te bereiken. Als er geen centrale hubgateway in het lokale netwerk is, zal het proberen bedieningscommando's te verzenden via de Xiaomi LAN-controlefunctie. Alleen wanneer de bovenstaande lokale controlevoorwaarden niet zijn vervuld, worden de apparaatbedieningscommando's via de cloud verzonden.\r\n- Cloud: Alle bedieningscommando's worden via de cloud verzonden.\r\n### Apparaten importeren vanuit huis\r\nDe integratie voegt apparaten toe van de geselecteerde huizen.\r\n \r\n### Hallo {nick_name}, selecteer alstublieft de integratie controlemethodiek en het huis waar het apparaat dat u wilt importeren zich bevindt.", + "description": "## Gebruiksinstructies\r\n### Importeer apparaten uit huis\r\nDe integratie voegt apparaten toe van de geselecteerde huizen.\r\n### Apparaten filteren\r\nOndersteunt het filteren van apparaten op kamernaam, apparaattoegangstype en apparaattype, en ondersteunt ook het filteren op apparaatdimensie. **{local_count}** apparaten zijn gefilterd.\r\n### Controlemodus\r\n- Auto: Wanneer er een beschikbare Xiaomi centrale hubgateway in het lokale netwerk is, geeft Home Assistant de voorkeur aan het verzenden van apparaatbedieningscommando's via de centrale hubgateway om lokale controle te bereiken. Als er geen centrale hubgateway in het lokale netwerk is, zal het proberen bedieningscommando's te verzenden via de Xiaomi LAN-controlefunctie. Alleen wanneer de bovenstaande lokale controlevoorwaarden niet zijn vervuld, worden de apparaatbedieningscommando's via de cloud verzonden.\r\n- Cloud: Alle bedieningscommando's worden via de cloud verzonden.", + "data": { + "home_infos": "Importeer apparaten uit huis", + "devices_filter": "Apparaten filteren", + "ctrl_mode": "Controlemodus" + } + }, + "devices_filter": { + "title": "Apparaten filteren", + "description": "## Gebruiksinstructies\r\nOndersteunt het filteren van apparaten op kamernaam, apparaattoegangstype en apparaattype, en ondersteunt ook het filteren op apparaatdimensie. De filterlogica is als volgt:\r\n- Eerst, volgens de statistische logica, de vereniging of doorsnede van alle opgenomen items verkrijgen, vervolgens de doorsnede of vereniging van de uitgesloten items verkrijgen, en ten slotte het [opgenomen samenvattingsresultaat] aftrekken van het [uitgesloten samenvattingsresultaat] om het [filterresultaat] te verkrijgen.\r\n- Als er geen opgenomen items zijn geselecteerd, betekent dit dat alles is opgenomen.\r\n### Filtermodus\r\n- Uitsluiten: Ongewenste items verwijderen.\r\n- Opnemen: Gewenste items opnemen.\r\n### Statistische Logica\r\n- EN-logica: Neem de doorsnede van alle items in dezelfde modus.\r\n- OF-logica: Neem de vereniging van alle items in dezelfde modus.\r\n\r\nU kunt ook naar de pagina [Configuratie > Apparaatlijst bijwerken] van het integratie-item gaan, [Apparaten filteren] aanvinken om opnieuw te filteren.", "data": { - "ctrl_mode": "Controlemodus", - "home_infos": "Importeer apparaten uit huis" + "room_filter_mode": "Kamerfiltermodus", + "room_list": "Kamers", + "type_filter_mode": "Apparaattypen filteren", + "type_list": "Apparaattypen", + "model_filter_mode": "Apparaatmodel filteren", + "model_list": "Apparaatmodellen", + "devices_filter_mode": "Apparaten filteren", + "device_list": "Apparaatlijst", + "statistics_logic": "Statistische logica" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "Bevestig Configuratie", - "description": "Hallo **{nick_name}**, bevestig alstublieft de nieuwste configuratie-informatie en klik vervolgens op INDENKEN.\r\nDe integratie zal opnieuw laden met de bijgewerkte configuratie.\r\n\r\nIntegratietaal: \t{lang_new}\r\nBijnaam: \t{nick_name_new}\r\nDebugmodus voor actie: \t{action_debug}\r\nVerberg niet-standaard gemaakte entiteiten: \t{hide_non_standard_entities}\r\nWijzigingen in apparaten: \tVoeg **{devices_add}** apparaten toe, Verwijder **{devices_remove}** apparaten\r\nWijzigingen in transformateregels: \tEr zijn in totaal **{trans_rules_count}** regels, en **{trans_rules_count_success}** regels zijn bijgewerkt", + "description": "Hallo **{nick_name}**, bevestig alstublieft de nieuwste configuratie-informatie en klik vervolgens op INDENKEN.\r\nDe integratie zal opnieuw laden met de bijgewerkte configuratie.\r\n\r\nIntegratietaal: \t{lang_new}\r\nBijnaam: \t{nick_name_new}\r\nDebugmodus voor actie: \t{action_debug}\r\nVerberg niet-standaard gemaakte entiteiten: \t{hide_non_standard_entities}\r\nApparaatstatuswijzigingen weergeven:\t{display_devices_changed_notify}\r\nWijzigingen in apparaten: \tVoeg **{devices_add}** apparaten toe, Verwijder **{devices_remove}** apparaten\r\nWijzigingen in transformateregels: \tEr zijn in totaal **{trans_rules_count}** regels, en **{trans_rules_count_success}** regels zijn bijgewerkt", "data": { "confirm": "Bevestig de wijziging" } @@ -141,4 +182,4 @@ "inconsistent_account": "Accountinformatie is inconsistent. Log in met het juiste account." } } -} +} \ No newline at end of file diff --git a/custom_components/xiaomi_home/translations/pt-BR.json b/custom_components/xiaomi_home/translations/pt-BR.json index fca54a5..521c2fa 100644 --- a/custom_components/xiaomi_home/translations/pt-BR.json +++ b/custom_components/xiaomi_home/translations/pt-BR.json @@ -23,14 +23,38 @@ "description": "Clique em AVANÇAR para tentar novamente." }, "homes_select": { - "title": "Selecione a Casa e os Dispositivos", - "description": "## Instruções de Uso\r\n### Modo de controle\r\n- Auto: Quando houver um gateway central Xiaomi disponível na rede local, o Home Assistant priorizará o envio de comandos de controle do dispositivo através do gateway central, obtendo assim controle local. Se não houver gateway central na rede local, ele tentará enviar comandos através da função de controle LAN da Xiaomi. Somente quando as condições de controle local acima não forem atendidas, os comandos serão enviados pela nuvem.\r\n- Nuvem: Todos os comandos de controle são enviados através da nuvem.\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\n### Modo de sincronização do nome do cômodo\r\nAo importar dispositivos do aplicativo Xiaomi Home para o Home Assistant, a convenção de nomeação da área onde o dispositivo é adicionado é a seguinte. Observe que o processo de sincronização do dispositivo não altera as configurações de casa ou cômodo no aplicativo Xiaomi Home.\r\n- Não sincronizar: O dispositivo não será adicionado a nenhuma área.\r\n- Outras opções: O dispositivo será adicionado a uma área nomeada de acordo com o nome da casa e/ou do cômodo que já existem no aplicativo Xiaomi Home.\r\n### Modo de depuração para ação\r\nPara as ações definidas no MIoT-Spec-V2 do dispositivo, será criada uma entidade de texto juntamente com uma entidade de notificação, nas quais você poderá enviar comandos de controle ao dispositivo para fins de depuração.\r\n### Ocultar entidades criadas não padrão\r\nOculta as entidades geradas a partir de instâncias não padrão do MIoT-Spec-V2, cujos nomes começam com \"*\".\r\n\r\n \r\n### Olá {nick_name}, selecione o modo de controle da integração e a casa onde estão os dispositivos que você deseja importar.", + "title": "Selecionar Família e Dispositivo", + "description": "## Introdução\r\n### Importar a Família do Dispositivo\r\nA integração adicionará dispositivos da família selecionada.\r\n### Modo de Sincronização do Nome da Sala\r\nAo sincronizar dispositivos do APP Mi Home para o Home Assistant, a nomeação da área no Home Assistant seguirá as regras abaixo. Observe que o processo de sincronização não alterará as configurações de família e sala no APP Mi Home.\r\n- Não sincronizar: O dispositivo não será adicionado a nenhuma área.\r\n- Outras opções: A área à qual o dispositivo é adicionado será nomeada de acordo com o nome da família ou da sala no APP Mi Home.\r\n### Configurações Avançadas\r\nMostrar configurações avançadas para modificar as opções de configuração profissional da integração.\r\n\r\n \r\n### {nick_name} Olá! Por favor, selecione a família à qual você deseja adicionar o dispositivo.", "data": { - "ctrl_mode": "Modo de controle", - "home_infos": "Importar dispositivos da casa", - "area_name_rule": "Modo de sincronização do nome do cômodo", - "action_debug": "Modo de depuração para ação", - "hide_non_standard_entities": "Ocultar entidades não padrão criadas" + "home_infos": "Importar a Família do Dispositivo", + "area_name_rule": "Modo de Sincronização do Nome da Sala", + "advanced_options": "Configurações Avançadas" + } + }, + "advanced_options": { + "title": "Configurações Avançadas", + "description": "## Introdução\r\n### A menos que você entenda claramente o significado das opções a seguir, mantenha as configurações padrão.\r\n### Filtrar Dispositivos\r\nSuporte para filtrar dispositivos por nome da sala e tipo de dispositivo, bem como filtragem por família.\r\n### Modo de Controle\r\n- Automático: Quando um gateway central Xiaomi disponível na rede local está disponível, o Home Assistant enviará comandos de controle de dispositivo através do gateway central para realizar a função de controle local. Quando não há gateway central na rede local, ele tentará enviar comandos de controle através do protocolo OT da Xiaomi para realizar a função de controle local. Somente quando as condições de controle local acima não forem atendidas, os comandos de controle do dispositivo serão enviados através da nuvem.\r\n- Nuvem: Os comandos de controle são enviados apenas através da nuvem.\r\n### Modo de Depuração de Ações\r\nPara métodos definidos pelo MIoT-Spec-V2 do dispositivo, além de gerar uma entidade de notificação, também será gerada uma entidade de caixa de texto para você enviar comandos de controle ao dispositivo durante a depuração.\r\n### Ocultar Entidades Geradas Não Padrão\r\nOcultar entidades geradas por instâncias MIoT-Spec-V2 não padrão que começam com \"*\".\r\n### Exibir notificações de mudança de status do dispositivo\r\nExibir notificações detalhadas de mudança de status do dispositivo, mostrando apenas as notificações selecionadas.", + "data": { + "devices_filter": "Filtrar Dispositivos", + "ctrl_mode": "Modo de Controle", + "action_debug": "Modo de Depuração de Ações", + "hide_non_standard_entities": "Ocultar Entidades Geradas Não Padrão", + "display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo" + } + }, + "devices_filter": { + "title": "Filtrar Dispositivos", + "description": "## Instruções de Uso\r\nSuporta a filtragem de dispositivos por nome da sala, tipo de acesso do dispositivo e modelo do dispositivo, e também suporta a filtragem por dimensão do dispositivo. A lógica de filtragem é a seguinte:\r\n- Primeiro, de acordo com a lógica estatística, obtenha a união ou interseção de todos os itens incluídos, depois obtenha a interseção ou união dos itens excluídos, e finalmente subtraia o [resultado do resumo incluído] do [resultado do resumo excluído] para obter o [resultado do filtro].\r\n- Se nenhum item incluído for selecionado, significa que todos estão incluídos.\r\n### Modo de Filtragem\r\n- Excluir: Remover itens indesejados.\r\n- Incluir: Incluir itens desejados.\r\n### Lógica Estatística\r\n- Lógica E: Pegue a interseção de todos os itens no mesmo modo.\r\n- Lógica OU: Pegue a união de todos os itens no mesmo modo.\r\n\r\nVocê também pode ir para a página [Configuração > Atualizar Lista de Dispositivos] do item de integração, marcar [Filtrar Dispositivos] para refiltrar.", + "data": { + "room_filter_mode": "Filtrar por Sala", + "room_list": "Salas", + "type_filter_mode": "Filtrar por Tipo de Dispositivo", + "type_list": "Tipos de Dispositivo", + "model_filter_mode": "Filtrar por Modelo de Dispositivo", + "model_list": "Modelos de Dispositivo", + "devices_filter_mode": "Filtrar Dispositivos", + "device_list": "Lista de Dispositivos", + "statistics_logic": "Lógica de Estatísticas" } } }, @@ -76,6 +100,7 @@ "update_devices": "Atualizar lista de dispositivos", "action_debug": "Modo de depuração para ação", "hide_non_standard_entities": "Ocultar entidades não padrão criadas", + "display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo", "update_trans_rules": "Atualizar regras de conversão de entidades", "update_lan_ctrl_config": "Atualizar configuração de controle LAN" } @@ -89,10 +114,26 @@ }, "homes_select": { "title": "Selecionar novamente Casa e Dispositivos", - "description": "## Instruções de Uso\r\n### Modo de controle\r\n- Auto: Quando houver um gateway central Xiaomi disponível na rede local, o Home Assistant priorizará o envio de comandos através dele para obter controle local. Caso não haja, tentará enviar comandos através da função de controle LAN da Xiaomi. Somente se as condições anteriores não forem atendidas, o controle será feito pela nuvem.\r\n- Nuvem: Todos os comandos de controle são enviados pela nuvem.\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\r\n \r\n### Olá {nick_name}, selecione o modo de controle da integração e a casa de onde deseja importar dispositivos.", + "description": "## Instruções de Uso\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\r\n### Filtrar Dispositivos\r\nSuporta a filtragem de dispositivos por nome da sala, tipo de acesso do dispositivo e modelo do dispositivo, e também suporta a filtragem por dimensão do dispositivo. **{local_count}** dispositivos foram filtrados.\r\n### Modo de controle\r\n- Auto: Quando houver um gateway central Xiaomi disponível na rede local, o Home Assistant priorizará o envio de comandos através dele para obter controle local. Caso não haja, tentará enviar comandos através da função de controle LAN da Xiaomi. Somente se as condições anteriores não forem atendidas, o controle será feito pela nuvem.\r\n- Nuvem: Todos os comandos de controle são enviados pela nuvem.", + "data": { + "home_infos": "Importar dispositivos da casa", + "devices_filter": "Filtrar Dispositivos", + "ctrl_mode": "Modo de controle" + } + }, + "devices_filter": { + "title": "Filtrar Dispositivos", + "description": "## Instruções de Uso\r\nSuporta a filtragem de dispositivos por nome da sala, tipo de acesso do dispositivo e modelo do dispositivo, e também suporta a filtragem por dimensão do dispositivo. A lógica de filtragem é a seguinte:\r\n- Primeiro, de acordo com a lógica estatística, obtenha a união ou interseção de todos os itens incluídos, depois obtenha a interseção ou união dos itens excluídos, e finalmente subtraia o [resultado do resumo incluído] do [resultado do resumo excluído] para obter o [resultado do filtro].\r\n- Se nenhum item incluído for selecionado, significa que todos estão incluídos.\r\n### Modo de Filtragem\r\n- Excluir: Remover itens indesejados.\r\n- Incluir: Incluir itens desejados.\r\n### Lógica Estatística\r\n- Lógica E: Pegue a interseção de todos os itens no mesmo modo.\r\n- Lógica OU: Pegue a união de todos os itens no mesmo modo.\r\n\r\nVocê também pode ir para a página [Configuração > Atualizar Lista de Dispositivos] do item de integração, marcar [Filtrar Dispositivos] para refiltrar.", "data": { - "ctrl_mode": "Modo de controle", - "home_infos": "Importar dispositivos da casa" + "room_filter_mode": "Filtrar por Sala", + "room_list": "Salas", + "type_filter_mode": "Filtrar por Tipo de Dispositivo", + "type_list": "Tipos de Dispositivo", + "model_filter_mode": "Filtrar por Modelo de Dispositivo", + "model_list": "Modelos de Dispositivo", + "devices_filter_mode": "Filtrar Dispositivos", + "device_list": "Lista de Dispositivos", + "statistics_logic": "Lógica de Estatísticas" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "Confirmar Configuração", - "description": "Olá **{nick_name}**, confirme as informações da configuração mais recente e depois clique em ENVIAR.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nApelido:\t{nick_name_new}\r\nModo de depuração para ação:\t{action_debug}\r\nOcultar entidades não padrão criadas:\t{hide_non_standard_entities}\r\nAlterações de Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração nas Regras de Transformação:\tUm total de **{trans_rules_count}** regras, e **{trans_rules_count_success}** regras atualizadas", + "description": "Olá **{nick_name}**, confirme as informações da configuração mais recente e depois clique em ENVIAR.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nApelido:\t{nick_name_new}\r\nModo de depuração para ação:\t{action_debug}\r\nOcultar entidades não padrão criadas:\t{hide_non_standard_entities}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações de Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração nas Regras de Transformação:\tUm total de **{trans_rules_count}** regras, e **{trans_rules_count_success}** regras atualizadas", "data": { "confirm": "Confirmar a mudança" } @@ -141,4 +182,4 @@ "inconsistent_account": "As informações da conta são inconsistentes. Por favor, faça login com a conta correta." } } -} +} \ No newline at end of file diff --git a/custom_components/xiaomi_home/translations/pt.json b/custom_components/xiaomi_home/translations/pt.json index 480f59e..3ff0f03 100644 --- a/custom_components/xiaomi_home/translations/pt.json +++ b/custom_components/xiaomi_home/translations/pt.json @@ -23,14 +23,38 @@ "description": "Clique em SEGUINTE para tentar novamente." }, "homes_select": { - "title": "Selecione a Casa e os Dispositivos", - "description": "## Instruções de Utilização\r\n### Modo de Controlo\r\n- Automático: Quando existir um gateway central Xiaomi disponível na rede local, o Home Assistant dará prioridade ao envio de comandos de controlo através do gateway central, permitindo um controlo local. Caso não exista um gateway central na rede local, tentará enviar comandos através da funcionalidade de controlo LAN. Apenas se estas condições não forem cumpridas, os comandos serão enviados pela nuvem.\r\n- Nuvem: Todos os comandos de controlo são enviados através da nuvem.\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\n### Modo de sincronização do nome da divisão\r\nAo importar dispositivos da aplicação Xiaomi Home para o Home Assistant, a nomeação da área onde o dispositivo é adicionado segue as regras abaixo. Note que o processo de sincronização dos dispositivos não altera as definições de casa ou divisão na aplicação Xiaomi Home.\r\n- Não sincronizar: O dispositivo não será atribuído a qualquer área.\r\n- Outras opções: O dispositivo será adicionado a uma área cujo nome corresponde ao da casa e/ou divisão definida na aplicação Xiaomi Home.\r\n### Modo de depuração de ação\r\nPara as ações definidas no MIoT-Spec-V2 do dispositivo, será criada uma entidade do tipo texto juntamente com uma entidade de notificação, nas quais poderá enviar comandos de controlo ao dispositivo para fins de depuração.\r\n### Ocultar entidades não padrão\r\nOculta as entidades geradas a partir de instâncias não padrão do MIoT-Spec-V2, cujos nomes começam por \"*\".\r\n\r\n \r\n### Olá {nick_name}, selecione o modo de controlo da integração e a casa na qual deseja importar os dispositivos.", + "title": "Selecionar Família e Dispositivo", + "description": "## Introdução\r\n### Importar a Família do Dispositivo\r\nA integração adicionará dispositivos da família selecionada.\r\n### Modo de Sincronização do Nome da Sala\r\nAo sincronizar dispositivos do APP Mi Home para o Home Assistant, a nomeação da área no Home Assistant seguirá as regras abaixo. Observe que o processo de sincronização não alterará as configurações de família e sala no APP Mi Home.\r\n- Não sincronizar: O dispositivo não será adicionado a nenhuma área.\r\n- Outras opções: A área à qual o dispositivo é adicionado será nomeada de acordo com o nome da família ou da sala no APP Mi Home.\r\n### Configurações Avançadas\r\nMostrar configurações avançadas para modificar as opções de configuração profissional da integração.\r\n\r\n \r\n### {nick_name} Olá! Por favor, selecione a família à qual você deseja adicionar o dispositivo.", "data": { + "home_infos": "Importar a Família do Dispositivo", + "area_name_rule": "Modo de Sincronização do Nome da Sala", + "advanced_options": "Configurações Avançadas" + } + }, + "advanced_options": { + "title": "Opções Avançadas", + "description": "## Introdução\r\n### A menos que você entenda claramente o significado das opções abaixo, mantenha as configurações padrão.\r\n### Filtrar Dispositivos\r\nSuporte para filtrar dispositivos por nome da sala e tipo de dispositivo, bem como filtragem por família.\r\n### Modo de Controle\r\n- Automático: Quando um gateway central Xiaomi está disponível na rede local, o Home Assistant enviará comandos de controlo de dispositivos através do gateway central para realizar o controlo local. Quando não há gateway central na rede local, tentará enviar comandos de controlo através do protocolo Xiaomi OT para realizar o controlo local. Apenas quando as condições de controlo local acima não são atendidas, os comandos de controlo de dispositivos serão enviados através da nuvem.\r\n- Nuvem: Os comandos de controlo são enviados apenas através da nuvem.\r\n### Modo de Depuração de Ações\r\nPara métodos definidos pelo MIoT-Spec-V2, além de gerar uma entidade de notificação, também será gerada uma entidade de caixa de texto para depuração de controlo de dispositivos.\r\n### Ocultar Entidades Geradas Não Padrão\r\nOcultar entidades geradas por instâncias MIoT-Spec-V2 não padrão, cujos nomes começam com \"*\".\r\n### Exibir notificações de mudança de status do dispositivo\r\nExibir notificações detalhadas de mudança de status do dispositivo, mostrando apenas as notificações selecionadas.", + "data": { + "devices_filter": "Filtrar Dispositivos", "ctrl_mode": "Modo de Controlo", - "home_infos": "Importar dispositivos da casa", - "area_name_rule": "Modo de sincronização do nome da divisão", - "action_debug": "Modo de depuração de ação", - "hide_non_standard_entities": "Ocultar entidades não padrão" + "action_debug": "Modo de Depuração de Ações", + "hide_non_standard_entities": "Ocultar Entidades Geradas Não Padrão", + "display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo" + } + }, + "devices_filter": { + "title": "Filtrar Dispositivos", + "description": "## Instruções de Utilização\r\nSuporta a filtragem de dispositivos por nome da sala, tipo de acesso do dispositivo e modelo do dispositivo, e também suporta a filtragem por dimensão do dispositivo. A lógica de filtragem é a seguinte:\r\n- Primeiro, de acordo com a lógica estatística, obtenha a união ou interseção de todos os itens incluídos, depois obtenha a interseção ou união dos itens excluídos, e finalmente subtraia o [resultado do resumo incluído] do [resultado do resumo excluído] para obter o [resultado do filtro].\r\n- Se nenhum item incluído for selecionado, significa que todos estão incluídos.\r\n### Modo de Filtragem\r\n- Excluir: Remover itens indesejados.\r\n- Incluir: Incluir itens desejados.\r\n### Lógica Estatística\r\n- Lógica E: Pegue a interseção de todos os itens no mesmo modo.\r\n- Lógica OU: Pegue a união de todos os itens no mesmo modo.\r\n\r\nVocê também pode ir para a página [Configuração > Atualizar Lista de Dispositivos] do item de integração, marcar [Filtrar Dispositivos] para refiltrar.", + "data": { + "room_filter_mode": "Filtrar por Sala", + "room_list": "Salas", + "type_filter_mode": "Filtrar por Tipo de Dispositivo", + "type_list": "Tipos de Dispositivo", + "model_filter_mode": "Filtrar por Modelo de Dispositivo", + "model_list": "Modelos de Dispositivo", + "devices_filter_mode": "Filtrar Dispositivos", + "device_list": "Lista de Dispositivos", + "statistics_logic": "Lógica de Estatísticas" } } }, @@ -76,6 +100,7 @@ "update_devices": "Atualizar lista de dispositivos", "action_debug": "Modo de depuração de ação", "hide_non_standard_entities": "Ocultar entidades não padrão", + "display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo", "update_trans_rules": "Atualizar regras de conversão de entidades", "update_lan_ctrl_config": "Atualizar configuração de controlo LAN" } @@ -89,10 +114,26 @@ }, "homes_select": { "title": "Selecionar novamente a Casa e os Dispositivos", - "description": "## Instruções de Utilização\r\n### Modo de Controlo\r\n- Automático: Quando houver um gateway central Xiaomi disponível na rede local, o Home Assistant priorizará o envio de comandos através dele para obter controlo local. Se não existir um gateway central, tentará enviar comandos através da função de controlo LAN da Xiaomi. Apenas se estas condições não forem satisfeitas, os comandos serão enviados pela nuvem.\r\n- Nuvem: Todos os comandos de controlo são enviados através da nuvem.\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\r\n \r\n### Olá {nick_name}, selecione o modo de controlo da integração e a casa da qual pretende importar dispositivos.", + "description": "## Instruções de Utilização\r\n### Importar dispositivos da casa\r\nA integração adicionará dispositivos das casas selecionadas.\r\n### Filtrar Dispositivos\r\nSuporta a filtragem de dispositivos por nome da sala, tipo de acesso do dispositivo e modelo do dispositivo, e também suporta a filtragem por dimensão do dispositivo. **{local_count}** dispositivos foram filtrados.\r\n### Modo de Controlo\r\n- Automático: Quando houver um gateway central Xiaomi disponível na rede local, o Home Assistant priorizará o envio de comandos através dele para obter controlo local. Se não existir um gateway central, tentará enviar comandos através da função de controlo LAN da Xiaomi. Apenas se estas condições não forem satisfeitas, os comandos serão enviados pela nuvem.\r\n- Nuvem: Todos os comandos de controlo são enviados através da nuvem.", "data": { - "ctrl_mode": "Modo de Controlo", - "home_infos": "Importar dispositivos da casa" + "home_infos": "Importar dispositivos da casa", + "devices_filter": "Filtrar Dispositivos", + "ctrl_mode": "Modo de Controlo" + } + }, + "devices_filter": { + "title": "Filtrar Dispositivos", + "description": "## Instruções de Utilização\r\nSuporta a filtragem de dispositivos por nome da sala, tipo de acesso do dispositivo e modelo do dispositivo, e também suporta a filtragem por dimensão do dispositivo. A lógica de filtragem é a seguinte:\r\n- Primeiro, de acordo com a lógica estatística, obtenha a união ou interseção de todos os itens incluídos, depois obtenha a interseção ou união dos itens excluídos, e finalmente subtraia o [resultado do resumo incluído] do [resultado do resumo excluído] para obter o [resultado do filtro].\r\n- Se nenhum item incluído for selecionado, significa que todos estão incluídos.\r\n### Modo de Filtragem\r\n- Excluir: Remover itens indesejados.\r\n- Incluir: Incluir itens desejados.\r\n### Lógica Estatística\r\n- Lógica E: Pegue a interseção de todos os itens no mesmo modo.\r\n- Lógica OU: Pegue a união de todos os itens no mesmo modo.\r\n\r\nVocê também pode ir para a página [Configuração > Atualizar Lista de Dispositivos] do item de integração, marcar [Filtrar Dispositivos] para refiltrar.", + "data": { + "room_filter_mode": "Filtrar por Sala", + "room_list": "Salas", + "type_filter_mode": "Filtrar por Tipo de Dispositivo", + "type_list": "Tipos de Dispositivo", + "model_filter_mode": "Filtrar por Modelo de Dispositivo", + "model_list": "Modelos de Dispositivo", + "devices_filter_mode": "Filtrar Dispositivos", + "device_list": "Lista de Dispositivos", + "statistics_logic": "Lógica de Estatísticas" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "Confirmar Configuração", - "description": "Olá **{nick_name}**, confirme a informação da configuração mais recente e depois clique em SUBMETER.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nAlcunha:\t{nick_name_new}\r\nModo de depuração de ação:\t{action_debug}\r\nOcultar entidades não padrão:\t{hide_non_standard_entities}\r\nAlterações aos Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração das Regras de Transformação:\tExistem **{trans_rules_count}** regras no total, com **{trans_rules_count_success}** regras atualizadas", + "description": "Olá **{nick_name}**, confirme a informação da configuração mais recente e depois clique em SUBMETER.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nAlcunha:\t{nick_name_new}\r\nModo de depuração de ação:\t{action_debug}\r\nOcultar entidades não padrão:\t{hide_non_standard_entities}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações aos Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração das Regras de Transformação:\tExistem **{trans_rules_count}** regras no total, com **{trans_rules_count_success}** regras atualizadas", "data": { "confirm": "Confirmar a alteração" } @@ -141,4 +182,4 @@ "inconsistent_account": "A informação da conta é inconsistente. Por favor, inicie sessão com a conta correta." } } -} +} \ No newline at end of file diff --git a/custom_components/xiaomi_home/translations/ru.json b/custom_components/xiaomi_home/translations/ru.json index 8418f93..6ea2734 100644 --- a/custom_components/xiaomi_home/translations/ru.json +++ b/custom_components/xiaomi_home/translations/ru.json @@ -23,14 +23,38 @@ "description": "Нажмите кнопку «Далее», чтобы повторить попытку" }, "homes_select": { - "title": "Выберите дом и устройства", - "description": "## Инструкция по использованию\r\n### Режим управления\r\n- Авто: Когда в локальной сети доступен центральный шлюз Xiaomi, Home Assistant будет в первую очередь отправлять команды управления устройствами через центральный шлюз для достижения локализованного управления. Если в локальной сети нет центрального шлюза, он попытается отправить команды управления через протокол Xiaomi OT для достижения локализованного управления. Только если вышеуказанные условия локализованного управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: Команды управления отправляются только через облако.\r\n### Импорт домашнего устройства\r\nИнтеграция добавит устройства из выбранных домов.\r\n### Режим синхронизации имен комнат\r\nПри синхронизации устройств из приложения Xiaomi Home в Home Assistant имена комнат устройств в Home Assistant будут именоваться в соответствии с именами дома или комнаты в приложении Xiaomi Home.\r\n- Не синхронизировать: устройство не будет добавлено в любую область.\r\n- Другие параметры: область, в которую добавляется устройство, называется именем дома или комнаты в приложении Xiaomi Home.\r\n### Режим отладки Action\r\nДля методов, определенных в MIoT-Spec-V2, помимо создания уведомительной сущности будет создана сущность текстового поля ввода, которую можно использовать для отправки команд управления устройством во время отладки.\r\n### Скрыть нестандартные сущности\r\nСкрыть сущности, созданные нестандартными примерами MIoT-Spec-V2, имена которых начинаются с « * ».\r\n\r\n \r\n### {nick_name} Здравствуйте! Выберите режим управления интеграцией и дом, в котором находятся устройства, которые вы хотите добавить.", + "title": "Выберите семью и устройство", + "description": "## Введение\r\n### Импорт семьи устройства\r\nИнтеграция добавит устройства из выбранной семьи.\r\n### Режим синхронизации имени комнаты\r\nПри синхронизации устройств из приложения Mi Home с Home Assistant, название области в Home Assistant будет следовать следующим правилам. Обратите внимание, что процесс синхронизации не изменит настройки семьи и комнаты в приложении Mi Home.\r\n- Не синхронизировать: Устройство не будет добавлено ни в одну область.\r\n- Другие варианты: Область, в которую добавляется устройство, будет названа в честь имени семьи или комнаты в приложении Mi Home.\r\n### Расширенные настройки\r\nПоказать расширенные настройки для изменения профессиональных параметров конфигурации интеграции.\r\n\r\n \r\n### {nick_name} Здравствуйте! Пожалуйста, выберите семью, в которую вы хотите добавить устройство.", + "data": { + "home_infos": "Импорт семьи устройства", + "area_name_rule": "Режим синхронизации имени комнаты", + "advanced_options": "Расширенные настройки" + } + }, + "advanced_options": { + "title": "Расширенные настройки", + "description": "## Введение\r\n### Если вы не очень хорошо понимаете значение следующих параметров, оставьте их по умолчанию.\r\n### Фильтрация устройств\r\nПоддерживает фильтрацию устройств по названию комнаты и типу устройства, а также фильтрацию по уровню устройства.\r\n### Режим управления\r\n- Автоматически: при наличии доступного центрального шлюза Xiaomi в локальной сети Home Assistant Home Assistant будет отправлять команды управления устройствами через центральный шлюз для локального управления. Если центрального шлюза нет в локальной сети, Home Assistant попытается отправить команды управления устройствами через протокол OT Xiaomi для локального управления. Только если вышеуказанные условия локального управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: команды управления отправляются только через облако.\r\n### Режим отладки действий\r\nДля методов, определенных устройством MIoT-Spec-V2, помимо создания уведомления, будет создана сущность текстового поля, которую вы можете использовать для отправки команд управления устройством во время отладки.\r\n### Скрыть нестандартные сущности\r\nСкрыть сущности, созданные нестандартными экземплярами MIoT-Spec-V2, имена которых начинаются с «*».\r\n### Отображать уведомления о изменении состояния устройства\r\nОтображать подробные уведомления о изменении состояния устройства, показывая только выбранные уведомления.", "data": { + "devices_filter": "Фильтрация устройств", "ctrl_mode": "Режим управления", - "home_infos": "Импорт домашнего устройства", - "area_name_rule": "Режим синхронизации имен комнат", - "action_debug": "Режим отладки Action", - "hide_non_standard_entities": "Скрыть нестандартные сущности" + "action_debug": "Режим отладки действий", + "hide_non_standard_entities": "Скрыть нестандартные сущности", + "display_devices_changed_notify": "Отображать уведомления о изменении состояния устройства" + } + }, + "devices_filter": { + "title": "Фильтрация устройств", + "description": "## Инструкция по использованию\r\nПоддерживает фильтрацию устройств по названию комнаты, типу доступа устройства и модели устройства, а также поддерживает фильтрацию по размеру устройства. Логика фильтрации следующая:\r\n- Сначала, согласно статистической логике, получите объединение или пересечение всех включенных элементов, затем получите пересечение или объединение исключенных элементов, и, наконец, вычтите [включенный итоговый результат] из [исключенного итогового результата], чтобы получить [результат фильтрации].\r\n- Если не выбраны включенные элементы, это означает, что все включены.\r\n### Режим фильтрации\r\n- Исключить: Удалить ненужные элементы.\r\n- Включить: Включить нужные элементы.\r\n### Статистическая логика\r\n- Логика И: Взять пересечение всех элементов в одном режиме.\r\n- Логика ИЛИ: Взять объединение всех элементов в одном режиме.\r\n\r\nВы также можете перейти на страницу [Конфигурация > Обновить список устройств] элемента интеграции, установить флажок [Фильтровать устройства], чтобы повторно отфильтровать.", + "data": { + "room_filter_mode": "Фильтрация по комнатам семьи", + "room_list": "Комнаты семьи", + "type_filter_mode": "Фильтрация по типу устройства", + "type_list": "Типы устройств", + "model_filter_mode": "Фильтрация по модели устройства", + "model_list": "Модели устройств", + "devices_filter_mode": "Фильтрация устройств", + "device_list": "Список устройств", + "statistics_logic": "Логика статистики" } } }, @@ -76,8 +100,9 @@ "update_devices": "Обновить список устройств", "action_debug": "Режим отладки Action", "hide_non_standard_entities": "Скрыть нестандартные сущности", - "update_trans_rules": "Обновить правила преобразования сущностей (глобальная настройка)", - "update_lan_ctrl_config": "Обновить конфигурацию управления LAN (глобальная настройка)" + "display_devices_changed_notify": "Отображать уведомления о изменении состояния устройства", + "update_trans_rules": "Обновить правила преобразования сущностей", + "update_lan_ctrl_config": "Обновить конфигурацию управления LAN" } }, "update_user_info": { @@ -89,10 +114,26 @@ }, "homes_select": { "title": "Выберите дом и устройства", - "description": "## Инструкция по использованию\r\n### Режим управления\r\n- Авто: Когда в локальной сети доступен центральный шлюз Xiaomi, Home Assistant будет в первую очередь отправлять команды управления устройствами через центральный шлюз для достижения локализованного управления. Если в локальной сети нет центрального шлюза, он попытается отправить команды управления через протокол Xiaomi OT для достижения локализованного управления. Только если вышеуказанные условия локализованного управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: Команды управления отправляются только через облако.\r\n### Импорт домашнего устройства\r\nИнтеграция добавит устройства из выбранных домов.\r\n \r\n### {nick_name} Здравствуйте! Выберите режим управления интеграцией и дом, в котором находятся устройства, которые вы хотите добавить.", + "description": "## Инструкция по использованию\r\n### Импорт домашнего устройства\r\nИнтеграция добавит устройства из выбранных домов.\r\n### Фильтрация устройств\r\nПоддерживает фильтрацию устройств по названию комнаты, типу доступа устройства и модели устройства, а также поддерживает фильтрацию по размеру устройства. Отфильтровано **{local_count}** устройств.\r\n### Режим управления\r\n- Авто: Когда в локальной сети доступен центральный шлюз Xiaomi, Home Assistant будет в первую очередь отправлять команды управления устройствами через центральный шлюз для достижения локализованного управления. Если в локальной сети нет центрального шлюза, он попытается отправить команды управления через протокол Xiaomi OT для достижения локализованного управления. Только если вышеуказанные условия локализованного управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: Команды управления отправляются только через облако.", "data": { - "ctrl_mode": "Режим управления", - "home_infos": "Импорт домашнего устройства" + "home_infos": "Импорт домашнего устройства", + "devices_filter": "Фильтрация устройств", + "ctrl_mode": "Режим управления" + } + }, + "devices_filter": { + "title": "Фильтрация устройств", + "description": "## Инструкция по использованию\r\nПоддерживает фильтрацию устройств по названию комнаты, типу доступа устройства и модели устройства, а также поддерживает фильтрацию по размеру устройства. Логика фильтрации следующая:\r\n- Сначала, согласно статистической логике, получите объединение или пересечение всех включенных элементов, затем получите пересечение или объединение исключенных элементов, и, наконец, вычтите [включенный итоговый результат] из [исключенного итогового результата], чтобы получить [результат фильтрации].\r\n- Если не выбраны включенные элементы, это означает, что все включены.\r\n### Режим фильтрации\r\n- Исключить: Удалить ненужные элементы.\r\n- Включить: Включить нужные элементы.\r\n### Статистическая логика\r\n- Логика И: Взять пересечение всех элементов в одном режиме.\r\n- Логика ИЛИ: Взять объединение всех элементов в одном режиме.\r\n\r\nВы также можете перейти на страницу [Конфигурация > Обновить список устройств] элемента интеграции, установить флажок [Фильтровать устройства], чтобы повторно отфильтровать.", + "data": { + "room_filter_mode": "Фильтрация по комнатам семьи", + "room_list": "Комнаты семьи", + "type_filter_mode": "Фильтрация по типу устройства", + "type_list": "Типы устройств", + "model_filter_mode": "Фильтрация по модели устройства", + "model_list": "Модели устройств", + "devices_filter_mode": "Фильтрация устройств", + "device_list": "Список устройств", + "statistics_logic": "Логика статистики" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "Подтверждение настройки", - "description": "**{nick_name}** Здравствуйте! Подтвердите последнюю информацию о настройке и нажмите «Отправить». Интеграция будет перезагружена с использованием обновленных настроек.\r\n\r\nЯзык интеграции:\t{lang_new}\r\nИмя пользователя:\t{nick_name_new}\r\nРежим отладки Action:\t{action_debug}\r\nСкрыть непроизводственные сущности:\t{hide_non_standard_entities}\r\nИзменение устройства:\tДобавлено **{devices_add}** устройство, удалено **{devices_remove}** устройства\r\nИзменение правил преобразования:\tВсего **{trans_rules_count}** правил, обновлено **{trans_rules_count_success}** правил", + "description": "**{nick_name}** Здравствуйте! Подтвердите последнюю информацию о настройке и нажмите «Отправить». Интеграция будет перезагружена с использованием обновленных настроек.\r\n\r\nЯзык интеграции:\t{lang_new}\r\nИмя пользователя:\t{nick_name_new}\r\nРежим отладки Action:\t{action_debug}\r\nСкрыть непроизводственные сущности:\t{hide_non_standard_entities}\r\nОтображать уведомления о изменении состояния устройства:\t{display_devices_changed_notify}\r\nИзменение устройства:\tДобавлено **{devices_add}** устройство, удалено **{devices_remove}** устройства\r\nИзменение правил преобразования:\tВсего **{trans_rules_count}** правил, обновлено **{trans_rules_count_success}** правил", "data": { "confirm": "Подтвердить изменения" } diff --git a/custom_components/xiaomi_home/translations/zh-Hans.json b/custom_components/xiaomi_home/translations/zh-Hans.json index 37b905d..f2f33d1 100644 --- a/custom_components/xiaomi_home/translations/zh-Hans.json +++ b/custom_components/xiaomi_home/translations/zh-Hans.json @@ -24,13 +24,37 @@ }, "homes_select": { "title": "选择家庭与设备", - "description": "## 使用介绍\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令,以实现本地化控制功能。本地局域网不存在中枢时,会尝试通过小米OT协议发送控制指令,以实现本地化控制功能。只有当上述本地化控制条件不满足时,设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### 导入设备的家庭\r\n集成将添加已选中家庭中的设备。\r\n### 房间名同步模式\r\n将设备从米家APP同步到 Home Assistant 时,设备在 Home Assistant 中所处区域的名称的命名方式将遵循以下规则。注意,设备同步过程不会改变米家APP中家庭和房间的设置。\r\n- 不同步:设备不会被添加至任何区域。\r\n- 其它选项:设备所添加到的区域以米家APP中的家庭或房间名称命名。\r\n### Action 调试模式\r\n对于设备 MIoT-Spec-V2 定义的方法,在生成通知实体之外,还会生成一个文本输入框实体,您可以在调试时用它向设备发送控制指令。\r\n### 隐藏非标准生成实体\r\n隐藏名称以“*”开头的非标准 MIoT-Spec-V2 实例生成的实体。\r\n\r\n \r\n### {nick_name} 您好!请选择集成控制模式以及您想要添加的设备所处的家庭。", + "description": "## 使用介绍\r\n### 导入设备的家庭\r\n集成将添加已选中家庭中的设备。\r\n### 房间名同步模式\r\n将设备从米家APP同步到 Home Assistant 时,设备在 Home Assistant 中所处区域的名称的命名方式将遵循以下规则。注意,设备同步过程不会改变米家APP中家庭和房间的设置。\r\n- 不同步:设备不会被添加至任何区域。\r\n- 其它选项:设备所添加到的区域以米家APP中的家庭或房间名称命名。\r\n### 高级设置选项\r\n展示高级设置选项,对集成的专业配置选项进行修改。\r\n\r\n \r\n### {nick_name} 您好!请选择您想要添加的设备所处家庭。", "data": { - "ctrl_mode": "控制模式", "home_infos": "导入设备的家庭", "area_name_rule": "房间名同步模式", + "advanced_options": "高级设置选项" + } + }, + "advanced_options": { + "title": "高级设置选项", + "description": "## 使用介绍\r\n### 除非您非常清楚下列选项的含义,否则请保持默认。\r\n### 筛选设备\r\n支持按照家庭房间名称、设备接入类型、设备型号筛选设备,同时也支持设备维度筛选。\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令,以实现本地化控制功能。本地局域网不存在中枢时,会尝试通过小米OT协议发送控制指令,以实现本地化控制功能。只有当上述本地化控制条件不满足时,设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### Action 调试模式\r\n对于设备 MIoT-Spec-V2 定义的方法,在生成通知实体之外,还会生成一个文本输入框实体,您可以在调试时用它向设备发送控制指令。\r\n### 隐藏非标准生成实体\r\n隐藏名称以“*”开头的非标准 MIoT-Spec-V2 实例生成的实体。\r\n### 显示设备状态变化通知\r\n细化显示设备状态变化通知,只显示勾选的通知消息。", + "data": { + "devices_filter": "筛选设备", + "ctrl_mode": "控制模式", "action_debug": "Action 调试模式", - "hide_non_standard_entities": "隐藏非标准生成实体" + "hide_non_standard_entities": "隐藏非标准生成实体", + "display_devices_changed_notify": "显示设备状态变化通知" + } + }, + "devices_filter": { + "title": "筛选设备", + "description": "## 使用介绍\r\n支持按照家庭房间名称、设备接入类型、设备型号筛选设备,同时也支持设备维度筛选,筛选逻辑如下:\r\n- 会先根据统计逻辑获取所有包含项的并集或者交集,然后再获取排除项的交集或者并集,最后将【包含汇总结果】减去【排除汇总结果】得到【筛选结果】\r\n- 如未选择包含项,表示包含全部。\r\n### 筛选模式\r\n- 排除:移除不需要的项。\r\n- 包含:包含需要的项。\r\n### 统计逻辑\r\n- 与逻辑:取所有同模式筛选项的交集。\r\n- 或逻辑:取所有同模式筛选项的并集。\r\n\r\n您也可以进入集成项的【配置>更新设备列表】页面,勾选【筛选设备】重新筛选。", + "data": { + "room_filter_mode": "筛选家庭房间", + "room_list": "家庭房间", + "type_filter_mode": "筛选设备接入类型", + "type_list": "设备接入类型", + "model_filter_mode": "筛选设备型号", + "model_list": "设备型号", + "devices_filter_mode": "筛选设备", + "device_list": "设备列表", + "statistics_logic": "统计逻辑" } } }, @@ -76,6 +100,7 @@ "update_devices": "更新设备列表", "action_debug": "Action 调试模式", "hide_non_standard_entities": "隐藏非标准生成实体", + "display_devices_changed_notify": "显示设备状态变化通知", "update_trans_rules": "更新实体转换规则", "update_lan_ctrl_config": "更新局域网控制配置" } @@ -89,10 +114,26 @@ }, "homes_select": { "title": "重新选择家庭与设备", - "description": "## 使用介绍\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令,以实现本地化控制功能。本地局域网不存在中枢时,会尝试通过小米OT协议发送控制指令,以实现本地化控制功能。只有当上述本地化控制条件不满足时,设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### 导入设备的家庭\r\n集成将添加已选中家庭中的设备。\r\n \r\n### {nick_name} 您好!请选择集成控制模式以及您想要添加的设备所处的家庭。", + "description": "## 使用介绍\r\n### 导入设备的家庭\r\n集成将添加已选中家庭中的设备。\r\n### 筛选设备\r\n支持按照家庭房间名称、设备接入类型、设备型号筛选设备,同时也支持设备维度筛选,已筛选出 **{local_count}** 个设备。\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令,以实现本地化控制功能。本地局域网不存在中枢时,会尝试通过小米OT协议发送控制指令,以实现本地化控制功能。只有当上述本地化控制条件不满足时,设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。", "data": { - "ctrl_mode": "控制模式", - "home_infos": "导入设备的家庭" + "home_infos": "导入设备的家庭", + "devices_filter": "筛选设备", + "ctrl_mode": "控制模式" + } + }, + "devices_filter": { + "title": "筛选设备", + "description": "## 使用介绍\r\n支持按照家庭房间名称、设备接入类型、设备型号筛选设备,同时也支持设备维度筛选,筛选逻辑如下:\r\n- 会先根据统计逻辑获取所有包含项的并集或者交集,然后再获取排除项的交集或者并集,最后将【包含汇总结果】减去【排除汇总结果】得到【筛选结果】\r\n- 如未选择包含项,表示包含全部。\r\n### 筛选模式\r\n- 排除:移除不需要的项。\r\n- 包含:包含需要的项。\r\n### 统计逻辑\r\n- 与逻辑:取所有同模式筛选项的交集。\r\n- 或逻辑:取所有同模式筛选项的并集。\r\n\r\n您也可以进入集成项的【配置>更新设备列表】页面,勾选【筛选设备】重新筛选。", + "data": { + "room_filter_mode": "筛选家庭房间", + "room_list": "家庭房间", + "type_filter_mode": "筛选设备接入类型", + "type_list": "设备接入类型", + "model_filter_mode": "筛选设备型号", + "model_list": "设备型号", + "devices_filter_mode": "筛选设备", + "device_list": "设备列表", + "statistics_logic": "统计逻辑" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "确认配置", - "description": "**{nick_name}** 您好!请确认最新的配置信息,然后点击“提交”。\r\n集成将会使用更新后的配置重新载入。\r\n\r\n集成语言:\t{lang_new}\r\n用户昵称:\t{nick_name_new}\r\nAction 调试模式:\t{action_debug}\r\n隐藏非标准生成实体:\t{hide_non_standard_entities}\r\n设备变化:\t新增 **{devices_add}** 个设备,移除 **{devices_remove}** 个设备\r\n转换规则变化:\t共条 **{trans_rules_count}** 规则,更新 **{trans_rules_count_success}** 条规则", + "description": "**{nick_name}** 您好!请确认最新的配置信息,然后点击“提交”。\r\n集成将会使用更新后的配置重新载入。\r\n\r\n集成语言:\t{lang_new}\r\n用户昵称:\t{nick_name_new}\r\nAction 调试模式:\t{action_debug}\r\n隐藏非标准生成实体:\t{hide_non_standard_entities}\r\n显示设备状态变化通知:\t{display_devices_changed_notify}\r\n设备变化:\t新增 **{devices_add}** 个设备,移除 **{devices_remove}** 个设备\r\n转换规则变化:\t共条 **{trans_rules_count}** 规则,更新 **{trans_rules_count_success}** 条规则", "data": { "confirm": "确认修改" } diff --git a/custom_components/xiaomi_home/translations/zh-Hant.json b/custom_components/xiaomi_home/translations/zh-Hant.json index c8551a0..5d9f7ba 100644 --- a/custom_components/xiaomi_home/translations/zh-Hant.json +++ b/custom_components/xiaomi_home/translations/zh-Hant.json @@ -24,13 +24,37 @@ }, "homes_select": { "title": "選擇家庭與設備", - "description": "## 使用介紹\r\n### 控制模式\r\n- 自動:本地區域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。本地區域網不存在中樞時,會嘗試通過小米OT協議發送控制指令,以實現本地化控制功能。只有當上述本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令僅通過雲端發送。\r\n### 導入設備的家庭\r\n集成將添加已選中家庭中的設備。\r\n### 房間名同步模式\r\n將設備從米家APP同步到 Home Assistant 時,設備在 Home Assistant 中所處區域的名稱的命名方式將遵循以下規則。注意,設備同步過程不會改變米家APP中家庭和房間的設置。\r\n- 不同步:設備不會被添加至任何區域。\r\n- 其它選項:設備所添加到的區域以米家APP中的家庭或房間名稱命名。\r\n### Action 調試模式\r\n對於設備 MIoT-Spec-V2 定義的方法,在生成通知實體之外,還會生成一個文本輸入框實體,您可以在調試時用它向設備發送控制指令。\r\n### 隱藏非標準生成實體\r\n隱藏名稱以“*”開頭的非標準 MIoT-Spec-V2 實例生成的實體。\r\n\r\n \r\n### {nick_name} 您好!請選擇集成控制模式以及您想要添加的設備所處的家庭。", + "description": "## 使用介紹\r\n### 導入設備的家庭\r\n集成將添加已選中家庭中的設備。\r\n### 房間名同步模式\r\n將設備從米家APP同步到 Home Assistant 時,設備在 Home Assistant 中所處區域的名稱的命名方式將遵循以下規則。注意,設備同步過程不會改變米家APP中家庭和房間的設置。\r\n- 不同步:設備不會被添加至任何區域。\r\n- 其它選項:設備所添加到的區域以米家APP中的家庭或房間名稱命名。\r\n### 高級設置選項\r\n展示高級設置選項,對集成的專業配置選項進行修改。\r\n\r\n \r\n### {nick_name} 您好!請選擇您想要添加的設備所處家庭。", "data": { - "ctrl_mode": "控制模式", "home_infos": "導入設備的家庭", "area_name_rule": "房間名同步模式", + "advanced_options": "高級設置選項" + } + }, + "advanced_options": { + "title": "高級設置選項", + "description": "## 使用介紹\r\n### 除非您非常清楚下列選項的含義,否則請保持默認。\r\n### 篩選設備\r\n支持按照房間名稱和設備類型篩選設備,同時也支持設備維度篩選。\r\n### 控制模式\r\n- 自動:本地局域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。本地局域網不存在中樞時,會嘗試通過小米OT協議發送控制指令,以實現本地化控制功能。只有當上述本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令僅通過雲端發送。\r\n### Action 調試模式\r\n對於設備 MIoT-Spec-V2 定義的方法,在生成通知實體之外,還會生成一個文本輸入框實體,您可以在調試時用它向設備發送控制指令。\r\n### 隱藏非標準生成實體\r\n隱藏名稱以“*”開頭的非標準 MIoT-Spec-V2 實例生成的實體。\r\n### 顯示設備狀態變化通知\r\n細化顯示設備狀態變化通知,只顯示勾選的通知消息。", + "data": { + "devices_filter": "篩選設備", + "ctrl_mode": "控制模式", "action_debug": "Action 調試模式", - "hide_non_standard_entities": "隱藏非標準生成實體" + "hide_non_standard_entities": "隱藏非標準生成實體", + "display_devices_changed_notify": "顯示設備狀態變化通知" + } + }, + "devices_filter": { + "title": "篩選設備", + "description": "## 使用介紹\r\n支持按照家庭房間名稱、設備接入類型、設備型號篩選設備,同時也支持設備維度篩選,篩選邏輯如下:\r\n- 會先根據統計邏輯獲取所有包含項的並集或者交集,然後再獲取排除項的交集或者並集,最後將【包含匯總結果】減去【排除匯總結果】得到【篩選結果】\r\n- 如未選擇包含項,表示包含全部。\r\n### 篩選模式\r\n- 排除:移除不需要的項。\r\n- 包含:包含需要的項。\r\n### 統計邏輯\r\n- 與邏輯:取所有同模式篩選項的交集。\r\n- 或邏輯:取所有同模式篩選項的並集。\r\n\r\n您也可以進入集成項的【配置>更新設備列表】頁面,勾選【篩選設備】重新篩選。", + "data": { + "room_filter_mode": "篩選家庭房間", + "room_list": "家庭房間", + "type_filter_mode": "篩選設備接入類型", + "type_list": "設備接入類型", + "model_filter_mode": "篩選設備型號", + "model_list": "設備型號", + "devices_filter_mode": "篩選設備", + "device_list": "設備列表", + "statistics_logic": "統計邏輯" } } }, @@ -76,6 +100,7 @@ "update_devices": "更新設備列表", "action_debug": "Action 調試模式", "hide_non_standard_entities": "隱藏非標準生成實體", + "display_devices_changed_notify": "顯示設備狀態變化通知", "update_trans_rules": "更新實體轉換規則", "update_lan_ctrl_config": "更新局域網控制配置" } @@ -89,10 +114,26 @@ }, "homes_select": { "title": "重新選擇家庭與設備", - "description": "\r\n## 使用介紹\r\n### 控制模式\r\n- 自動:本地局域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。只有當本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令強制通過雲端發送。\r\n### 導入設備的家庭\r\n集成將添加已選中家庭中的設備。\r\n \r\n### {nick_name} 您好!請選擇集成控制模式以及您想要添加的設備所處的家庭。", + "description": "## 使用介紹\r\n### 導入設備的家庭\r\n集成將添加已選中家庭中的設備。\r\n### 篩選設備\r\n支持按照家庭房間名稱、設備接入類型、設備型號篩選設備,同時也支持設備維度篩選,已篩選出 **{local_count}** 個設備。\r\n### 控制模式\r\n- 自動:本地局域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。只有當本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令強制通過雲端發送。", "data": { - "ctrl_mode": "控制模式", - "home_infos": "導入設備的家庭" + "home_infos": "導入設備的家庭", + "devices_filter": "篩選設備", + "ctrl_mode": "控制模式" + } + }, + "devices_filter": { + "title": "篩選設備", + "description": "## 使用介紹\r\n支持按照家庭房間名稱、設備接入類型、設備型號篩選設備,同時也支持設備維度篩選,篩選邏輯如下:\r\n- 會先根據統計邏輯獲取所有包含項的並集或者交集,然後再獲取排除項的交集或者並集,最後將【包含匯總結果】減去【排除匯總結果】得到【篩選結果】\r\n- 如未選擇包含項,表示包含全部。\r\n### 篩選模式\r\n- 排除:移除不需要的項。\r\n- 包含:包含需要的項。\r\n### 統計邏輯\r\n- 與邏輯:取所有同模式篩選項的交集。\r\n- 或邏輯:取所有同模式篩選項的並集。\r\n\r\n您也可以進入集成項的【配置>更新設備列表】頁面,勾選【篩選設備】重新篩選。", + "data": { + "room_filter_mode": "篩選家庭房間", + "room_list": "家庭房間", + "type_filter_mode": "篩選設備接入類型", + "type_list": "設備接入類型", + "model_filter_mode": "篩選設備型號", + "model_list": "設備型號", + "devices_filter_mode": "篩選設備", + "device_list": "設備列表", + "statistics_logic": "統計邏輯" } }, "update_trans_rules": { @@ -112,7 +153,7 @@ }, "config_confirm": { "title": "確認配置", - "description": "**{nick_name}** 您好!請確認最新的配置信息,然後點擊“提交”。\r\n集成將會使用更新後的配置重新載入。\r\n\r\n集成語言:\t{lang_new}\r\n用戶暱稱:\t{nick_name_new}\r\nAction 調試模式:\t{action_debug}\r\n隱藏非標準生成實體:\t{hide_non_standard_entities}\r\n設備變化:\t新增 **{devices_add}** 個設備,移除 **{devices_remove}** 個設備\r\n轉換規則變化:\t共條 **{trans_rules_count}** 規則,更新 **{trans_rules_count_success}** 條規則", + "description": "**{nick_name}** 您好!請確認最新的配置信息,然後點擊“提交”。\r\n集成將會使用更新後的配置重新載入。\r\n\r\n集成語言:\t{lang_new}\r\n用戶暱稱:\t{nick_name_new}\r\nAction 調試模式:\t{action_debug}\r\n隱藏非標準生成實體:\t{hide_non_standard_entities}\r\n顯示設備狀態變化通知:\t{display_devices_changed_notify}\r\n設備變化:\t新增 **{devices_add}** 個設備,移除 **{devices_remove}** 個設備\r\n轉換規則變化:\t共條 **{trans_rules_count}** 規則,更新 **{trans_rules_count_success}** 條規則", "data": { "confirm": "確認修改" } From 6d978872e77eccc247f6728574d4a47bcb3abcce Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:37:13 +0800 Subject: [PATCH 16/17] docs: update changelog and version to v0.1.4b1 (#428) --- CHANGELOG.md | 11 +++++++++++ custom_components/xiaomi_home/manifest.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50dfdee..d19d4fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # CHANGELOG +## v0.1.4b1 +### Added +- Support devices filter, and device changed notify logical refinement. [#332](https://github.com/XiaoMi/ha_xiaomi_home/pull/332) +### Changed +- Readme amend HACS installation. [#404](https://github.com/XiaoMi/ha_xiaomi_home/pull/404) +### Fixed +- Fix unit_convert AttributeError, Change to catch all Exception. [#396](https://github.com/XiaoMi/ha_xiaomi_home/pull/396) +- Ignore undefined piid and keep processing following arguments. [#377](https://github.com/XiaoMi/ha_xiaomi_home/pull/377) +- Fix some type error, wrong use of any and Any. [#338](https://github.com/XiaoMi/ha_xiaomi_home/pull/338) +- Fix lumi.switch.acn040 identify service translation of zh-Hans [#412](https://github.com/XiaoMi/ha_xiaomi_home/pull/412) + ## v0.1.4b0 ### Added ### Changed diff --git a/custom_components/xiaomi_home/manifest.json b/custom_components/xiaomi_home/manifest.json index b5994a7..43acda5 100644 --- a/custom_components/xiaomi_home/manifest.json +++ b/custom_components/xiaomi_home/manifest.json @@ -26,7 +26,7 @@ "psutil", "aiohttp[speedups]" ], - "version": "v0.1.4b0", + "version": "v0.1.4b1", "zeroconf": [ "_miot-central._tcp.local." ] From 365f4e57d8b3aa7f8f22c9d3aa4b49d6818af9bd Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Tue, 24 Dec 2024 21:58:50 +0800 Subject: [PATCH 17/17] feat: remove duplicate dependency (#390) --- custom_components/xiaomi_home/manifest.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/xiaomi_home/manifest.json b/custom_components/xiaomi_home/manifest.json index 43acda5..5400dfb 100644 --- a/custom_components/xiaomi_home/manifest.json +++ b/custom_components/xiaomi_home/manifest.json @@ -23,8 +23,7 @@ "paho-mqtt<=2.0.0", "numpy", "cryptography", - "psutil", - "aiohttp[speedups]" + "psutil" ], "version": "v0.1.4b1", "zeroconf": [