49 Commits

Author SHA1 Message Date
67785f747a doc: update changelog and version (#290)
* doc: update changelog and version

* doc: update changelog
2024-12-20 21:20:49 +08:00
bd3a98b976 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
2024-12-20 19:21:43 +08:00
6ce3206b30 fix: HASS core version requirements (#214)
* Update README.md

* Update hacs.json

* Update README_zh.md
2024-12-20 17:35:21 +08:00
aacb794e1f 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
2024-12-20 17:34:34 +08:00
a879ae2cdf 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
2024-12-20 17:34:03 +08:00
640ac25d9c chore: remove default label (#276) 2024-12-20 15:03:34 +08:00
5f5b3feea5 Fix: Replace inconsistent logging.error with _LOGGER.error (#262) 2024-12-20 13:56:51 +08:00
571483b302 chore: Improve multi-language translation actions (#256)
* test: check bool trans integrity

* feat: add tools to update and format data

* style: sort data

* feat: update check_rule_format.py

* style: remove unuse SUPPORTED_PLATFORMS item

* test: check spec rule sort

* fix: fix py import

* feat: remove unuse code

* feat: add spec data sort
2024-12-20 09:15:07 +08:00
b955c199fc Merge pull request #237 from chemwolf6922/fix-dutch-translation-declaration
Fix Dutch translation declaration
2024-12-19 17:42:15 +08:00
f10885fbfd Fix dutch translation declaration 2024-12-19 17:22:31 +08:00
bba8ba7f7b Merge pull request #168 from wheresrofl/patch-1
feat: dutch translation
2024-12-19 14:07:54 +08:00
1f20416e97 Merge pull request #220 from topsworld/test_lang_integrity
test: add lang integrity check
2024-12-19 14:04:16 +08:00
2d6387c30a test: check_rule_format.py use constant 2024-12-19 13:53:53 +08:00
b93d8631b8 fix: translations structure error 2024-12-19 13:31:17 +08:00
dabf277942 test: check lang_integrity 2024-12-19 13:30:26 +08:00
c4a981a15d Merge pull request #212 from chemwolf6922/patch-1
Simplify the install logic
2024-12-19 12:12:08 +08:00
3c464a0b0c Simplify the install logic. 2024-12-19 10:58:41 +08:00
b4be1e0aa9 Merge pull request #204 from FlyingFeng2021/main
Fix incorrect links
2024-12-19 10:56:31 +08:00
8364a544f2 Fix incorrect links 2024-12-19 10:03:03 +08:00
d980b1bfb4 feat: more dutch translations
accidentally missed this file
2024-12-18 18:04:23 +01:00
2fb030c52d Merge branch 'XiaoMi:main' into patch-1 2024-12-18 17:01:15 +00:00
54e26378be Merge pull request #179 from yejinze/main
fix: directory copy issue
2024-12-18 22:28:27 +08:00
6fae26a378 fix: directory copy issue 2024-12-18 22:01:10 +08:00
d437963628 Merge pull request #174 from SusanPhevos/doc-add-i18n
docs: add i18n
2024-12-18 21:37:23 +08:00
be627afe54 docs: add i18n in README 2024-12-18 21:34:04 +08:00
fa382939a0 Merge branch 'XiaoMi:main' into main 2024-12-18 21:26:03 +08:00
caec202c71 Merge pull request #170 from topsworld/main
feat: const.INTEGRATION_LANGUAGES add pt, pt-BR
2024-12-18 21:24:25 +08:00
1e0ddc1f0c feat: const.INTEGRATION_LANGUAGES add pt, pt-BR 2024-12-18 21:20:51 +08:00
01f6bbf2c7 feat: nederland translation 2024-12-18 13:06:00 +00:00
e0cd8486eb Merge pull request #167 from XiaoMi/doc_update_version
merge: update document
2024-12-18 21:02:39 +08:00
7618f6b366 doc: update change log 2024-12-18 20:46:32 +08:00
fce67ba2da doc: move CONTRIBUTING.md, CHANGELOG.md to root path 2024-12-18 20:45:07 +08:00
2c37fb63ca doc: update change log and version 2024-12-18 20:39:48 +08:00
fec5d10f42 add pt and pt-BR language support in i18n 2024-12-18 20:16:14 +08:00
10aa78f490 Merge pull request #135 from XiaoMi/feat_heater_devices
feat: support xiaomi heater devices
2024-12-18 19:00:33 +08:00
6e2de896c3 feat: update xiaomi heater ctrl logic 2024-12-18 17:42:40 +08:00
ce4f23b1bd style: fix the trailing space problem reported by pylint 2024-12-18 15:56:45 +08:00
e70acd8421 style: update comments to meet coding standards 2024-12-18 15:56:45 +08:00
b498a708ac style: ignore pylint warning 2024-12-18 15:56:45 +08:00
c744919032 fix: handle UnitOfConductivity import #54
Move unit imports inside function 和 add fallback for older versions. Resolves
2024-12-18 15:56:45 +08:00
6bb4bf32d7 fix: handle UnitOfConductivity import
Move unit imports inside function and add fallback for older versions.

Resolves #123
2024-12-18 15:56:45 +08:00
83dbceac6d Create pt-BR.json 2024-12-18 15:29:43 +08:00
7a50778d2b Create pt.json 2024-12-18 15:29:28 +08:00
b5f9e931b7 feat: support xiaomi heater devices 2024-12-18 10:36:31 +08:00
99e654f0c7 style: remove invalid space 2024-12-18 10:35:29 +08:00
b7fc534a34 fix: bool value false 2024-12-17 17:57:02 +08:00
da58d4c0a5 perf: remove permanent true statement 2024-12-17 17:50:08 +08:00
dbf943386e fix: set prop 2024-12-17 17:33:28 +08:00
bff5b3bf44 fix: air-conditioner switch on 2024-12-17 16:31:08 +08:00
41 changed files with 3031 additions and 394 deletions

View File

@ -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 / 其他说明

36
CHANGELOG.md Normal file
View File

@ -0,0 +1,36 @@
# 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
- Language supports pt, pt-BR.
### Changed
- Adjust the minimum version of HASS core to 2024.4.4 and above versions.
### Fixed
## v0.1.1
### Added
### Changed
### Fixed
- Fix humidifier trans rule. https://github.com/XiaoMi/ha_xiaomi_home/issues/59
- Fix get homeinfo error. https://github.com/XiaoMi/ha_xiaomi_home/issues/22
- Fix air-conditioner switch on. https://github.com/XiaoMi/ha_xiaomi_home/issues/37 https://github.com/XiaoMi/ha_xiaomi_home/issues/16
- Fix invalid cover status. https://github.com/XiaoMi/ha_xiaomi_home/issues/11 https://github.com/XiaoMi/ha_xiaomi_home/issues/85
- Water heater entity add STATE_OFF. https://github.com/XiaoMi/ha_xiaomi_home/issues/105 https://github.com/XiaoMi/ha_xiaomi_home/issues/17
## v0.1.0
### Added
- First version
### Changed
### Fixed

View File

@ -1,6 +1,6 @@
# Contribution Guidelines
[English](./CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
[English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
Thank you for considering contributing to our project! We appreciate your efforts to make our project better.

View File

@ -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
@ -323,7 +323,7 @@ Device information service (urn:miot-spec-v2:service:device-information:00007801
## Multiple Language Support
There are 8 languages available for selection in the config flow language option of Xiaomi Home, including Simplified Chinese, Traditional Chinese, English, Spanish, Russian, French, German, and Japanese. The config flow page in Simplified Chinese and English has been manually reviewed by the developer. Other languages are translated by machine translation. If you want to modify the words and sentences in the config flow page, you need to modify the json file of the certain language in `custom_components/xiaomi_home/translations/` directory.
There are 8 languages available for selection in the config flow language option of Xiaomi Home, including Simplified Chinese, Traditional Chinese, English, Spanish, Russian, French, German, and Japanese. The config flow page in Simplified Chinese and English has been manually reviewed by the developer. Other languages are translated by machine translation. If you want to modify the words and sentences in the config flow page, you need to modify the json file of the certain language in `custom_components/xiaomi_home/translations/` and `custom_components/xiaomi_home/miot/i18n/` directory.
When displaying Home Assistant entity name, Xiaomi Home downloads the multiple language file configured by the device vendor from MIoT Cloud, which contains translations for MIoT-Spec-V2 instances of the device. `multi_lang.json` is a locally maintained multiple language dictionary, which has a higher priority than the multiple language file obtained from the cloud and can be used to supplement or modify the multiple language translation of devices.
@ -376,8 +376,8 @@ Example:
## Documents
- [License](./LICENSE.md)
- Contribution Guidelines: [English](./doc/CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
- [ChangeLog](./doc/CHANGELOG.md)
- Contribution Guidelines: [English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
- [ChangeLog](./CHANGELOG.md)
- Development Documents: https://developers.home-assistant.io/docs/creating_component_index
## Directory Structure

View File

@ -82,9 +82,12 @@ async def async_setup_entry(
new_entities = []
for miot_device in device_list:
for data in miot_device.entity_list.get('climate', []):
for data in miot_device.entity_list.get('air-conditioner', []):
new_entities.append(
AirConditioner(miot_device=miot_device, entity_data=data))
for data in miot_device.entity_list.get('heater', []):
new_entities.append(
Heater(miot_device=miot_device, entity_data=data))
if new_entities:
async_add_entities(new_entities)
@ -115,7 +118,7 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
"""Initialize the Climate."""
"""Initialize the Air conditioner."""
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_icon = 'mdi:air-conditioner'
self._attr_supported_features = ClimateEntityFeature(0)
@ -344,31 +347,31 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
f'set climate prop.fan_mode failed, {fan_mode}, '
f'{self.entity_id}')
@ property
@property
def target_temperature(self) -> Optional[float]:
"""Return the target temperature."""
return self.get_prop_value(
prop=self._prop_target_temp) if self._prop_target_temp else None
@ property
@property
def target_humidity(self) -> Optional[int]:
"""Return the target humidity."""
return self.get_prop_value(
prop=self._prop_target_humi) if self._prop_target_humi else None
@ property
@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return self.get_prop_value(
prop=self._prop_env_temp) if self._prop_env_temp else None
@ property
@property
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return self.get_prop_value(
prop=self._prop_env_humi) if self._prop_env_humi else None
@ property
@property
def hvac_mode(self) -> Optional[HVACMode]:
"""Return the hvac mode. e.g., heat, cool mode."""
if self.get_prop_value(prop=self._prop_on) is False:
@ -377,7 +380,7 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
map_=self._hvac_mode_map,
key=self.get_prop_value(prop=self._prop_mode))
@ property
@property
def fan_mode(self) -> Optional[str]:
"""Return the fan mode.
@ -387,7 +390,7 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
map_=self._fan_mode_map,
key=self.get_prop_value(prop=self._prop_fan_level))
@ property
@property
def swing_mode(self) -> Optional[str]:
"""Return the swing mode.
@ -473,3 +476,144 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
self._value_ac_state.update(v_ac_state)
_LOGGER.debug(
'ac_state update, %s', self._value_ac_state)
class Heater(MIoTServiceEntity, ClimateEntity):
"""Heater entities for Xiaomi Home."""
# service: heater
_prop_on: Optional[MIoTSpecProperty]
_prop_mode: Optional[MIoTSpecProperty]
_prop_target_temp: Optional[MIoTSpecProperty]
_prop_heat_level: Optional[MIoTSpecProperty]
# service: environment
_prop_env_temp: Optional[MIoTSpecProperty]
_prop_env_humi: Optional[MIoTSpecProperty]
_heat_level_map: Optional[dict[int, str]]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
"""Initialize the Heater."""
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_icon = 'mdi:air-conditioner'
self._attr_supported_features = ClimateEntityFeature(0)
self._attr_preset_modes = []
self._prop_on = None
self._prop_mode = None
self._prop_target_temp = None
self._prop_heat_level = None
self._prop_env_temp = None
self._prop_env_humi = None
self._heat_level_map = None
# properties
for prop in entity_data.props:
if prop.name == 'on':
self._attr_supported_features |= (
ClimateEntityFeature.TURN_ON)
self._attr_supported_features |= (
ClimateEntityFeature.TURN_OFF)
self._prop_on = prop
elif prop.name == 'target-temperature':
if not isinstance(prop.value_range, dict):
_LOGGER.error(
'invalid target-temperature value_range format, %s',
self.entity_id)
continue
self._attr_min_temp = prop.value_range['min']
self._attr_max_temp = prop.value_range['max']
self._attr_target_temperature_step = prop.value_range['step']
self._attr_temperature_unit = prop.external_unit
self._attr_supported_features |= (
ClimateEntityFeature.TARGET_TEMPERATURE)
self._prop_target_temp = prop
elif prop.name == 'heat-level':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'invalid heat-level value_list, %s', self.entity_id)
continue
self._heat_level_map = {
item['value']: item['description']
for item in prop.value_list}
self._attr_preset_modes = list(self._heat_level_map.values())
self._attr_supported_features |= (
ClimateEntityFeature.PRESET_MODE)
self._prop_heat_level = prop
elif prop.name == 'temperature':
self._prop_env_temp = prop
elif prop.name == 'relative-humidity':
self._prop_env_humi = prop
# hvac modes
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
async def async_turn_on(self) -> None:
"""Turn the entity on."""
await self.set_property_async(prop=self._prop_on, value=True)
async def async_turn_off(self) -> None:
"""Turn the entity off."""
await self.set_property_async(prop=self._prop_on, value=False)
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
await self.set_property_async(
prop=self._prop_on, value=False
if hvac_mode == HVACMode.OFF else True)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
if ATTR_TEMPERATURE in kwargs:
temp = kwargs[ATTR_TEMPERATURE]
if temp > self.max_temp:
temp = self.max_temp
elif temp < self.min_temp:
temp = self.min_temp
await self.set_property_async(
prop=self._prop_target_temp, value=temp)
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode."""
await self.set_property_async(
self._prop_heat_level,
value=self.get_map_value(
map_=self._heat_level_map, description=preset_mode))
@property
def target_temperature(self) -> Optional[float]:
"""Return the target temperature."""
return self.get_prop_value(
prop=self._prop_target_temp) if self._prop_target_temp else None
@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return self.get_prop_value(
prop=self._prop_env_temp) if self._prop_env_temp else None
@property
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return self.get_prop_value(
prop=self._prop_env_humi) if self._prop_env_humi else None
@property
def hvac_mode(self) -> Optional[HVACMode]:
"""Return the hvac mode."""
return (
HVACMode.HEAT if self.get_prop_value(prop=self._prop_on)
else HVACMode.OFF)
@property
def preset_mode(self) -> Optional[str]:
return (
self.get_map_description(
map_=self._heat_level_map,
key=self.get_prop_value(prop=self._prop_heat_level))
if self._prop_heat_level else None)

View File

@ -564,8 +564,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
last_step=False,
)
@ staticmethod
@ callback
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:

View File

@ -236,7 +236,7 @@ class Light(MIoTServiceEntity, LightEntity):
"""Return the color temperature."""
return self.get_prop_value(prop=self._prop_color_temp)
@ property
@property
def rgb_color(self) -> Optional[tuple[int, int, int]]:
"""Return the rgb color value."""
rgb = self.get_prop_value(prop=self._prop_color)
@ -247,7 +247,7 @@ class Light(MIoTServiceEntity, LightEntity):
b = rgb & 0xFF
return r, g, b
@ property
@property
def effect(self) -> Optional[str]:
"""Return the current mode."""
return self.__get_mode_description(

View File

@ -23,10 +23,11 @@
"paho-mqtt<=2.0.0",
"numpy",
"cryptography",
"psutil"
"psutil",
"aiohttp[speedups]"
],
"version": "v0.1.0",
"version": "v0.1.3",
"zeroconf": [
"_miot-central._tcp.local."
]
}
}

View File

@ -46,10 +46,19 @@ off Xiaomi or its affiliates' products.
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]:

View File

@ -67,24 +67,16 @@ SPEC_STD_LIB_EFFECTIVE_TIME = 3600*24*14
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,14 +99,17 @@ SUPPORT_CENTRAL_GATEWAY_CTRL: list = ['cn']
DEFAULT_INTEGRATION_LANGUAGE: str = 'en'
INTEGRATION_LANGUAGES = {
'zh-Hans': '简体中文',
'zh-Hant': '繁體中文',
'de': 'Deutsch',
'en': 'English',
'es': 'Español',
'ru': 'Русский',
'fr': 'Français',
'de': 'Deutsch',
'ja': '日本語'
'ja': '日本語',
'nl': 'Nederlands',
'pt': 'Português',
'pt-BR': 'Português (Brasil)',
'ru': 'Русский',
'zh-Hans': '简体中文',
'zh-Hant': '繁體中文'
}
DEFAULT_CTRL_MODE: str = 'auto'

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "Apparaten",
"found_central_gateway": ", Lokale centrale hub-gateway gevonden"
},
"control_mode": {
"auto": "Automatisch",
"cloud": "Cloud"
},
"room_name_rule": {
"none": "Niet synchroniseren",
"home_room": "Huisnaam en Kamernaam (Xiaomi Home Slaapkamer)",
"room": "Kamernaam (Slaapkamer)",
"home": "Huisnaam (Xiaomi Home)"
},
"option_status": {
"enable": "Inschakelen",
"disable": "Uitschakelen"
},
"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"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Authenticatie-informatie is ongeldig, cloudverbinding zal niet beschikbaar zijn. Ga naar de Xiaomi Home-integratiepagina en klik op 'Opties' om opnieuw te verifiëren.",
"invalid_device_cache": "Cache van apparaatgegevens is abnormaal. Ga naar de Xiaomi Home-integratiepagina, klik op 'Opties->Apparaatlijst bijwerken' en werk de lokale cache bij.",
"invalid_cert_info": "Ongeldig gebruikerscertificaat, lokale centrale verbinding zal niet beschikbaar zijn. Ga naar de Xiaomi Home-integratiepagina en klik op 'Opties' om opnieuw te verifiëren.",
"device_cloud_error": "Er is een uitzondering opgetreden bij het ophalen van apparaatgegevens uit de cloud. Controleer de lokale netwerkverbinding.",
"xiaomi_home_error_title": "Xiaomi Home-integratiefout",
"xiaomi_home_error": "Gedetecteerd **{nick_name}({uid}, {cloud_server})** fout, ga naar de optiespagina om opnieuw te configureren.\n\n**Foutmelding**: \n{message}",
"device_list_changed_title": "Wijzigingen in Xiaomi Home-apparaatlijst",
"device_list_changed": "Gedetecteerd **{nick_name}({uid}, {cloud_server})** apparaatgegevens zijn gewijzigd. Ga naar de integratie-optiespagina, klik op `Opties->Apparaatlijst bijwerken` en werk lokale apparaatgegevens bij.\n\nHuidige netwerkstatus: {network_status}\n{message}\n",
"device_list_add": "\n**{count} nieuwe apparaten:** \n{message}",
"device_list_del": "\n**{count} apparaten niet beschikbaar:** \n{message}",
"device_list_offline": "\n**{count} apparaten offline:** \n{message}",
"network_status_online": "Online",
"network_status_offline": "Offline",
"device_exec_error": "Uitvoeringsfout"
}
},
"error": {
"common": {
"-10000": "Onbekende fout",
"-10001": "Service niet beschikbaar",
"-10002": "Ongeldige parameter",
"-10003": "Onvoldoende middelen",
"-10004": "Interne fout",
"-10005": "Onvoldoende machtigingen",
"-10006": "Uitvoeringstijd verstreken",
"-10007": "Apparaat offline of bestaat niet",
"-10020": "Niet geautoriseerd (OAuth2)",
"-10030": "Ongeldig token (HTTP)",
"-10040": "Ongeldig berichtformaat",
"-10050": "Ongeldig certificaat",
"-704000000": "Onbekende fout",
"-704010000": "Niet geautoriseerd (apparaat kan zijn verwijderd)",
"-704014006": "Apparaatbeschrijving niet gevonden",
"-704030013": "Eigenschap niet leesbaar",
"-704030023": "Eigenschap niet schrijfbaar",
"-704030033": "Eigenschap niet abonneerbaar",
"-704040002": "Service bestaat niet",
"-704040003": "Eigenschap bestaat niet",
"-704040004": "Gebeurtenis bestaat niet",
"-704040005": "Actie bestaat niet",
"-704040999": "Functie niet online",
"-704042001": "Apparaat bestaat niet",
"-704042011": "Apparaat offline",
"-704053036": "Apparaatbedieningstijd verstreken",
"-704053100": "Apparaat kan deze handeling niet uitvoeren in de huidige staat",
"-704083036": "Apparaatbedieningstijd verstreken",
"-704090001": "Apparaat bestaat niet",
"-704220008": "Ongeldige ID",
"-704220025": "Aantal actieparameters komt niet overeen",
"-704220035": "Fout in actieparameter",
"-704220043": "Fout in eigenschapswaarde",
"-704222034": "Fout in retourwaarde actie",
"-705004000": "Onbekende fout",
"-705004501": "Onbekende fout",
"-705201013": "Eigenschap niet leesbaar",
"-705201015": "Fout bij uitvoeren van actie",
"-705201023": "Eigenschap niet schrijfbaar",
"-705201033": "Eigenschap niet abonneerbaar",
"-706012000": "Onbekende fout",
"-706012013": "Eigenschap niet leesbaar",
"-706012015": "Fout bij uitvoeren van actie",
"-706012023": "Eigenschap niet schrijfbaar",
"-706012033": "Eigenschap niet abonneerbaar",
"-706012043": "Fout in eigenschapswaarde",
"-706014006": "Apparaatbeschrijving niet gevonden"
}
}
}

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "dispositivos",
"found_central_gateway": "encontrado o gateway central local"
},
"control_mode": {
"auto": "automático",
"cloud": "nuvem"
},
"room_name_rule": {
"none": "não sincronizado",
"home_room": "Nome da casa e nome do quarto (Xiaomi Home Quarto)",
"room": "Nome do quarto (Quarto)",
"home": "Nome da casa (Xiaomi Home)"
},
"option_status": {
"enable": "habilitado",
"disable": "desabilitado"
},
"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"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Informações de autenticação inválidas, a conexão com a nuvem estará indisponível. Vá para a página de integração do Xiaomi Home e clique em 'Opções' para reautenticar.",
"invalid_device_cache": "Informações de dispositivo no cache inválidas. Vá para a página de integração do Xiaomi Home e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.",
"invalid_cert_info": "Certificado de usuário inválido. A conexão local do gateway central estará indisponível. Vá para a página de integração do Xiaomi Home e clique em 'Opções' para reautenticar.",
"device_cloud_error": "Erro ao obter informações do dispositivo da nuvem. Verifique a conexão da rede local.",
"xiaomi_home_error_title": "Erro de Integração do Xiaomi Home",
"xiaomi_home_error": "Erro detectado em **{nick_name}({uid}, {cloud_server})**. Vá para a página de opções para reconfigurar.\n\n**Erro**: \n{message}",
"device_list_changed_title": "Mudança na lista de dispositivos do Xiaomi Home",
"device_list_changed": "Detectado que as informações do dispositivo **{nick_name}({uid}, {cloud_server})** mudaram. Vá para a página de integração e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.\n\nStatus atual da rede: {network_status}\n{message}\n",
"device_list_add": "\n**{count} dispositivos novos**: \n{message}",
"device_list_del": "\n**{count} dispositivos não disponíveis**: \n{message}",
"device_list_offline": "\n**{count} dispositivos offline**: \n{message}",
"network_status_online": "online",
"network_status_offline": "offline",
"device_exec_error": "Erro na execução"
}
},
"error": {
"common": {
"-10000": "Erro desconhecido",
"-10001": "Serviço indisponível",
"-10002": "Parâmetro inválido",
"-10003": "Recursos insuficientes",
"-10004": "Erro interno",
"-10005": "Permissões insuficientes",
"-10006": "Execução expirada",
"-10007": "Dispositivo offline ou inexistente",
"-10020": "OAuth2 não autorizado",
"-10030": "Token inválido (HTTP)",
"-10040": "Formato de mensagem inválido",
"-10050": "Certificado inválido",
"-704000000": "Erro desconhecido",
"-704010000": "Não autorizado (o dispositivo pode ter sido excluído)",
"-704014006": "Descrição do dispositivo não encontrada",
"-704030013": "Propriedade não pode ser lida",
"-704030023": "Propriedade não pode ser escrita",
"-704030033": "Propriedade não pode ser assinada",
"-704040002": "Serviço inexistente",
"-704040003": "Propriedade inexistente",
"-704040004": "Evento inexistente",
"-704040005": "Ação inexistente",
"-704040999": "Funcionalidade não lançada",
"-704042001": "Dispositivo inexistente",
"-704042011": "Dispositivo offline",
"-704053036": "Tempo de operação do dispositivo expirado",
"-704053100": "Dispositivo não pode executar esta operação no estado atual",
"-704083036": "Tempo de operação do dispositivo expirado",
"-704090001": "Dispositivo inexistente",
"-704220008": "ID inválido",
"-704220025": "Número de parâmetros de ação incompatível",
"-704220035": "Parâmetro de ação incorreto",
"-704220043": "Valor da propriedade incorreto",
"-704222034": "Valor de retorno de ação incorreto",
"-705004000": "Erro desconhecido",
"-705004501": "Erro desconhecido",
"-705201013": "Propriedade não pode ser lida",
"-705201015": "Erro na execução da ação",
"-705201023": "Propriedade não pode ser escrita",
"-705201033": "Propriedade não pode ser assinada",
"-706012000": "Erro desconhecido",
"-706012013": "Propriedade não pode ser lida",
"-706012015": "Erro na execução da ação",
"-706012023": "Propriedade não pode ser escrita",
"-706012033": "Propriedade não pode ser assinada",
"-706012043": "Valor da propriedade incorreto",
"-706014006": "Descrição do dispositivo não encontrada"
}
}
}

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "dispositivos",
"found_central_gateway": ", encontrou a central de gateway local"
},
"control_mode": {
"auto": "Automático",
"cloud": "Nuvem"
},
"room_name_rule": {
"none": "Não sincronizar",
"home_room": "Nome da casa e Nome do quarto (Xiaomi Home Quarto)",
"room": "Nome do quarto (Quarto)",
"home": "Nome da casa (Xiaomi Home)"
},
"option_status": {
"enable": "Habilitar",
"disable": "Desabilitar"
},
"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"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Informações de autenticação inválidas, a conexão na nuvem ficará indisponível. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções' para autenticar novamente.",
"invalid_device_cache": "Erro no cache de informações do dispositivo. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.",
"invalid_cert_info": "Certificado de usuário inválido, a conexão com a central local ficará indisponível. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções' para autenticar novamente.",
"device_cloud_error": "Erro ao obter informações do dispositivo na nuvem. Verifique a conexão de rede local.",
"xiaomi_home_error_title": "Erro de integração do Xiaomi Home",
"xiaomi_home_error": "Detectado erro em **{nick_name}({uid}, {cloud_server})**. Por favor, acesse a página de opções para reconfigurar.\n\n**Informação do erro**: \n{message}",
"device_list_changed_title": "Mudança na lista de dispositivos do Xiaomi Home",
"device_list_changed": "Detectada alteração nas informações do dispositivo de **{nick_name}({uid}, {cloud_server})**. Por favor, acesse a página de opções de integração e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.\n\nStatus atual da rede: {network_status}\n{message}\n",
"device_list_add": "\n**{count} novos dispositivos**: \n{message}",
"device_list_del": "\n**{count} dispositivos indisponíveis**: \n{message}",
"device_list_offline": "\n**{count} dispositivos offline**: \n{message}",
"network_status_online": "Online",
"network_status_offline": "Offline",
"device_exec_error": "Erro de execução"
}
},
"error": {
"common": {
"-10000": "Erro desconhecido",
"-10001": "Serviço indisponível",
"-10002": "Parâmetro inválido",
"-10003": "Recursos insuficientes",
"-10004": "Erro interno",
"-10005": "Permissão negada",
"-10006": "Tempo limite de execução",
"-10007": "Dispositivo offline ou inexistente",
"-10020": "Não autorizado (OAuth2)",
"-10030": "Token inválido (HTTP)",
"-10040": "Formato de mensagem inválido",
"-10050": "Certificado inválido",
"-704000000": "Erro desconhecido",
"-704010000": "Não autorizado (o dispositivo pode ter sido removido)",
"-704014006": "Descrição do dispositivo não encontrada",
"-704030013": "Propriedade não legível",
"-704030023": "Propriedade não gravável",
"-704030033": "Propriedade não subscritível",
"-704040002": "Serviço inexistente",
"-704040003": "Propriedade inexistente",
"-704040004": "Evento inexistente",
"-704040005": "Ação inexistente",
"-704040999": "Funcionalidade não disponível",
"-704042001": "Dispositivo inexistente",
"-704042011": "Dispositivo offline",
"-704053036": "Tempo limite de operação do dispositivo",
"-704053100": "O dispositivo não pode executar esta operação no estado atual",
"-704083036": "Tempo limite de operação do dispositivo",
"-704090001": "Dispositivo inexistente",
"-704220008": "ID inválido",
"-704220025": "Número de parâmetros da ação não corresponde",
"-704220035": "Erro nos parâmetros da ação",
"-704220043": "Valor de propriedade inválido",
"-704222034": "Erro no valor de retorno da ação",
"-705004000": "Erro desconhecido",
"-705004501": "Erro desconhecido",
"-705201013": "Propriedade não legível",
"-705201015": "Erro na execução da ação",
"-705201023": "Propriedade não gravável",
"-705201033": "Propriedade não subscritível",
"-706012000": "Erro desconhecido",
"-706012013": "Propriedade não legível",
"-706012015": "Erro na execução da ação",
"-706012023": "Propriedade não gravável",
"-706012033": "Propriedade não subscritível",
"-706012043": "Valor de propriedade inválido",
"-706014006": "Descrição do dispositivo não encontrada"
}
}
}

View File

@ -43,20 +43,18 @@
},
"error": {
"common": {
"-1": "未知錯誤",
"-10000": "未知錯誤",
"-10001": "服務不可用",
"-10002": "無效參數",
"-10002": "參數無效",
"-10003": "資源不足",
"-10004": "內部錯誤",
"-10005": "權限不足",
"-10006": "執行超時",
"-10007": "設備離線或者不存在",
"-10020": "無效的消息格式"
},
"gw": {},
"lan": {},
"cloud": {
"-10020": "未授權OAuth2",
"-10030": "無效的tokenHTTP",
"-10040": "無效的消息格式",
"-10050": "無效的證書",
"-704000000": "未知錯誤",
"-704010000": "未授權(設備可能被刪除)",
"-704014006": "沒找到設備描述",

File diff suppressed because it is too large Load Diff

View File

@ -1760,7 +1760,7 @@ class MIoTClient:
delay_sec, self.__show_devices_changed_notify)
@ staticmethod
@staticmethod
async def get_miot_instance_async(
hass: HomeAssistant, entry_id: str, entry_data: Optional[dict] = None,
persistent_notify: Optional[Callable[[str, str, str], None]] = None

View File

@ -51,10 +51,9 @@ import json
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 @@ TOKEN_EXPIRES_TS_RATIO = 0.7
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 @@ class MIoTOauthClient:
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 @@ class MIoTOauthClient:
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 @@ class MIoTOauthClient:
(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 @@ class MIoTOauthClient:
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 @@ class MIoTOauthClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
'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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
'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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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 @@ class MIoTHttpClient:
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))

View File

@ -54,8 +54,8 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS,
@ -72,7 +72,6 @@ from homeassistant.const import (
UnitOfPower,
UnitOfVolume,
UnitOfVolumeFlowRate,
UnitOfConductivity
)
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.components.switch import SwitchDeviceClass
@ -505,7 +504,8 @@ class MIoTDevice:
prop_access.add('read')
if prop.writable:
prop_access.add('write')
if prop_access != (SPEC_PROP_TRANS_MAP['entities'][platform]['access']):
if prop_access != (SPEC_PROP_TRANS_MAP[
'entities'][platform]['access']):
return None
if prop.format_ not in SPEC_PROP_TRANS_MAP[
'entities'][platform]['format']:
@ -584,7 +584,8 @@ class MIoTDevice:
self.append_action(action=action)
def unit_convert(self, spec_unit: str) -> Optional[str]:
return {
"""Convert MIoT unit to Home Assistant unit."""
unit_map = {
'percentage': PERCENTAGE,
'weeks': UnitOfTime.WEEKS,
'days': UnitOfTime.DAYS,
@ -616,11 +617,21 @@ class MIoTDevice:
'm': UnitOfLength.METERS,
'km': UnitOfLength.KILOMETERS,
'm3/h': UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
'μS/cm': UnitOfConductivity.MICROSIEMENS_PER_CM,
'gram': UnitOfMass.GRAMS,
'dB': SIGNAL_STRENGTH_DECIBELS,
'kB': UnitOfInformation.KILOBYTES,
}.get(spec_unit, None)
}
# Handle UnitOfConductivity separately since
# it might not be available in all HA versions
try:
# pylint: disable=import-outside-toplevel
from homeassistant.const import UnitOfConductivity
unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM
except ImportError:
unit_map['μS/cm'] = 'μS/cm'
return unit_map.get(spec_unit, None)
def icon_convert(self, spec_unit: str) -> Optional[str]:
if spec_unit in ['percentage']:
@ -1170,8 +1181,8 @@ class MIoTEventEntity(Entity):
handler=self.__on_device_state_changed)
# Sub value changed
self.miot_device.sub_event(
handler=self.__on_event_occurred, siid=self.service.iid,
eiid=self.spec.iid)
handler=self.__on_event_occurred,
siid=self.service.iid, eiid=self.spec.iid)
async def async_will_remove_from_hass(self) -> None:
self.miot_device.unsub_device_state(

View File

@ -71,7 +71,8 @@ from .miot_error import MIoTErrorCode
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__(
@ -564,11 +569,11 @@ class MIoTLan:
0, lambda: self._main_loop.create_task(
self.init_async()))
@ property
@property
def virtual_did(self) -> str:
return self._virtual_did
@ property
@property
def mev(self) -> MIoTEventLoop:
return self._mev
@ -597,6 +602,12 @@ class MIoTLan:
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 @@ class MIoTLan:
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 @@ class MIoTLan:
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(

View File

@ -129,7 +129,7 @@ class MIoTStorage:
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 @@ class MIoTStorage:
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 @@ class MIoTStorage:
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:

View File

@ -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": "閉じる"
"zh-Hans": {
"true": "开启",
"false": "关闭"
},
"zh-Hant": {
"true": "開啟",
"false": "關閉"
}
},
"yes_no": {
"zh-Hans": {
"true": "",
"false": ""
},
"zh-Hant": {
"true": "是",
"false": "否"
"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": "いいえ"
"zh-Hans": {
"true": "",
"false": ""
},
"zh-Hant": {
"true": "是",
"false": "否"
}
},
"motion_state": {
"zh-Hans": {
"true": "有人",
"false": "无人"
},
"zh-Hant": {
"true": "有人",
"false": "無人"
"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": "動きが検出されません"
"zh-Hans": {
"true": "有人",
"false": "无人"
},
"zh-Hant": {
"true": "有人",
"false": "無人"
}
},
"contact_state": {
"zh-Hans": {
"true": "接触",
"false": "分离"
},
"zh-Hant": {
"true": "接觸",
"false": "分離"
"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": "分離"
}
}
}

View File

@ -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",

View File

@ -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,10 +24,18 @@
"2.1"
]
},
"urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1": {
"urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1": {
"services": [
"5"
]
},
"urn:miot-spec-v2:device:light:0000A001:philips-strip3": {
"properties": [
"2.2"
],
"services": [
"1",
"5"
"3"
]
},
"urn:miot-spec-v2:device:light:0000A001:yeelink-color2": {
@ -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"
]
}
}

View File

@ -208,9 +208,32 @@ SPEC_DEVICE_TRANS_MAP: dict[str, dict | str] = {
}
}
},
'entity': 'climate'
'entity': 'air-conditioner'
},
'air-condition-outlet': 'air-conditioner'
'air-condition-outlet': 'air-conditioner',
'heater': {
'required': {
'heater': {
'required': {
'properties': {
'on': {'read', 'write'}
}
},
'optional': {
'properties': {'target-temperature', 'heat-level'}
},
}
},
'optional': {
'environment': {
'required': {},
'optional': {
'properties': {'temperature', 'relative-humidity'}
}
},
},
'entity': 'heater'
}
}
"""SPEC_SERVICE_TRANS_MAP

View File

@ -119,7 +119,7 @@ class Notify(MIoTActionEntity, NotifyEntity):
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,

View File

@ -45,9 +45,7 @@
"get_cert_error": "ゲートウェイ証明書を取得できませんでした。",
"no_family_selected": "家庭が選択されていません。",
"no_devices": "選択された家庭にデバイスがありません。デバイスがある家庭を選択して続行してください。",
"no_central_device": "【中央ゲートウェイモード】Home Assistant が存在する LAN 内に使用可能な Xiaomi 中央ゲートウェイがある必要があります。選択された家庭がこの要件を満たしているかどうかを確認してください。",
"update_config_error": "設定情報の更新に失敗しました。",
"not_confirm": "変更項目が確認されていません。確認を選択してから送信してください。"
"no_central_device": "【中央ゲートウェイモード】Home Assistant が存在する LAN 内に使用可能な Xiaomi 中央ゲートウェイがある必要があります。選択された家庭がこの要件を満たしているかどうかを確認してください。"
},
"abort": {
"network_connect_error": "設定に失敗しました。ネットワーク接続に異常があります。デバイスのネットワーク設定を確認してください。",

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "Xiaomi Home Integratie",
"step": {
"eula": {
"title": "Risiconotitie",
"description": "1. Uw Xiaomi-gebruikersinformatie en apparaatinformatie worden opgeslagen in het Home Assistant-systeem. **Xiaomi kan de beveiliging van het opslagmechanisme van Home Assistant niet garanderen**. U bent verantwoordelijk voor het voorkomen dat uw informatie wordt gestolen.\r\n2. Deze integratie wordt onderhouden door de open-sourcegemeenschap. Er kunnen stabiliteitsproblemen of andere problemen optreden. Bij problemen of fouten met deze integratie, **moet u hulp zoeken bij de open-sourcegemeenschap in plaats van contact op te nemen met de Xiaomi klantenservice**.\r\n3. U heeft enige technische vaardigheden nodig om uw lokale werkomgeving te onderhouden. De integratie is niet gebruiksvriendelijk voor beginners.\r\n4. Lees het README-bestand voordat u begint.\n\n5. Om een stabiel gebruik van de integratie te waarborgen en misbruik van de interface te voorkomen, **mag deze integratie alleen worden gebruikt in Home Assistant. Voor details, zie de LICENSE**.",
"data": {
"eula": "Ik ben me bewust van de bovenstaande risico's en bereid om vrijwillig alle risico's die gepaard gaan met het gebruik van de integratie te aanvaarden."
}
},
"auth_config": {
"title": "Basisconfiguratie",
"description": "### Inlogregio\r\nSelecteer de regio van uw Xiaomi-account. U kunt deze vinden in de Xiaomi Home APP > Profiel (onderin het menu) > Extra instellingen > Over Xiaomi Home.\r\n### Taal\r\nKies de taal voor de apparaats- en entiteitsnamen. Sommige zinnen zonder vertaling worden in het Engels weergegeven.\r\n### OAuth2 Omleidings-URL\r\nHet OAuth2 authenticatie omleidingsadres is **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. Home Assistant moet zich in hetzelfde lokale netwerk bevinden als de huidige werkterminal (bijv. de persoonlijke computer) en de werkterminal moet toegang hebben tot de startpagina van Home Assistant via dit adres. Anders kan de inlogauthenticatie mislukken.\r\n### Opmerking\r\n- Voor gebruikers met honderden of meer Mi Home-apparaten kan het aanvankelijke toevoegen van de integratie enige tijd duren. Wees geduldig.\r\n- Als Home Assistant draait in een Docker-omgeving, zorg er dan voor dat de Docker-netwerkmodus is ingesteld op host, anders werkt de lokale controlefunctionaliteit mogelijk niet correct.\r\n- De lokale controlefunctionaliteit van de integratie heeft enkele afhankelijkheden. Lees het README zorgvuldig.",
"data": {
"cloud_server": "Inlogregio",
"integration_language": "Taal",
"oauth_redirect_url": "OAuth2 Omleidings-URL"
}
},
"oauth_error": {
"title": "Inlogfout",
"description": "Klik OP VOLGENDE om het opnieuw te proberen."
},
"devices_filter": {
"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&emsp;\r\n### Hallo {nick_name}, selecteer alstublieft de integratie controlemethodiek en het huis waar het apparaat dat u wilt importeren zich bevindt.",
"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"
}
}
},
"progress": {
"oauth": "### {link_left}Klik hier om in te loggen{link_right}\r\n(U wordt automatisch doorgestuurd naar de volgende pagina na succesvolle inlog)"
},
"error": {
"eula_not_agree": "Lees de risiconotitie.",
"get_token_error": "Mislukt bij het ophalen van inlogautorisatie-informatie (OAuth-token).",
"get_homeinfo_error": "Mislukt bij het ophalen van huisinformatie.",
"mdns_discovery_error": "Lokaal apparaatsontdekkingsservice-exceptie.",
"get_cert_error": "Mislukt bij het ophalen van het certificaat van de centrale hubgateway.",
"no_family_selected": "Geen huis geselecteerd.",
"no_devices": "Het geselecteerde huis heeft geen apparaten. Kies a.u.b. een huis met apparaten en ga verder.",
"no_central_device": "[Centrale Hub Gateway Modus] vereist een beschikbare Xiaomi centrale hubgateway in het lokale netwerk waar Home Assistant zich bevindt. Controleer of het geselecteerde huis aan deze vereiste voldoet."
},
"abort": {
"network_connect_error": "Configuratie mislukt. De netwerkverbinding is abnormaal. Controleer de netwerkinstellingen van de apparatuur.",
"already_configured": "Configuratie voor deze gebruiker is al voltooid. Ga naar de integratiepagina en klik op de CONFIGUREER-knop om wijzigingen aan te brengen.",
"invalid_auth_info": "Authenticatie-informatie is verlopen. Ga naar de integratiepagina en klik op de CONFIGUREER-knop om opnieuw te authentiseren.",
"config_flow_error": "Integratie configuratiefout: {error}."
}
},
"options": {
"step": {
"auth_config": {
"title": "Authenticatieconfiguratie",
"description": "Lokale authenticatie-informatie is verlopen. Begin alstublieft het authenticatieproces opnieuw.\r\n### Huidige inlogregio: {cloud_server}\r\n### OAuth2 Omleidings-URL\r\nHet OAuth2 authenticatie omleidingsadres is **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. Home Assistant moet zich in hetzelfde lokale netwerk bevinden als de huidige werkterminal (bijv. de persoonlijke computer) en de werkterminal moet toegang hebben tot de startpagina van Home Assistant via dit adres. Anders kan de inlogauthenticatie mislukken.",
"data": {
"oauth_redirect_url": "OAuth2 Omleidings-URL"
}
},
"oauth_error": {
"title": "Er is een fout opgetreden tijdens het inloggen.",
"description": "Klik OP VOLGENDE om opnieuw te proberen."
},
"config_options": {
"title": "Configuratie-opties",
"description": "### Hallo, {nick_name}\r\n\r\nXiaomi ID: {uid}\r\nHuidige inlogregio: {cloud_server}\r\n\r\nKies de opties die u wilt configureren en klik vervolgens op VOLGENDE.",
"data": {
"integration_language": "Integratietaal",
"update_user_info": "Werk gebruikersinformatie bij",
"update_devices": "Werk apparatenlijst bij",
"action_debug": "Debugmodus voor actie",
"hide_non_standard_entities": "Verberg niet-standaard gemaakte entiteiten",
"update_trans_rules": "Werk entiteitsconversieregels bij",
"update_lan_ctrl_config": "Werk LAN controleconfiguratie bij"
}
},
"update_user_info": {
"title": "Bijwerken van gebruikersnickname",
"description": "Hallo {nick_name}, u kunt uw aangepaste bijnaam hieronder wijzigen.",
"data": {
"nick_name": "Bijnaam"
}
},
"devices_filter": {
"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&emsp;\r\n### Hallo {nick_name}, selecteer alstublieft de integratie controlemethodiek en het huis waar het apparaat dat u wilt importeren zich bevindt.",
"data": {
"ctrl_mode": "Controlemodus",
"home_infos": "Importeer apparaten uit huis"
}
},
"update_trans_rules": {
"title": "Bijwerken van entiteiten transformateregels",
"description": "## Gebruiksinstructies\r\n- Werk de entiteitsinformatie van apparaten in de huidige integratie-instantie bij, inclusief MIoT-Spec-V2 meertalige configuratie, booleanvertaling en modelfiltering.\r\n- **Waarschuwing**: Dit is een globale configuratie en zal de lokale cache bijwerken. Dit zal alle integratie-instanties beïnvloeden.\r\n- Deze handeling duurt enige tijd, wees geduldig. Vink \"Bevestig bijwerken\" aan en klik op \"Volgende\" om **{urn_count}** regels bij te werken, anders overslaan.",
"data": {
"confirm": "Bevestig de update"
}
},
"update_lan_ctrl_config": {
"title": "Update LAN controleconfiguratie",
"description": "## Gebruiksinstructies\r\nWerk de configuraties voor de Xiaomi LAN controlefunctie bij. Wanneer de cloud en de centrale hubgateway de apparaten niet kunnen bedienen, zal de integratie proberen de apparaten via het LAN te bedienen. Als er geen netwerkkaart is geselecteerd, zal de LAN controlefunctie niet werken.\r\n- Alleen MIoT-Spec-V2 compatibele IP-apparaten in het LAN worden ondersteund. Sommige apparaten die vóór 2020 zijn geproduceerd, ondersteunen mogelijk geen LAN controle of LAN abonnement.\r\n- Selecteer de netwerkkaart(en) op hetzelfde netwerk als de te bedienen apparaten. Meerdere netwerkkaarten kunnen worden geselecteerd. Als Home Assistant vanwege de meervoudige selectie van de netwerkkaarten twee of meer verbindingen heeft met het lokale netwerk, wordt aanbevolen om de verbinding met de beste netwerkverbinding te selecteren, anders kan dit een negatief effect hebben op de apparaten.\r\n- Als er terminalapparaten (Xiaomi-luidsprekers met scherm, mobiele telefoons, enz.) in het LAN zijn die lokale controle ondersteunen, kan het inschakelen van LAN-abonnement leiden tot lokale automatisering- en apparaatanomalieën.\r\n- **Waarschuwing**: Dit is een globale configuratie. Het zal alle integratie-instanties beïnvloeden. Gebruik het met voorzichtigheid.\r\n{notice_net_dup}",
"data": {
"net_interfaces": "Selecteer alstublieft de te gebruiken netwerkkaart",
"enable_subscribe": "Zet LAN-abonnement aan"
}
},
"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",
"data": {
"confirm": "Bevestig de wijziging"
}
}
},
"progress": {
"oauth": "### {link_left}Klik hier om opnieuw in te loggen{link_right}"
},
"error": {
"not_auth": "Niet geauthenticeerd. Klik op de authenticatielink om de gebruikersidentiteit te verifiëren.",
"get_token_error": "Mislukt bij het ophalen van inlogautorisatie-informatie (OAuth-token).",
"get_homeinfo_error": "Mislukt bij het ophalen van huisinformatie.",
"get_cert_error": "Mislukt bij het ophalen van het certificaat van de centrale hubgateway.",
"no_devices": "Het geselecteerde huis heeft geen apparaten. Kies a.u.b. een huis met apparaten en ga verder.",
"no_family_selected": "Geen huis geselecteerd.",
"no_central_device": "[Centrale Hub Gateway Modus] vereist een beschikbare Xiaomi centrale hubgateway in het lokale netwerk waar Home Assistant zich bevindt. Controleer of het geselecteerde huis aan deze vereiste voldoet.",
"mdns_discovery_error": "Lokaal apparaatsontdekkingsservice-exceptie.",
"update_config_error": "Mislukt bij het bijwerken van configuratie-informatie.",
"not_confirm": "Wijzigingen zijn niet bevestigd. Bevestig de wijziging voordat u deze indient."
},
"abort": {
"network_connect_error": "Configuratie mislukt. De netwerkverbinding is abnormaal. Controleer de netwerkinstellingen van de apparatuur.",
"options_flow_error": "Integratie herconfiguratiefout: {error}",
"re_add": "Voeg de integratie opnieuw toe. Foutmelding: {error}",
"storage_error": "Integratie opslagmodule-exceptie. Probeer het opnieuw of voeg de integratie opnieuw toe: {error}",
"inconsistent_account": "Accountinformatie is inconsistent. Log in met het juiste account."
}
}
}

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "Integração Xiaomi Home",
"step": {
"eula": {
"title": "Aviso de risco",
"description": "1. Suas informações de usuário Xiaomi e informações dos dispositivos serão armazenadas no sistema Home Assistant. **A Xiaomi não pode garantir a segurança do mecanismo de armazenamento do Home Assistant**. Você é responsável por evitar que suas informações sejam roubadas.\r\n2. Esta integração é mantida pela comunidade open-source. Podem haver problemas de estabilidade ou outros problemas. Ao encontrar falhas ou erros nesta integração, **você deve buscar ajuda da comunidade open-source em vez de contatar o suporte da Xiaomi**.\r\n3. Você precisará de certa habilidade técnica para manter seu ambiente operacional local. Esta integração não é amigável para iniciantes.\r\n4. Por favor, leia o arquivo README antes de começar.\n\n5. Para garantir o uso estável da integração e evitar abusos da interface, **esta integração só é permitida para uso no Home Assistant. Para mais detalhes, consulte a LICENSE**.",
"data": {
"eula": "Estou ciente dos riscos acima e disposto(a) a assumi-los voluntariamente ao utilizar a integração."
}
},
"auth_config": {
"title": "Configuração básica",
"description": "### Região de Login\r\nSelecione a região da sua conta Xiaomi. Você pode encontrá-la no aplicativo Xiaomi Home > Perfil (localizado no menu inferior) > Configurações adicionais > Sobre o Xiaomi Home.\r\n### Idioma\r\nSelecione o idioma dos nomes dos dispositivos e entidades. Algumas frases sem tradução serão exibidas em inglês.\r\n### URL de Redirecionamento OAuth2\r\nO endereço de redirecionamento da autenticação OAuth2 é **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. O Home Assistant precisa estar na mesma rede local que o terminal atual (por exemplo, o computador pessoal) e o terminal precisa acessar a página inicial do Home Assistant através desse endereço. Caso contrário, a autenticação de login pode falhar.\r\n### Observações\r\n- Para usuários com centenas ou mais dispositivos Mi Home, a adição inicial da integração levará algum tempo. Seja paciente.\r\n- Se o Home Assistant estiver sendo executado em um ambiente Docker, certifique-se de que o modo de rede do Docker esteja definido como host, caso contrário a funcionalidade de controle local pode não funcionar corretamente.\r\n- A funcionalidade de controle local da integração tem algumas dependências. Por favor, leia o README atentamente.",
"data": {
"cloud_server": "Região de Login",
"integration_language": "Idioma",
"oauth_redirect_url": "URL de Redirecionamento OAuth2"
}
},
"oauth_error": {
"title": "Erro de Login",
"description": "Clique em AVANÇAR para tentar novamente."
},
"devices_filter": {
"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&emsp;\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": {
"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"
}
}
},
"progress": {
"oauth": "### {link_left}Clique aqui para fazer login{link_right}\r\n(Você será redirecionado automaticamente para a próxima página após um login bem-sucedido)"
},
"error": {
"eula_not_agree": "Por favor, leia o aviso de risco.",
"get_token_error": "Falha ao obter as informações de autorização de login (token OAuth).",
"get_homeinfo_error": "Falha ao obter as informações da casa.",
"mdns_discovery_error": "Exceção no serviço de descoberta de dispositivos locais.",
"get_cert_error": "Falha ao obter o certificado do gateway central.",
"no_family_selected": "Nenhuma casa selecionada.",
"no_devices": "A casa selecionada não possui nenhum dispositivo. Por favor, escolha uma casa que contenha dispositivos e continue.",
"no_central_device": "[Modo Gateway Central] requer um gateway central Xiaomi disponível na rede local onde o Home Assistant está. Verifique se a casa selecionada atende a esse requisito."
},
"abort": {
"network_connect_error": "Configuração falhou. A conexão de rede está anormal. Verifique a configuração de rede do equipamento.",
"already_configured": "A configuração para este usuário já foi concluída. Vá para a página de integrações e clique no botão CONFIGURAR para modificações.",
"invalid_auth_info": "As informações de autenticação expiraram. Vá para a página de integrações e clique em CONFIGURAR para reautenticar.",
"config_flow_error": "Erro na configuração da integração: {error}."
}
},
"options": {
"step": {
"auth_config": {
"title": "Configuração de Autenticação",
"description": "As informações de autenticação local expiraram. Por favor, reinicie o processo de autenticação.\r\n### Região de Login Atual: {cloud_server}\r\n### URL de Redirecionamento OAuth2\r\nO endereço de redirecionamento da autenticação OAuth2 é **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. O Home Assistant precisa estar na mesma rede local que o terminal atual (por exemplo, o computador pessoal) e o terminal precisa acessar a página inicial do Home Assistant através desse endereço. Caso contrário, a autenticação de login pode falhar.",
"data": {
"oauth_redirect_url": "URL de Redirecionamento OAuth2"
}
},
"oauth_error": {
"title": "Ocorreu um erro durante o login.",
"description": "Clique em AVANÇAR para tentar novamente."
},
"config_options": {
"title": "Opções de Configuração",
"description": "### Olá, {nick_name}\r\n\r\nID Xiaomi: {uid}\r\nRegião de Login Atual: {cloud_server}\r\n\r\nSelecione as opções que você deseja configurar e clique em AVANÇAR.",
"data": {
"integration_language": "Idioma da Integração",
"update_user_info": "Atualizar informações do usuário",
"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",
"update_trans_rules": "Atualizar regras de conversão de entidades",
"update_lan_ctrl_config": "Atualizar configuração de controle LAN"
}
},
"update_user_info": {
"title": "Atualizar Apelido do Usuário",
"description": "Olá {nick_name}, você pode modificar seu apelido personalizado abaixo.",
"data": {
"nick_name": "Apelido"
}
},
"devices_filter": {
"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&emsp;\r\n### Olá {nick_name}, selecione o modo de controle da integração e a casa de onde deseja importar dispositivos.",
"data": {
"ctrl_mode": "Modo de controle",
"home_infos": "Importar dispositivos da casa"
}
},
"update_trans_rules": {
"title": "Atualizar Regras de Transformação de Entidades",
"description": "## Instruções de Uso\r\n- Atualiza as informações das entidades dos dispositivos na instância atual da integração, incluindo configuração multilíngue MIoT-Spec-V2, tradução de booleanos e filtragem de modelos.\r\n- **Aviso**: Esta é uma configuração global e atualizará o cache local. Ela afetará todas as instâncias da integração.\r\n- Esta operação levará algum tempo, seja paciente. Marque \"Confirmar atualização\" e clique em \"Avançar\" para iniciar a atualização de **{urn_count}** regras, caso contrário, pule.\r\n",
"data": {
"confirm": "Confirmar a atualização"
}
},
"update_lan_ctrl_config": {
"title": "Atualizar configuração de controle LAN",
"description": "## Instruções de Uso\r\nAtualize as configurações para a função de controle LAN da Xiaomi. Quando a nuvem e o gateway central não puderem controlar os dispositivos, a integração tentará controlá-los através da LAN. Se nenhuma placa de rede for selecionada, o controle LAN não terá efeito.\r\n- Somente dispositivos compatíveis com MIoT-Spec-V2 conectados via IP na LAN são suportados. Alguns dispositivos produzidos antes de 2020 podem não suportar controle LAN ou assinatura LAN.\r\n- Selecione a(s) placa(s) de rede que estão na mesma rede que os dispositivos a serem controlados. É possível selecionar várias placas. Se o Home Assistant tiver duas ou mais conexões com a rede local devido a múltiplas placas, recomenda-se selecionar a que tiver melhor conexão de rede. Caso contrário, isso pode afetar o desempenho.\r\n- Se houver dispositivos terminais (alto-falantes Xiaomi com tela, celular, etc.) na LAN que suportem controle local, habilitar a assinatura LAN pode causar comportamentos anormais em automações e dispositivos locais.\r\n- **Aviso**: Esta é uma configuração global. Afetará todas as instâncias da integração. Use com cautela.\r\n{notice_net_dup}",
"data": {
"net_interfaces": "Selecione a placa de rede a ser usada",
"enable_subscribe": "Habilitar assinatura LAN"
}
},
"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",
"data": {
"confirm": "Confirmar a mudança"
}
}
},
"progress": {
"oauth": "### {link_left}Por favor, clique aqui para relogar{link_right}"
},
"error": {
"not_auth": "Não autenticado. Por favor, clique no link de autenticação para autenticar sua identidade.",
"get_token_error": "Falha ao obter as informações de autorização de login (token OAuth).",
"get_homeinfo_error": "Falha ao obter as informações da casa.",
"get_cert_error": "Falha ao obter o certificado do gateway central.",
"no_devices": "A casa selecionada não possui nenhum dispositivo. Por favor, escolha uma casa com dispositivos e continue.",
"no_family_selected": "Nenhuma casa selecionada.",
"no_central_device": "[Modo Gateway Central] requer um gateway central Xiaomi disponível na rede local onde o Home Assistant está. Verifique se a casa selecionada atende a esse requisito.",
"mdns_discovery_error": "Exceção no serviço de descoberta de dispositivos locais.",
"update_config_error": "Falha ao atualizar as informações de configuração.",
"not_confirm": "As alterações não foram confirmadas. Por favor, confirme a mudança antes de enviar."
},
"abort": {
"network_connect_error": "Configuração falhou. A conexão de rede está anormal. Verifique a configuração da rede do equipamento.",
"options_flow_error": "Erro na reconfiguração da integração: {error}",
"re_add": "Por favor, adicione novamente a integração. Mensagem de erro: {error}",
"storage_error": "Exceção no módulo de armazenamento da integração. Tente novamente ou readicione a integração: {error}",
"inconsistent_account": "As informações da conta são inconsistentes. Por favor, faça login com a conta correta."
}
}
}

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "Integração Xiaomi Home",
"step": {
"eula": {
"title": "Aviso de Risco",
"description": "1. As informações da sua conta Xiaomi e dos seus dispositivos serão armazenadas no sistema do Home Assistant. **A Xiaomi não pode garantir a segurança do mecanismo de armazenamento do Home Assistant**. É da sua responsabilidade impedir que a sua informação seja roubada.\r\n2. Esta integração é mantida pela comunidade open-source. Podem ocorrer problemas de estabilidade ou outros. Ao encontrar problemas ou falhas nesta integração, **deverá procurar ajuda junto da comunidade open-source, em vez de contactar o apoio ao cliente da Xiaomi**.\r\n3. Necessitará de algumas competências técnicas para manter o seu ambiente de operação local. Esta integração não é intuitiva para utilizadores iniciantes.\r\n4. Leia o ficheiro README antes de começar.\n\n5. Para garantir uma utilização estável da integração e prevenir uso indevido, **esta integração só pode ser utilizada no Home Assistant. Para mais detalhes, consulte a LICENSE**.",
"data": {
"eula": "Estou ciente dos riscos acima e disposto(a) a assumi-los voluntariamente ao utilizar a integração."
}
},
"auth_config": {
"title": "Configuração Básica",
"description": "### Região de Login\r\nSelecione a região da sua conta Xiaomi. Pode encontrá-la na aplicação Xiaomi Home > Perfil (menu inferior) > Configurações adicionais > Sobre o Xiaomi Home.\r\n### Idioma\r\nSelecione o idioma para os nomes de dispositivos e entidades. Algumas frases sem tradução serão apresentadas em inglês.\r\n### URL de Redirecionamento OAuth2\r\nO endereço de redirecionamento para a autenticação OAuth2 é **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. O Home Assistant deve estar na mesma rede local que o terminal atual (por exemplo, o computador pessoal) e esse terminal deve conseguir aceder à página inicial do Home Assistant através deste endereço. Caso contrário, a autenticação de login pode falhar.\r\n### Notas\r\n- Para utilizadores com centenas (ou mais) de dispositivos Mi Home, a adição inicial da integração demorará algum tempo. Seja paciente.\r\n- Se o Home Assistant estiver a ser executado num ambiente Docker, assegure-se de que o modo de rede do Docker está configurado como host; caso contrário, a funcionalidade de controlo local pode não funcionar corretamente.\r\n- A funcionalidade de controlo local da integração tem algumas dependências. Leia cuidadosamente o README.",
"data": {
"cloud_server": "Região de Login",
"integration_language": "Idioma",
"oauth_redirect_url": "URL de Redirecionamento OAuth2"
}
},
"oauth_error": {
"title": "Erro de Login",
"description": "Clique em SEGUINTE para tentar novamente."
},
"devices_filter": {
"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&emsp;\r\n### Olá {nick_name}, selecione o modo de controlo da integração e a casa na qual deseja importar os dispositivos.",
"data": {
"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"
}
}
},
"progress": {
"oauth": "### {link_left}Clique aqui para iniciar sessão{link_right}\r\n(Será automaticamente redirecionado após um login bem-sucedido)"
},
"error": {
"eula_not_agree": "Por favor, leia o aviso de risco.",
"get_token_error": "Não foi possível obter a informação de autorização de login (token OAuth).",
"get_homeinfo_error": "Não foi possível obter a informação da casa.",
"mdns_discovery_error": "Exceção no serviço de descoberta de dispositivos locais.",
"get_cert_error": "Não foi possível obter o certificado do gateway central.",
"no_family_selected": "Nenhuma casa selecionada.",
"no_devices": "A casa selecionada não possui quaisquer dispositivos. Por favor, selecione uma casa que contenha dispositivos e continue.",
"no_central_device": "O [Modo Gateway Central] requer um gateway central Xiaomi disponível na rede local onde o Home Assistant está. Verifique se a casa selecionada cumpre este requisito."
},
"abort": {
"network_connect_error": "A configuração falhou. A ligação de rede é anormal. Verifique a configuração de rede do equipamento.",
"already_configured": "A configuração para este utilizador já foi concluída. Vá à página de integrações e clique em CONFIGURAR para efetuar alterações.",
"invalid_auth_info": "A informação de autenticação expirou. Vá à página de integrações e clique em CONFIGURAR para reautenticar.",
"config_flow_error": "Erro na configuração da integração: {error}."
}
},
"options": {
"step": {
"auth_config": {
"title": "Configuração de Autenticação",
"description": "A informação de autenticação local expirou. Por favor, reinicie o processo de autenticação.\r\n### Região de Login Atual: {cloud_server}\r\n### URL de Redirecionamento OAuth2\r\nO endereço de redirecionamento para a autenticação OAuth2 é **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. O Home Assistant deve estar na mesma rede local que o terminal atual (por exemplo, o computador) e esse terminal deve conseguir aceder à página inicial do Home Assistant através deste endereço. Caso contrário, a autenticação poderá falhar.",
"data": {
"oauth_redirect_url": "URL de Redirecionamento OAuth2"
}
},
"oauth_error": {
"title": "Ocorreu um erro durante o login.",
"description": "Clique em SEGUINTE para tentar novamente."
},
"config_options": {
"title": "Opções de Configuração",
"description": "### Olá, {nick_name}\r\n\r\nID Xiaomi: {uid}\r\nRegião de Login Atual: {cloud_server}\r\n\r\nSelecione as opções que pretende configurar e depois clique em SEGUINTE.",
"data": {
"integration_language": "Idioma da Integração",
"update_user_info": "Atualizar informação do utilizador",
"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",
"update_trans_rules": "Atualizar regras de conversão de entidades",
"update_lan_ctrl_config": "Atualizar configuração de controlo LAN"
}
},
"update_user_info": {
"title": "Atualizar Alcunha do Utilizador",
"description": "Olá {nick_name}, pode modificar a sua alcunha personalizada abaixo.",
"data": {
"nick_name": "Alcunha"
}
},
"devices_filter": {
"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&emsp;\r\n### Olá {nick_name}, selecione o modo de controlo da integração e a casa da qual pretende importar dispositivos.",
"data": {
"ctrl_mode": "Modo de Controlo",
"home_infos": "Importar dispositivos da casa"
}
},
"update_trans_rules": {
"title": "Atualizar Regras de Transformação de Entidades",
"description": "## Instruções de Utilização\r\n- Atualiza a informação das entidades dos dispositivos na instância atual da integração, incluindo configuração multilingue MIoT-Spec-V2, tradução de booleanos e filtragem de modelos.\r\n- **Aviso**: Esta é uma configuração global e atualizará a cache local, afetando todas as instâncias da integração.\r\n- Esta operação levará algum tempo, seja paciente. Selecione \"Confirmar a atualização\" e clique em \"SEGUINTE\" para iniciar a atualização de **{urn_count}** regras, caso contrário, ignore esta etapa.\r\n",
"data": {
"confirm": "Confirmar a atualização"
}
},
"update_lan_ctrl_config": {
"title": "Atualizar Configuração de Controlo LAN",
"description": "## Instruções de Utilização\r\nAtualize as configurações para a funcionalidade de controlo LAN da Xiaomi. Quando a nuvem e o gateway central não puderem controlar os dispositivos, a integração tentará controlá-los através da LAN. Se não selecionar nenhuma interface de rede, o controlo LAN não terá efeito.\r\n- Apenas dispositivos compatíveis com MIoT-Spec-V2 ligados via IP na LAN são suportados. Alguns dispositivos produzidos antes de 2020 podem não suportar controlo LAN ou subscrição LAN.\r\n- Selecione a(s) placa(s) de rede que estejam na mesma rede que os dispositivos a controlar. Pode selecionar várias placas. Se o Home Assistant tiver duas ou mais ligações à rede local devido à seleção de várias placas, é recomendado selecionar a que tiver melhor ligação, caso contrário poderá afetar negativamente o desempenho dos dispositivos.\r\n- Se houver dispositivos terminais (colunas Xiaomi com ecrã, telemóveis, etc.) na LAN que suportem controlo local, a ativação da subscrição LAN pode causar anomalias em automações e dispositivos locais.\r\n- **Aviso**: Esta é uma configuração global, afetando todas as instâncias da integração. Utilize com cautela.\r\n{notice_net_dup}",
"data": {
"net_interfaces": "Selecione a(s) interface(s) de rede a utilizar",
"enable_subscribe": "Ativar subscrição LAN"
}
},
"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",
"data": {
"confirm": "Confirmar a alteração"
}
}
},
"progress": {
"oauth": "### {link_left}Por favor, clique aqui para voltar a iniciar sessão{link_right}"
},
"error": {
"not_auth": "Não autenticado. Por favor, clique no link de autenticação para confirmar a sua identidade.",
"get_token_error": "Não foi possível obter a informação de autorização de login (token OAuth).",
"get_homeinfo_error": "Não foi possível obter a informação da casa.",
"get_cert_error": "Não foi possível obter o certificado do gateway central.",
"no_devices": "A casa selecionada não possui quaisquer dispositivos. Por favor, selecione uma casa com dispositivos e continue.",
"no_family_selected": "Nenhuma casa selecionada.",
"no_central_device": "O [Modo Gateway Central] requer um gateway central Xiaomi disponível na rede local onde o Home Assistant está. Verifique se a casa selecionada cumpre este requisito.",
"mdns_discovery_error": "Exceção no serviço de descoberta de dispositivos locais.",
"update_config_error": "Não foi possível atualizar a informação de configuração.",
"not_confirm": "As alterações não foram confirmadas. Por favor, confirme a alteração antes de submeter."
},
"abort": {
"network_connect_error": "A configuração falhou. A ligação de rede é anormal. Verifique a configuração da rede do equipamento.",
"options_flow_error": "Erro na reconfiguração da integração: {error}",
"re_add": "Por favor, volte a adicionar a integração. Mensagem de erro: {error}",
"storage_error": "Exceção no módulo de armazenamento da integração. Tente novamente ou volte a adicionar a integração: {error}",
"inconsistent_account": "A informação da conta é inconsistente. Por favor, inicie sessão com a conta correta."
}
}
}

