diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index e4ac606b..28890108 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -1,11 +1,9 @@ name: Bug report / 报告问题 description: Create a report to help us improve. / 报告问题以帮助我们改进 -title: "[Bug]: " -labels: ["bug"] body: - type: input attributes: - label: Describe the bug / 描述问题 + label: Describe the Bug / 描述问题 description: | > A clear and concise description of what the bug is. > 清晰且简明地描述问题。 @@ -14,7 +12,7 @@ body: - type: textarea attributes: - label: To Reproduce / 复现步骤 + label: How to Reproduce / 复现步骤 description: | > If applicable, add screenshots to help explain your problem. You can attach images by clicking this area to highlight it and then dragging files in. Steps to reproduce the behavior: > 如有需要,可添加截图以帮助解释问题。点击此区域以高亮显示并拖动截图文件以上传。请详细描述复现步骤: @@ -28,7 +26,7 @@ body: - type: input attributes: - label: Expected behavior / 预期结果 + label: Expected Behavior / 预期结果 description: | > A clear and concise description of what you expected to happen. > 描述预期结果。 @@ -44,7 +42,7 @@ body: - type: input attributes: - label: Home Assistant Core version / Home Assistant Core 版本 + label: Home Assistant Core Version / Home Assistant Core 版本 description: | > [Settings > About](https://my.home-assistant.io/redirect/info) > [设置 > 关于 Home Assistant](https://my.home-assistant.io/redirect/info) @@ -54,7 +52,7 @@ body: - type: input attributes: - label: Home Assistant Operation System version / Home Assistant Operation System 版本 + label: Home Assistant Operation System Version / Home Assistant Operation System 版本 description: | > [Settings > About](https://my.home-assistant.io/redirect/info) > [设置 > 关于 Home Assistant](https://my.home-assistant.io/redirect/info) @@ -64,7 +62,7 @@ body: - type: input attributes: - label: Xiaomi Home integration version / 米家集成版本 + label: Xiaomi Home Integration Version / 米家集成版本 description: | > [Settings > Devices & services > Configured > `Xiaomi Home`](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > [设置 > 设备与服务 > 已配置 > `Xiaomi Home`](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) @@ -74,4 +72,4 @@ body: - type: textarea attributes: - label: Additional context / 其他说明 + label: Additional Context / 其他说明 diff --git a/custom_components/xiaomi_home/miot/const.py b/custom_components/xiaomi_home/miot/const.py index 1bb684a1..c586992e 100644 --- a/custom_components/xiaomi_home/miot/const.py +++ b/custom_components/xiaomi_home/miot/const.py @@ -67,24 +67,16 @@ MANUFACTURER_EFFECTIVE_TIME = 3600*24*14 SUPPORTED_PLATFORMS: list = [ - # 'alarm_control_panel', 'binary_sensor', 'button', 'climate', - # 'camera', - # 'conversation', 'cover', - # 'device_tracker', 'event', 'fan', 'humidifier', 'light', - # 'lock', - # 'media_player', 'notify', 'number', - # 'remote', - # 'scene', 'select', 'sensor', 'switch', @@ -107,17 +99,17 @@ DEFAULT_INTEGRATION_LANGUAGE: str = 'en' INTEGRATION_LANGUAGES = { - 'zh-Hans': '简体中文', - 'zh-Hant': '繁體中文', - 'en': 'English', 'de': 'Deutsch', + 'en': 'English', 'es': 'Español', 'fr': 'Français', 'ja': '日本語', + 'nl': 'Nederlands', 'pt': 'Português', 'pt-BR': 'Português (Brasil)', 'ru': 'Русский', - 'nl': 'Nederlands' + 'zh-Hans': '简体中文', + 'zh-Hant': '繁體中文' } DEFAULT_CTRL_MODE: str = 'auto' diff --git a/custom_components/xiaomi_home/miot/specs/bool_trans.json b/custom_components/xiaomi_home/miot/specs/bool_trans.json index 649310d7..0bf193f0 100644 --- a/custom_components/xiaomi_home/miot/specs/bool_trans.json +++ b/custom_components/xiaomi_home/miot/specs/bool_trans.json @@ -3,14 +3,20 @@ "urn:miot-spec-v2:property:air-cooler:000000EB": "open_close", "urn:miot-spec-v2:property:alarm:00000012": "open_close", "urn:miot-spec-v2:property:anion:00000025": "open_close", + "urn:miot-spec-v2:property:anti-fake:00000130": "yes_no", + "urn:miot-spec-v2:property:arrhythmia:000000B4": "yes_no", "urn:miot-spec-v2:property:auto-cleanup:00000124": "open_close", "urn:miot-spec-v2:property:auto-deodorization:00000125": "open_close", "urn:miot-spec-v2:property:auto-keep-warm:0000002B": "open_close", "urn:miot-spec-v2:property:automatic-feeding:000000F0": "open_close", "urn:miot-spec-v2:property:blow:000000CD": "open_close", + "urn:miot-spec-v2:property:card-insertion-state:00000106": "yes_no", + "urn:miot-spec-v2:property:contact-state:0000007C": "contact_state", + "urn:miot-spec-v2:property:current-physical-control-lock:00000099": "open_close", + "urn:miot-spec-v2:property:delay:0000014F": "yes_no", "urn:miot-spec-v2:property:deodorization:000000C6": "open_close", "urn:miot-spec-v2:property:dns-auto-mode:000000DC": "open_close", - "urn:miot-spec-v2:property:current-physical-control-lock:00000099": "open_close", + "urn:miot-spec-v2:property:driving-status:000000B9": "yes_no", "urn:miot-spec-v2:property:dryer:00000027": "open_close", "urn:miot-spec-v2:property:eco:00000024": "open_close", "urn:miot-spec-v2:property:glimmer-full-color:00000089": "open_close", @@ -20,17 +26,25 @@ "urn:miot-spec-v2:property:horizontal-swing:00000017": "open_close", "urn:miot-spec-v2:property:hot-water-recirculation:0000011C": "open_close", "urn:miot-spec-v2:property:image-distortion-correction:0000010F": "open_close", - "urn:miot-spec-v2:property:mute:00000040": "open_close", + "urn:miot-spec-v2:property:local-storage:0000011E": "yes_no", "urn:miot-spec-v2:property:motion-detection:00000056": "open_close", + "urn:miot-spec-v2:property:motion-state:0000007D": "motion_state", "urn:miot-spec-v2:property:motion-tracking:0000008A": "open_close", + "urn:miot-spec-v2:property:motor-reverse:00000072": "yes_no", + "urn:miot-spec-v2:property:mute:00000040": "open_close", "urn:miot-spec-v2:property:off-delay:00000053": "open_close", "urn:miot-spec-v2:property:on:00000006": "open_close", "urn:miot-spec-v2:property:physical-controls-locked:0000001D": "open_close", + "urn:miot-spec-v2:property:plasma:00000132": "yes_no", "urn:miot-spec-v2:property:preheat:00000103": "open_close", + "urn:miot-spec-v2:property:seating-state:000000B8": "yes_no", + "urn:miot-spec-v2:property:silent-execution:000000FB": "yes_no", "urn:miot-spec-v2:property:sleep-aid-mode:0000010B": "open_close", "urn:miot-spec-v2:property:sleep-mode:00000028": "open_close", + "urn:miot-spec-v2:property:snore-state:0000012A": "yes_no", "urn:miot-spec-v2:property:soft-wind:000000CF": "open_close", "urn:miot-spec-v2:property:speed-control:000000E8": "open_close", + "urn:miot-spec-v2:property:submersion-state:0000007E": "yes_no", "urn:miot-spec-v2:property:time-watermark:00000087": "open_close", "urn:miot-spec-v2:property:un-straight-blowing:00000100": "open_close", "urn:miot-spec-v2:property:uv:00000029": "open_close", @@ -43,41 +57,19 @@ "urn:miot-spec-v2:property:wdr-mode:00000088": "open_close", "urn:miot-spec-v2:property:wet:0000002A": "open_close", "urn:miot-spec-v2:property:wifi-band-combine:000000E0": "open_close", - "urn:miot-spec-v2:property:anti-fake:00000130": "yes_no", - "urn:miot-spec-v2:property:arrhythmia:000000B4": "yes_no", - "urn:miot-spec-v2:property:card-insertion-state:00000106": "yes_no", - "urn:miot-spec-v2:property:delay:0000014F": "yes_no", - "urn:miot-spec-v2:property:driving-status:000000B9": "yes_no", - "urn:miot-spec-v2:property:local-storage:0000011E": "yes_no", - "urn:miot-spec-v2:property:motor-reverse:00000072": "yes_no", - "urn:miot-spec-v2:property:plasma:00000132": "yes_no", - "urn:miot-spec-v2:property:seating-state:000000B8": "yes_no", - "urn:miot-spec-v2:property:silent-execution:000000FB": "yes_no", - "urn:miot-spec-v2:property:snore-state:0000012A": "yes_no", - "urn:miot-spec-v2:property:submersion-state:0000007E": "yes_no", "urn:miot-spec-v2:property:wifi-ssid-hidden:000000E3": "yes_no", - "urn:miot-spec-v2:property:wind-reverse:00000117": "yes_no", - "urn:miot-spec-v2:property:motion-state:0000007D": "motion_state", - "urn:miot-spec-v2:property:contact-state:0000007C": "contact_state" + "urn:miot-spec-v2:property:wind-reverse:00000117": "yes_no" }, "translate": { "default": { - "zh-Hans": { - "true": "真", - "false": "假" - }, - "zh-Hant": { - "true": "真", - "false": "假" + "de": { + "true": "Wahr", + "false": "Falsch" }, "en": { "true": "True", "false": "False" }, - "de": { - "true": "Wahr", - "false": "Falsch" - }, "es": { "true": "Verdadero", "false": "Falso" @@ -86,32 +78,44 @@ "true": "Vrai", "false": "Faux" }, + "ja": { + "true": "真", + "false": "偽" + }, + "nl": { + "true": "True", + "false": "False" + }, + "pt": { + "true": "True", + "false": "False" + }, + "pt-BR": { + "true": "True", + "false": "False" + }, "ru": { "true": "Истина", "false": "Ложь" }, - "ja": { + "zh-Hans": { "true": "真", - "false": "偽" + "false": "假" + }, + "zh-Hant": { + "true": "真", + "false": "假" } }, "open_close": { - "zh-Hans": { - "true": "开启", - "false": "关闭" - }, - "zh-Hant": { - "true": "開啟", - "false": "關閉" + "de": { + "true": "Öffnen", + "false": "Schließen" }, "en": { "true": "Open", "false": "Close" }, - "de": { - "true": "Öffnen", - "false": "Schließen" - }, "es": { "true": "Abierto", "false": "Cerrado" @@ -120,32 +124,44 @@ "true": "Ouvert", "false": "Fermer" }, + "ja": { + "true": "開く", + "false": "閉じる" + }, + "nl": { + "true": "Open", + "false": "Dicht" + }, + "pt": { + "true": "Aberto", + "false": "Fechado" + }, + "pt-BR": { + "true": "Aberto", + "false": "Fechado" + }, "ru": { "true": "Открыть", "false": "Закрыть" }, - "ja": { - "true": "開く", - "false": "閉じる" - } - }, - "yes_no": { "zh-Hans": { - "true": "是", - "false": "否" + "true": "开启", + "false": "关闭" }, "zh-Hant": { - "true": "是", - "false": "否" + "true": "開啟", + "false": "關閉" + } + }, + "yes_no": { + "de": { + "true": "Ja", + "false": "Nein" }, "en": { "true": "Yes", "false": "No" }, - "de": { - "true": "Ja", - "false": "Nein" - }, "es": { "true": "Sí", "false": "No" @@ -154,32 +170,44 @@ "true": "Oui", "false": "Non" }, + "ja": { + "true": "はい", + "false": "いいえ" + }, + "nl": { + "true": "Ja", + "false": "Nee" + }, + "pt": { + "true": "Sim", + "false": "Não" + }, + "pt-BR": { + "true": "Sim", + "false": "Não" + }, "ru": { "true": "Да", "false": "Нет" }, - "ja": { - "true": "はい", - "false": "いいえ" - } - }, - "motion_state": { "zh-Hans": { - "true": "有人", - "false": "无人" + "true": "是", + "false": "否" }, "zh-Hant": { - "true": "有人", - "false": "無人" + "true": "是", + "false": "否" + } + }, + "motion_state": { + "de": { + "true": "Bewegung erkannt", + "false": "Keine Bewegung erkannt" }, "en": { "true": "Motion Detected", "false": "No Motion Detected" }, - "de": { - "true": "Bewegung erkannt", - "false": "Keine Bewegung erkannt" - }, "es": { "true": "Movimiento detectado", "false": "No se detecta movimiento" @@ -188,32 +216,44 @@ "true": "Mouvement détecté", "false": "Aucun mouvement détecté" }, + "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": "Движение не обнаружено" }, - "ja": { - "true": "動きを検知", - "false": "動きが検出されません" - } - }, - "contact_state": { "zh-Hans": { - "true": "接触", - "false": "分离" + "true": "有人", + "false": "无人" }, "zh-Hant": { - "true": "接觸", - "false": "分離" + "true": "有人", + "false": "無人" + } + }, + "contact_state": { + "de": { + "true": "Kontakt", + "false": "Kein Kontakt" }, "en": { "true": "Contact", "false": "No Contact" }, - "de": { - "true": "Kontakt", - "false": "Kein Kontakt" - }, "es": { "true": "Contacto", "false": "Sin contacto" @@ -222,13 +262,33 @@ "true": "Contact", "false": "Pas de contact" }, + "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": "Нет контакта" }, - "ja": { + "zh-Hans": { "true": "接触", - "false": "非接触" + "false": "分离" + }, + "zh-Hant": { + "true": "接觸", + "false": "分離" } } } diff --git a/custom_components/xiaomi_home/miot/specs/multi_lang.json b/custom_components/xiaomi_home/miot/specs/multi_lang.json index ed1fd969..80cb8b84 100644 --- a/custom_components/xiaomi_home/miot/specs/multi_lang.json +++ b/custom_components/xiaomi_home/miot/specs/multi_lang.json @@ -1,5 +1,27 @@ { "urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": { + "de": { + "service:001": "Geräteinformationen", + "service:001:property:003": "Geräte-ID", + "service:001:property:005": "Seriennummer (SN)", + "service:002": "Gateway", + "service:002:event:001": "Netzwerk geändert", + "service:002:event:002": "Netzwerk geändert", + "service:002:property:001": "Zugriffsmethode", + "service:002:property:001:valuelist:000": "Kabelgebunden", + "service:002:property:001:valuelist:001": "5G Drahtlos", + "service:002:property:001:valuelist:002": "2.4G Drahtlos", + "service:002:property:002": "IP-Adresse", + "service:002:property:003": "WiFi-Netzwerkname", + "service:002:property:004": "Aktuelle Zeit", + "service:002:property:005": "DHCP-Server-MAC-Adresse", + "service:003": "Anzeigelampe", + "service:003:property:001": "Schalter", + "service:004": "Virtueller Dienst", + "service:004:action:001": "Virtuelles Ereignis erzeugen", + "service:004:event:001": "Virtuelles Ereignis aufgetreten", + "service:004:property:001": "Ereignisname" + }, "en": { "service:001": "Device Information", "service:001:property:003": "Device ID", @@ -66,50 +88,6 @@ "service:004:event:001": "Événement virtuel survenu", "service:004:property:001": "Nom de l'événement" }, - "ru": { - "service:001": "Информация об устройстве", - "service:001:property:003": "ID устройства", - "service:001:property:005": "Серийный номер (SN)", - "service:002": "Шлюз", - "service:002:event:001": "Сеть изменена", - "service:002:event:002": "Сеть изменена", - "service:002:property:001": "Метод доступа", - "service:002:property:001:valuelist:000": "Проводной", - "service:002:property:001:valuelist:001": "5G Беспроводной", - "service:002:property:001:valuelist:002": "2.4G Беспроводной", - "service:002:property:002": "IP Адрес", - "service:002:property:003": "Название WiFi сети", - "service:002:property:004": "Текущее время", - "service:002:property:005": "MAC адрес DHCP сервера", - "service:003": "Световой индикатор", - "service:003:property:001": "Переключатель", - "service:004": "Виртуальная служба", - "service:004:action:001": "Создать виртуальное событие", - "service:004:event:001": "Произошло виртуальное событие", - "service:004:property:001": "Название события" - }, - "de": { - "service:001": "Geräteinformationen", - "service:001:property:003": "Geräte-ID", - "service:001:property:005": "Seriennummer (SN)", - "service:002": "Gateway", - "service:002:event:001": "Netzwerk geändert", - "service:002:event:002": "Netzwerk geändert", - "service:002:property:001": "Zugriffsmethode", - "service:002:property:001:valuelist:000": "Kabelgebunden", - "service:002:property:001:valuelist:001": "5G Drahtlos", - "service:002:property:001:valuelist:002": "2.4G Drahtlos", - "service:002:property:002": "IP-Adresse", - "service:002:property:003": "WiFi-Netzwerkname", - "service:002:property:004": "Aktuelle Zeit", - "service:002:property:005": "DHCP-Server-MAC-Adresse", - "service:003": "Anzeigelampe", - "service:003:property:001": "Schalter", - "service:004": "Virtueller Dienst", - "service:004:action:001": "Virtuelles Ereignis erzeugen", - "service:004:event:001": "Virtuelles Ereignis aufgetreten", - "service:004:property:001": "Ereignisname" - }, "ja": { "service:001": "デバイス情報", "service:001:property:003": "デバイスID", @@ -132,6 +110,28 @@ "service:004:event:001": "バーチャルイベントが発生しました", "service:004:property:001": "イベント名" }, + "ru": { + "service:001": "Информация об устройстве", + "service:001:property:003": "ID устройства", + "service:001:property:005": "Серийный номер (SN)", + "service:002": "Шлюз", + "service:002:event:001": "Сеть изменена", + "service:002:event:002": "Сеть изменена", + "service:002:property:001": "Метод доступа", + "service:002:property:001:valuelist:000": "Проводной", + "service:002:property:001:valuelist:001": "5G Беспроводной", + "service:002:property:001:valuelist:002": "2.4G Беспроводной", + "service:002:property:002": "IP Адрес", + "service:002:property:003": "Название WiFi сети", + "service:002:property:004": "Текущее время", + "service:002:property:005": "MAC адрес DHCP сервера", + "service:003": "Световой индикатор", + "service:003:property:001": "Переключатель", + "service:004": "Виртуальная служба", + "service:004:action:001": "Создать виртуальное событие", + "service:004:event:001": "Произошло виртуальное событие", + "service:004:property:001": "Название события" + }, "zh-Hant": { "service:001": "設備信息", "service:001:property:003": "設備ID", diff --git a/custom_components/xiaomi_home/miot/specs/spec_filter.json b/custom_components/xiaomi_home/miot/specs/spec_filter.json index 0a72547e..5cea69f6 100644 --- a/custom_components/xiaomi_home/miot/specs/spec_filter.json +++ b/custom_components/xiaomi_home/miot/specs/spec_filter.json @@ -1,26 +1,22 @@ { - "urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1": { + "urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4": { + "properties": [ + "9.*", + "13.*", + "15.*" + ], "services": [ - "5" + "10" ] }, "urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01": { + "properties": [ + "5.1" + ], "services": [ "4", "7", "8" - ], - "properties": [ - "5.1" - ] - }, - "urn:miot-spec-v2:device:light:0000A001:philips-strip3": { - "services": [ - "1", - "3" - ], - "properties": [ - "2.2" ] }, "urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": { @@ -28,12 +24,20 @@ "2.1" ] }, - "urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1": { + "urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1": { "services": [ - "1", "5" ] }, + "urn:miot-spec-v2:device:light:0000A001:philips-strip3": { + "properties": [ + "2.2" + ], + "services": [ + "1", + "3" + ] + }, "urn:miot-spec-v2:device:light:0000A001:yeelink-color2": { "properties": [ "3.*", @@ -50,14 +54,10 @@ "3" ] }, - "urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4": { + "urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1": { "services": [ - "10" - ], - "properties": [ - "9.*", - "13.*", - "15.*" + "1", + "5" ] } } \ No newline at end of file diff --git a/custom_components/xiaomi_home/notify.py b/custom_components/xiaomi_home/notify.py index 42d0d175..f0b46ac9 100644 --- a/custom_components/xiaomi_home/notify.py +++ b/custom_components/xiaomi_home/notify.py @@ -119,7 +119,7 @@ async def async_send_message( in_value: list[dict] = [] for index, prop in enumerate(self.spec.in_): if type(in_list[index]).__name__ != prop.format_: - logging.error( + _LOGGER.error( 'action exec failed, %s(%s), invalid params item, ' 'which item(%s) in the list must be %s, %s', self.name, self.entity_id, prop.description_trans, diff --git a/test/check_rule_format.py b/test/check_rule_format.py index 48d602ef..3c20afae 100644 --- a/test/check_rule_format.py +++ b/test/check_rule_format.py @@ -6,9 +6,20 @@ import pytest import yaml -SOURCE_PATH: str = path.dirname(path.abspath(__file__)) -TRANS_RELATIVE_PATH: str = '../custom_components/xiaomi_home/translations' -MIOT_I18N_RELATIVE_PATH: str = '../custom_components/xiaomi_home/miot/i18n' +ROOT_PATH: str = path.dirname(path.abspath(__file__)) +TRANS_RELATIVE_PATH: str = path.join( + ROOT_PATH, '../custom_components/xiaomi_home/translations') +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') +SPEC_FILTER_FILE = path.join( + ROOT_PATH, + '../custom_components/xiaomi_home/miot/specs/spec_filter.json') def load_json_file(file_path: str) -> Optional[dict]: @@ -23,6 +34,11 @@ def load_json_file(file_path: str) -> Optional[dict]: return None +def save_json_file(file_path: str, data: dict) -> None: + with open(file_path, 'w', encoding='utf-8') as file: + json.dump(data, file, ensure_ascii=False, indent=4) + + def load_yaml_file(file_path: str) -> Optional[dict]: try: with open(file_path, 'r', encoding='utf-8') as file: @@ -98,6 +114,16 @@ def bool_trans(d: dict) -> bool: return False if not nested_3_dict_str_str(d['translate']): return False + default_trans: dict = d['translate'].pop('default') + if not default_trans: + print('default trans is empty') + return False + default_keys: set[str] = set(default_trans.keys()) + for key, trans in d['translate'].items(): + trans_keys: set[str] = set(trans.keys()) + if set(trans.keys()) != default_keys: + print('bool trans inconsistent', key, default_keys, trans_keys) + return False return True @@ -125,54 +151,69 @@ def compare_dict_structure(dict1: dict, dict2: dict) -> bool: return True +def sort_bool_trans(file_path: str): + trans_data: dict = load_json_file(file_path=file_path) + 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 = dict(sorted(filter_data.items())) + for urn, spec in filter_data.items(): + filter_data[urn] = dict(sorted(spec.items())) + return filter_data + + @pytest.mark.github def test_bool_trans(): - data: dict = load_json_file( - path.join( - SOURCE_PATH, - '../custom_components/xiaomi_home/miot/specs/bool_trans.json')) - assert data - assert bool_trans(data) + data: dict = load_json_file(SPEC_BOOL_TRANS_FILE) + 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( - path.join( - SOURCE_PATH, - '../custom_components/xiaomi_home/miot/specs/spec_filter.json')) - assert data - assert spec_filter(data) + data: dict = load_json_file(SPEC_FILTER_FILE) + 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( - path.join( - SOURCE_PATH, - '../custom_components/xiaomi_home/miot/specs/multi_lang.json')) - assert data - assert nested_3_dict_str_str(data) + 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(): - i18n_path: str = path.join(SOURCE_PATH, MIOT_I18N_RELATIVE_PATH) - for file_name in listdir(i18n_path): - file_path: str = path.join(i18n_path, file_name) + 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) - assert data - assert nested_3_dict_str_str(data) + assert data, f'load {file_path} failed' + assert nested_3_dict_str_str(data), f'{file_path} format error' @pytest.mark.github def test_translations(): - i18n_path: str = path.join(SOURCE_PATH, TRANS_RELATIVE_PATH) - for file_name in listdir(i18n_path): - file_path: str = path.join(i18n_path, file_name) + 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) - assert data - assert dict_str_dict(data) + assert data, f'load {file_path} failed' + assert dict_str_dict(data), f'{file_path} format error' @pytest.mark.github @@ -181,30 +222,72 @@ def test_miot_lang_integrity(): from miot.const import INTEGRATION_LANGUAGES integration_lang_list: list[str] = [ f'{key}.json' for key in list(INTEGRATION_LANGUAGES.keys())] - translations_names: set[str] = set(listdir( - path.join(SOURCE_PATH, TRANS_RELATIVE_PATH))) + translations_names: set[str] = set(listdir(TRANS_RELATIVE_PATH)) assert len(translations_names) == len(integration_lang_list) assert translations_names == set(integration_lang_list) - i18n_names: set[str] = set(listdir( - path.join(SOURCE_PATH, MIOT_I18N_RELATIVE_PATH))) + 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_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( - path.join(SOURCE_PATH, TRANS_RELATIVE_PATH, integration_lang_list[0])) + path.join(TRANS_RELATIVE_PATH, integration_lang_list[0])) for name in list(integration_lang_list)[1:]: compare_dict: dict = load_json_file( - path.join(SOURCE_PATH, TRANS_RELATIVE_PATH, name)) + path.join(TRANS_RELATIVE_PATH, name)) if not compare_dict_structure(default_dict, compare_dict): print('compare_dict_structure failed /translations, ', name) assert False # Check i18n files structure default_dict = load_json_file( - path.join( - SOURCE_PATH, MIOT_I18N_RELATIVE_PATH, integration_lang_list[0])) + path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0])) for name in list(integration_lang_list)[1:]: compare_dict: dict = load_json_file( - path.join(SOURCE_PATH, MIOT_I18N_RELATIVE_PATH, name)) + path.join(MIOT_I18N_RELATIVE_PATH, name)) if not compare_dict_structure(default_dict, compare_dict): print('compare_dict_structure failed /miot/i18n, ', name) assert False + + +@pytest.mark.github +def test_miot_data_sort(): + # pylint: disable=import-outside-toplevel + from miot.const import INTEGRATION_LANGUAGES + sort_langs: dict = dict(sorted(INTEGRATION_LANGUAGES.items())) + assert list(INTEGRATION_LANGUAGES.keys()) == list(sort_langs.keys()), ( + '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( + 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( + sort_spec_filter(file_path=SPEC_FILTER_FILE)), ( + f'{SPEC_FILTER_FILE} not sorted, goto project root path' + ' and run the following command sorting, ', + 'pytest -s -v -m update ./test/check_rule_format.py') + + +@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) + print(SPEC_BOOL_TRANS_FILE, 'formatted.') + sort_data = sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE) + save_json_file(file_path=SPEC_MULTI_LANG_FILE, data=sort_data) + print(SPEC_MULTI_LANG_FILE, 'formatted.') + sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE) + save_json_file(file_path=SPEC_FILTER_FILE, data=sort_data) + print(SPEC_FILTER_FILE, 'formatted.') diff --git a/test/pytest.ini b/test/pytest.ini index 098fdc06..7f37f335 100644 --- a/test/pytest.ini +++ b/test/pytest.ini @@ -1,3 +1,4 @@ [pytest] markers: - github: tests for github actions \ No newline at end of file + github: tests for github actions + update: update or re-sort config file \ No newline at end of file