diff --git a/custom_components/xiaomi_home/miot/miot_spec.py b/custom_components/xiaomi_home/miot/miot_spec.py index 2866853..7820fec 100644 --- a/custom_components/xiaomi_home/miot/miot_spec.py +++ b/custom_components/xiaomi_home/miot/miot_spec.py @@ -977,8 +977,8 @@ class _SpecBoolTranslation: DEFAULT_INTEGRATION_LANGUAGE, None)) if data_default: self._data_default = [ - {'value': True, 'description': data_default[True]}, - {'value': False, 'description': data_default[False]} + {'value': True, 'description': data_default['true']}, + {'value': False, 'description': data_default['false']} ] for urn, key in data['data'].items(): @@ -991,8 +991,8 @@ class _SpecBoolTranslation: DEFAULT_INTEGRATION_LANGUAGE, None)) if trans_data: self._data[urn] = [ - {'value': True, 'description': trans_data[True]}, - {'value': False, 'description': trans_data[False]} + {'value': True, 'description': trans_data['true']}, + {'value': False, 'description': trans_data['false']} ] async def deinit_async(self) -> None: diff --git a/custom_components/xiaomi_home/miot/miot_storage.py b/custom_components/xiaomi_home/miot/miot_storage.py index ee8e955..3767365 100644 --- a/custom_components/xiaomi_home/miot/miot_storage.py +++ b/custom_components/xiaomi_home/miot/miot_storage.py @@ -65,13 +65,13 @@ from cryptography import x509 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ed25519 -from custom_components.xiaomi_home.miot.common import MIoTHttp # pylint: disable=relative-beyond-top-level from .const import ( MANUFACTURER_EFFECTIVE_TIME, MIHOME_CA_CERT_STR, MIHOME_CA_CERT_SHA256) +from .common import MIoTHttp from .miot_error import MIoTCertError, MIoTError, MIoTStorageError _LOGGER = logging.getLogger(__name__) @@ -217,7 +217,8 @@ class MIoTStorage: w_bytes = json.dumps(data).encode('utf-8') else: _LOGGER.error( - 'save error, unsupported data type, %s', type(data).__name__) + 'save error, unsupported data type, %s', + type(data).__name__) return False with open(full_path, 'wb') as w_file: w_file.write(w_bytes) diff --git a/custom_components/xiaomi_home/miot/specs/bool_trans.yaml b/custom_components/xiaomi_home/miot/specs/bool_trans.yaml index 6036504..7bb3483 100644 --- a/custom_components/xiaomi_home/miot/specs/bool_trans.yaml +++ b/custom_components/xiaomi_home/miot/specs/bool_trans.yaml @@ -59,188 +59,188 @@ data: urn:miot-spec-v2:property:wifi-ssid-hidden:000000E3: yes_no urn:miot-spec-v2:property:wind-reverse:00000117: yes_no translate: - default: - de: - true: Wahr - false: Falsch - en: - true: True - false: False - es: - true: Verdadero - false: Falso - fr: - true: Vrai - false: Faux - it: - true: Vero - false: Falso - ja: - true: 真 - false: 偽 - nl: - true: True - false: False - pt: - true: True - false: False - pt-BR: - true: True - false: False - ru: - true: Истина - false: Ложь - zh-Hans: - true: 真 - false: 假 - zh-Hant: - true: 真 - false: 假 - open_close: - de: - true: Öffnen - false: Schließen - en: - true: Open - false: Close - es: - true: Abierto - false: Cerrado - fr: - true: Ouvert - false: Fermer - it: - true: Aperto - false: Chiuso - ja: - true: 開く - false: 閉じる - nl: - true: Open - false: Dicht - pt: - true: Aberto - false: Fechado - pt-BR: - true: Aberto - false: Fechado - ru: - true: Открыть - false: Закрыть - zh-Hans: - true: 开启 - false: 关闭 - zh-Hant: - true: 開啟 - false: 關閉 - yes_no: - de: - true: Ja - false: Nein - en: - true: Yes - false: No - es: - true: Sí - false: No - fr: - true: Oui - false: Non - it: - true: Si - false: No - ja: - true: はい - false: いいえ - nl: - true: Ja - false: Nee - pt: - true: Sim - false: Não - pt-BR: - true: Sim - false: Não - ru: - true: Да - false: Нет - zh-Hans: - true: 是 - false: 否 - zh-Hant: - true: 是 - false: 否 - motion_state: - de: - true: Bewegung erkannt - false: Keine Bewegung erkannt - en: - true: Motion Detected - false: No Motion Detected - es: - true: Movimiento detectado - false: No se detecta movimiento - fr: - true: Mouvement détecté - false: Aucun mouvement détecté - it: - true: Movimento Rilevato, - false: Nessun Movimento Rilevato - ja: - true: 動きを検知 - false: 動きが検出されません - nl: - true: Contact - false: Geen contact - pt: - true: Contato - false: Sem contato - pt-BR: - true: Contato - false: Sem contato - ru: - true: Обнаружено движение - false: Движение не обнаружено - zh-Hans: - true: 有人 - false: 无人 - zh-Hant: - true: 有人 - false: 無人 contact_state: de: - true: Kontakt - false: Kein Kontakt + 'false': Kein Kontakt + 'true': Kontakt en: - true: Contact - false: No Contact + 'false': No Contact + 'true': Contact es: - true: Contacto - false: Sin contacto + 'false': Sin contacto + 'true': Contacto fr: - true: Contact - false: Pas de contact + 'false': Pas de contact + 'true': Contact it: - true: Contatto - false: Nessun contatto + 'false': Nessun contatto + 'true': Contatto ja: - true: 接触 - false: 非接触 + 'false': 非接触 + 'true': 接触 nl: - true: Contact - false: Geen contact + 'false': Geen contact + 'true': Contact pt: - true: Contato - false: Sem contato + 'false': Sem contato + 'true': Contato pt-BR: - true: Contato - false: Sem contato + 'false': Sem contato + 'true': Contato ru: - true: Контакт - false: Нет контакта + 'false': Нет контакта + 'true': Контакт zh-Hans: - true: 接触 - false: 分离 + 'false': 分离 + 'true': 接触 zh-Hant: - true: 接觸 - false: 分離 + 'false': 分離 + 'true': 接觸 + default: + de: + 'false': Falsch + 'true': Wahr + en: + 'false': 'False' + 'true': 'True' + es: + 'false': Falso + 'true': Verdadero + fr: + 'false': Faux + 'true': Vrai + it: + 'false': Falso + 'true': Vero + ja: + 'false': 偽 + 'true': 真 + nl: + 'false': 'False' + 'true': 'True' + pt: + 'false': 'False' + 'true': 'True' + pt-BR: + 'false': 'False' + 'true': 'True' + ru: + 'false': Ложь + 'true': Истина + zh-Hans: + 'false': 假 + 'true': 真 + zh-Hant: + 'false': 假 + 'true': 真 + motion_state: + de: + 'false': Keine Bewegung erkannt + 'true': Bewegung erkannt + en: + 'false': No Motion Detected + 'true': Motion Detected + es: + 'false': No se detecta movimiento + 'true': Movimiento detectado + fr: + 'false': Aucun mouvement détecté + 'true': Mouvement détecté + it: + 'false': Nessun Movimento Rilevato + 'true': Movimento Rilevato + ja: + 'false': 動きが検出されません + 'true': 動きを検知 + nl: + 'false': Geen contact + 'true': Contact + pt: + 'false': Sem contato + 'true': Contato + pt-BR: + 'false': Sem contato + 'true': Contato + ru: + 'false': Движение не обнаружено + 'true': Обнаружено движение + zh-Hans: + 'false': 无人 + 'true': 有人 + zh-Hant: + 'false': 無人 + 'true': 有人 + open_close: + de: + 'false': Schließen + 'true': Öffnen + en: + 'false': Close + 'true': Open + es: + 'false': Cerrado + 'true': Abierto + fr: + 'false': Fermer + 'true': Ouvert + it: + 'false': Chiuso + 'true': Aperto + ja: + 'false': 閉じる + 'true': 開く + nl: + 'false': Dicht + 'true': Open + pt: + 'false': Fechado + 'true': Aberto + pt-BR: + 'false': Fechado + 'true': Aberto + ru: + 'false': Закрыть + 'true': Открыть + zh-Hans: + 'false': 关闭 + 'true': 开启 + zh-Hant: + 'false': 關閉 + 'true': 開啟 + yes_no: + de: + 'false': Nein + 'true': Ja + en: + 'false': 'No' + 'true': 'Yes' + es: + 'false': 'No' + 'true': Sí + fr: + 'false': Non + 'true': Oui + it: + 'false': 'No' + 'true': Si + ja: + 'false': いいえ + 'true': はい + nl: + 'false': Nee + 'true': Ja + pt: + 'false': Não + 'true': Sim + pt-BR: + 'false': Não + 'true': Sim + ru: + 'false': Нет + 'true': Да + zh-Hans: + 'false': 否 + 'true': 是 + zh-Hant: + 'false': 否 + 'true': 是 diff --git a/custom_components/xiaomi_home/miot/specs/spec_filter.yaml b/custom_components/xiaomi_home/miot/specs/spec_filter.yaml index 5b416af..2102e48 100644 --- a/custom_components/xiaomi_home/miot/specs/spec_filter.yaml +++ b/custom_components/xiaomi_home/miot/specs/spec_filter.yaml @@ -1,43 +1,43 @@ urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4: properties: - - "9.*" - - "13.*" - - "15.*" + - 9.* + - 13.* + - 15.* services: - - "10" + - '10' urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01: properties: - - "5.1" + - '5.1' services: - - "4" - - "7" - - "8" + - '4' + - '7' + - '8' urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1: events: - - "2.1" + - '2.1' urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1: services: - - "5" + - '5' urn:miot-spec-v2:device:light:0000A001:philips-strip3: properties: - - "2.2" + - '2.2' services: - - "1" - - "3" + - '1' + - '3' urn:miot-spec-v2:device:light:0000A001:yeelink-color2: properties: - - "3.*" - - "2.5" + - 3.* + - '2.5' urn:miot-spec-v2:device:light:0000A001:yeelink-dnlight2: services: - - "3" + - '3' urn:miot-spec-v2:device:light:0000A001:yeelink-mbulb3: services: - - "3" + - '3' urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1: services: - - "1" - - "5" + - '1' + - '5' urn:miot-spec-v2:device:router:0000A036:xiaomi-rd03: services: - - "*" + - '*' diff --git a/custom_components/xiaomi_home/sensor.py b/custom_components/xiaomi_home/sensor.py index 0a2540c..88b4bac 100644 --- a/custom_components/xiaomi_home/sensor.py +++ b/custom_components/xiaomi_home/sensor.py @@ -53,7 +53,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.components.sensor import SensorEntity, SensorDeviceClass -from homeassistant.components.sensor import DEVICE_CLASS_UNITS, SensorStateClass +from homeassistant.components.sensor import DEVICE_CLASS_UNITS from .miot.miot_device import MIoTDevice, MIoTPropertyEntity from .miot.miot_spec import MIoTSpecProperty diff --git a/test/check_rule_format.py b/test/check_rule_format.py index 5075367..18ce034 100644 --- a/test/check_rule_format.py +++ b/test/check_rule_format.py @@ -16,13 +16,10 @@ MIOT_I18N_RELATIVE_PATH: str = path.join( ROOT_PATH, '../custom_components/xiaomi_home/miot/i18n') SPEC_BOOL_TRANS_FILE = path.join( ROOT_PATH, - '../custom_components/xiaomi_home/miot/specs/bool_trans.json') -SPEC_MULTI_LANG_FILE = path.join( - ROOT_PATH, - '../custom_components/xiaomi_home/miot/specs/multi_lang.json') + '../custom_components/xiaomi_home/miot/specs/bool_trans.yaml') SPEC_FILTER_FILE = path.join( ROOT_PATH, - '../custom_components/xiaomi_home/miot/specs/spec_filter.json') + '../custom_components/xiaomi_home/miot/specs/spec_filter.yaml') def load_json_file(file_path: str) -> Optional[dict]: @@ -54,6 +51,12 @@ def load_yaml_file(file_path: str) -> Optional[dict]: return None +def save_yaml_file(file_path: str, data: dict) -> None: + with open(file_path, 'w', encoding='utf-8') as file: + yaml.safe_dump( + data, file, default_flow_style=False, allow_unicode=True, indent=2) + + def dict_str_str(d: dict) -> bool: """restricted format: dict[str, str]""" if not isinstance(d, dict): @@ -161,25 +164,17 @@ def compare_dict_structure(dict1: dict, dict2: dict) -> bool: def sort_bool_trans(file_path: str): - trans_data: dict = load_json_file(file_path=file_path) + trans_data = load_yaml_file(file_path=file_path) + assert isinstance(trans_data, dict), f'{file_path} format error' trans_data['data'] = dict(sorted(trans_data['data'].items())) for key, trans in trans_data['translate'].items(): trans_data['translate'][key] = dict(sorted(trans.items())) return trans_data -def sort_multi_lang(file_path: str): - multi_lang: dict = load_json_file(file_path=file_path) - multi_lang = dict(sorted(multi_lang.items())) - for urn, trans in multi_lang.items(): - multi_lang[urn] = dict(sorted(trans.items())) - for lang, spec in multi_lang[urn].items(): - multi_lang[urn][lang] = dict(sorted(spec.items())) - return multi_lang - - def sort_spec_filter(file_path: str): - filter_data: dict = load_json_file(file_path=file_path) + filter_data = load_yaml_file(file_path=file_path) + assert isinstance(filter_data, dict), f'{file_path} format error' filter_data = dict(sorted(filter_data.items())) for urn, spec in filter_data.items(): filter_data[urn] = dict(sorted(spec.items())) @@ -188,30 +183,26 @@ def sort_spec_filter(file_path: str): @pytest.mark.github def test_bool_trans(): - data: dict = load_json_file(SPEC_BOOL_TRANS_FILE) + data = load_yaml_file(SPEC_BOOL_TRANS_FILE) + assert isinstance(data, dict) assert data, f'load {SPEC_BOOL_TRANS_FILE} failed' assert bool_trans(data), f'{SPEC_BOOL_TRANS_FILE} format error' @pytest.mark.github def test_spec_filter(): - data: dict = load_json_file(SPEC_FILTER_FILE) + data = load_yaml_file(SPEC_FILTER_FILE) + assert isinstance(data, dict) assert data, f'load {SPEC_FILTER_FILE} failed' assert spec_filter(data), f'{SPEC_FILTER_FILE} format error' -@pytest.mark.github -def test_multi_lang(): - data: dict = load_json_file(SPEC_MULTI_LANG_FILE) - assert data, f'load {SPEC_MULTI_LANG_FILE} failed' - assert nested_3_dict_str_str(data), f'{SPEC_MULTI_LANG_FILE} format error' - - @pytest.mark.github def test_miot_i18n(): for file_name in listdir(MIOT_I18N_RELATIVE_PATH): file_path: str = path.join(MIOT_I18N_RELATIVE_PATH, file_name) - data: dict = load_json_file(file_path) + data = load_json_file(file_path) + assert isinstance(data, dict) assert data, f'load {file_path} failed' assert nested_3_dict_str_str(data), f'{file_path} format error' @@ -220,7 +211,8 @@ def test_miot_i18n(): def test_translations(): for file_name in listdir(TRANS_RELATIVE_PATH): file_path: str = path.join(TRANS_RELATIVE_PATH, file_name) - data: dict = load_json_file(file_path) + data = load_json_file(file_path) + assert isinstance(data, dict) assert data, f'load {file_path} failed' assert dict_str_dict(data), f'{file_path} format error' @@ -237,15 +229,16 @@ def test_miot_lang_integrity(): i18n_names: set[str] = set(listdir(MIOT_I18N_RELATIVE_PATH)) assert len(i18n_names) == len(translations_names) assert i18n_names == translations_names - bool_trans_data: set[str] = load_json_file(SPEC_BOOL_TRANS_FILE) + bool_trans_data = load_yaml_file(SPEC_BOOL_TRANS_FILE) + assert isinstance(bool_trans_data, dict) bool_trans_names: set[str] = set( bool_trans_data['translate']['default'].keys()) assert len(bool_trans_names) == len(translations_names) # Check translation files structure - default_dict: dict = load_json_file( + default_dict = load_json_file( path.join(TRANS_RELATIVE_PATH, integration_lang_list[0])) for name in list(integration_lang_list)[1:]: - compare_dict: dict = load_json_file( + compare_dict = load_json_file( path.join(TRANS_RELATIVE_PATH, name)) if not compare_dict_structure(default_dict, compare_dict): _LOGGER.info( @@ -255,7 +248,7 @@ def test_miot_lang_integrity(): default_dict = load_json_file( path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0])) for name in list(integration_lang_list)[1:]: - compare_dict: dict = load_json_file( + compare_dict = load_json_file( path.join(MIOT_I18N_RELATIVE_PATH, name)) if not compare_dict_structure(default_dict, compare_dict): _LOGGER.info( @@ -272,19 +265,13 @@ def test_miot_data_sort(): 'INTEGRATION_LANGUAGES not sorted, correct order\r\n' f'{list(sort_langs.keys())}') assert json.dumps( - load_json_file(file_path=SPEC_BOOL_TRANS_FILE)) == json.dumps( + load_yaml_file(file_path=SPEC_BOOL_TRANS_FILE)) == json.dumps( sort_bool_trans(file_path=SPEC_BOOL_TRANS_FILE)), ( f'{SPEC_BOOL_TRANS_FILE} not sorted, goto project root path' ' and run the following command sorting, ', 'pytest -s -v -m update ./test/check_rule_format.py') assert json.dumps( - load_json_file(file_path=SPEC_MULTI_LANG_FILE)) == json.dumps( - sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE)), ( - f'{SPEC_MULTI_LANG_FILE} not sorted, goto project root path' - ' and run the following command sorting, ', - 'pytest -s -v -m update ./test/check_rule_format.py') - assert json.dumps( - load_json_file(file_path=SPEC_FILTER_FILE)) == json.dumps( + load_yaml_file(file_path=SPEC_FILTER_FILE)) == json.dumps( sort_spec_filter(file_path=SPEC_FILTER_FILE)), ( f'{SPEC_FILTER_FILE} not sorted, goto project root path' ' and run the following command sorting, ', @@ -294,11 +281,8 @@ def test_miot_data_sort(): @pytest.mark.update def test_sort_spec_data(): sort_data: dict = sort_bool_trans(file_path=SPEC_BOOL_TRANS_FILE) - save_json_file(file_path=SPEC_BOOL_TRANS_FILE, data=sort_data) + save_yaml_file(file_path=SPEC_BOOL_TRANS_FILE, data=sort_data) _LOGGER.info('%s formatted.', SPEC_BOOL_TRANS_FILE) - sort_data = sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE) - save_json_file(file_path=SPEC_MULTI_LANG_FILE, data=sort_data) - _LOGGER.info('%s formatted.', SPEC_MULTI_LANG_FILE) sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE) - save_json_file(file_path=SPEC_FILTER_FILE, data=sort_data) + save_yaml_file(file_path=SPEC_FILTER_FILE, data=sort_data) _LOGGER.info('%s formatted.', SPEC_FILTER_FILE)