View File

@ -45,13 +45,13 @@
"get_cert_error": "Не удалось получить сертификат центрального шлюза.",
"no_family_selected": "Не выбрана домашняя сеть.",
"no_devices": "В выбранной домашней сети нет устройств. Пожалуйста, выберите домашнюю сеть с устройствами и продолжайте.",
"no_central_device": "Для режима центрального шлюза Xiaomi необходимо наличие доступного центрального шлюза Xiaomi в локальной сети Home Assistant. Проверьте, соответствует ли выбранная домашняя сеть этому требованию.",
"abort": {
"network_connect_error": "Ошибка настройки. Сетевое подключение недоступно. Проверьте настройки сети устройства.",
"already_configured": "Этот пользователь уже настроен. Перейдите на страницу интеграции и нажмите кнопку «Настроить», чтобы изменить настройки.",
"invalid_auth_info": "Информация об авторизации истекла. Перейдите на страницу интеграции и нажмите кнопку «Настроить», чтобы переавторизоваться.",
"config_flow_error": "Ошибка настройки интеграции: {error}"
}
"no_central_device": "Для режима центрального шлюза Xiaomi необходимо наличие доступного центрального шлюза Xiaomi в локальной сети Home Assistant. Проверьте, соответствует ли выбранная домашняя сеть этому требованию."
},
"abort": {
"network_connect_error": "Ошибка настройки. Сетевое подключение недоступно. Проверьте настройки сети устройства.",
"already_configured": "Этот пользователь уже настроен. Перейдите на страницу интеграции и нажмите кнопку «Настроить», чтобы изменить настройки.",
"invalid_auth_info": "Информация об авторизации истекла. Перейдите на страницу интеграции и нажмите кнопку «Настроить», чтобы переавторизоваться.",
"config_flow_error": "Ошибка настройки интеграции: {error}"
}
},
"options": {

View File

@ -1,7 +0,0 @@
# CHANGELOG
## 0.1.0
### Added
- first version
### Changed
### Fixed

View File

@ -1,6 +1,6 @@
# 贡献指南
[English](./CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
[English](../CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
感谢您考虑为我们的项目做出贡献!您的努力将使我们的项目变得更好。

View File

@ -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 下载
@ -325,7 +325,7 @@ event instance name 下的值表示转换后实体所用的 `_attr_device_class`
## 多语言支持
米家集成配置选项中可选择的集成使用的语言有简体中文、繁体中文、英文、西班牙语、俄语、法语、德语、日语这八种语言。目前,米家集成配置页面的简体中文和英文已经过人工校审,其他语言由机器翻译。如果您希望修改配置页面的词句,则需要修改 `custom_components/xiaomi_home/translations/` 目录下相应语言的 json 文件。
米家集成配置选项中可选择的集成使用的语言有简体中文、繁体中文、英文、西班牙语、俄语、法语、德语、日语这八种语言。目前,米家集成配置页面的简体中文和英文已经过人工校审,其他语言由机器翻译。如果您希望修改配置页面的词句,则需要修改 `custom_components/xiaomi_home/translations/` 以及 `custom_components/xiaomi_home/miot/i18n/` 目录下相应语言的 json 文件。
在显示 Home Assistant 实体名称时,米家集成会从小米云下载设备厂商为设备配置的多语言文件,该文件包含设备 MIoT-Spec-V2 实例的多语言翻译。 `multi_lang.json` 是本地维护的多语言配置字典,其优先级高于从云端获取的多语言文件,可用于补充或修改设备的多语言翻译。
@ -378,8 +378,8 @@ siid、piid、eiid、aiid、value 均为十进制三位整数。
## 文档
- [许可证](../LICENSE.md)
- 贡献指南: [English](./CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
- [更新日志](./CHANGELOG.md)
- 贡献指南: [English](../CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
- [更新日志](../CHANGELOG.md)
- 开发文档: https://developers.home-assistant.io/docs/creating_component_index
## 目录结构

View File

@ -1,5 +1,5 @@
{
"name": "Xiaomi Home",
"homeassistant": "2024.11.0",
"homeassistant": "2024.4.4",
"hacs": "1.34.0"
}

View File

@ -14,14 +14,21 @@ if [ ! -d "$config_path" ]; then
exit 1
fi
# Remove the old version.
rm -rf "$config_path/custom_components/xiaomi_home"
# Get the script path.
script_path=$(dirname "$0")
# Change to the script path.
cd "$script_path"
# Set source and target
component_name=xiaomi_home
source_path="$script_path/custom_components/$component_name"
target_root="$config_path/custom_components"
target_path="$target_root/$component_name"
# Remove the old version.
rm -rf "$target_path"
# Copy the new version.
cp -r custom_components/xiaomi_home/ "$config_path/custom_components/"
mkdir -p "$target_root"
cp -r "$source_path" "$target_path"
# Done.
echo "Xiaomi Home installation is completed. Please restart Home Assistant."

View File

@ -4,8 +4,22 @@ import json
from os import listdir, path
from typing import Optional
import pytest
import yaml
SOURCE_DIR: str = path.dirname(path.abspath(__file__))
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]:
@ -20,6 +34,23 @@ 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:
return yaml.safe_load(file)
except FileNotFoundError:
print(file_path, 'is not found.')
return None
except yaml.YAMLError:
print(file_path, 'is not a valid YAML file.')
return None
def dict_str_str(d: dict) -> bool:
"""restricted format: dict[str, str]"""
if not isinstance(d, dict):
@ -83,45 +114,180 @@ 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
def compare_dict_structure(dict1: dict, dict2: dict) -> bool:
if not isinstance(dict1, dict) or not isinstance(dict2, dict):
print('invalid type')
return False
if dict1.keys() != dict2.keys():
print('inconsistent key values, ', dict1.keys(), dict2.keys())
return False
for key in dict1:
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
if not compare_dict_structure(dict1[key], dict2[key]):
print('inconsistent key values, dict, ', key)
return False
elif isinstance(dict1[key], list) and isinstance(dict2[key], list):
if not all(
isinstance(i, type(j))
for i, j in zip(dict1[key], dict2[key])):
print('inconsistent key values, list, ', key)
return False
elif not isinstance(dict1[key], type(dict2[key])):
print('inconsistent key values, type, ', key)
return False
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_DIR,
'../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_DIR,
'../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_DIR,
'../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_DIR, '../custom_components/xiaomi_home/miot/i18n')
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():
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, f'load {file_path} failed'
assert dict_str_dict(data), f'{file_path} format error'
@pytest.mark.github
def test_miot_lang_integrity():
# pylint: disable=import-outside-toplevel
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(TRANS_RELATIVE_PATH))
assert len(translations_names) == len(integration_lang_list)
assert translations_names == set(integration_lang_list)
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(TRANS_RELATIVE_PATH, integration_lang_list[0]))
for name in list(integration_lang_list)[1:]:
compare_dict: dict = load_json_file(
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(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0]))
for name in list(integration_lang_list)[1:]:
compare_dict: dict = load_json_file(
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.')

View File

@ -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(

View File

@ -1,3 +1,4 @@
[pytest]
markers:
github: tests for github actions
github: tests for github actions
update: update or re-sort config file

View File

@ -8,8 +8,37 @@ from zeroconf.asyncio import AsyncZeroconf
# 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 = '<Your central hub gateway did>'
test_token = '<Your central hub gateway 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>']
# 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 test_lan_async():
test_did: {
'token': test_token,
'model': test_model
}
},
**test_devices
})
# Test sub device state

44
tools/common.py Normal file
View File

@ -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)

80
tools/update_lan_rule.py Normal file
View File

@ -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.')