feat: first commit

This commit is contained in:
topsworld 2024-12-10 17:36:32 +08:00
commit 838fff399b
75 changed files with 19923 additions and 0 deletions

77
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@ -0,0 +1,77 @@
name: Bug report / 报告问题
description: Create a report to help us improve. / 报告问题以帮助我们改进
title: "[Bug]: "
labels: ["bug"]
body:
- type: input
attributes:
label: Describe the bug / 描述问题
description: |
> A clear and concise description of what the bug is.
> 清晰且简明地描述问题。
validations:
required: true
- type: textarea
attributes:
label: 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:
> 如有需要,可添加截图以帮助解释问题。点击此区域以高亮显示并拖动截图文件以上传。请详细描述复现步骤:
placeholder: |
1. Go to ...
2. Click on ...
3. Scroll down to ...
4. See error
validations:
required: true
- type: input
attributes:
label: Expected behavior / 预期结果
description: |
> A clear and concise description of what you expected to happen.
> 描述预期结果。
validations:
required: true
- type: textarea
attributes:
label: Home Assistant Logs / 系统日志
description: |
> [Settings > System > Logs > DOWNLOAD FULL LOG](https://my.home-assistant.io/redirect/logs) > Filter `xiaomi_home`
> [设置 > 系统 > 日志 > 下载完整日志](https://my.home-assistant.io/redirect/logs) > 筛选 `xiaomi_home`
- type: input
attributes:
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)
placeholder: "2024.8.1"
validations:
required: true
- type: input
attributes:
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)
placeholder: "12.4"
validations:
required: true
- type: input
attributes:
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)
placeholder: "v0.0.1"
validations:
required: true
- type: textarea
attributes:
label: Additional context / 其他说明

9
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,9 @@
blank_issues_enabled: false
contact_links:
- name: Feature Suggestion / 功能建议
url: https://github.com/XiaoMi/ha_xiaomi_home/discussions/new?category=ideas
about: Share ideas for enhancements or new features. / 建议改进或增加新功能
- name: Support and Help / 支持与帮助
url: https://github.com/XiaoMi/ha_xiaomi_home/discussions/categories/q-a
about: Please ask and answer questions here. / 请在这里提问和答疑

79
.github/workflows/validate.yaml vendored Normal file
View File

@ -0,0 +1,79 @@
name: Validate
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
validate-hassfest:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Hassfest validation
uses: home-assistant/actions/hassfest@master
validate-hacs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: HACS validation
uses: hacs/action@main
with:
category: integration
ignore: brands
validate-format:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Check format
run: |
./custom_components/xiaomi_home/test/test_all.sh
validate-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
- name: Analyse the code with pylint
run: |
pylint $(git ls-files '*.py')
validate-setup:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Install the integration
run: |
export config_path=./test_config
mkdir $config_path
./install.sh $config_path
echo "default_config:" >> $config_path/configuration.yaml
echo "logger:" >> $config_path/configuration.yaml
echo " default: info" >> $config_path/configuration.yaml
echo " logs:" >> $config_path/configuration.yaml
echo " custom_components.xiaomi_home: debug" >> $config_path/configuration.yaml
- name: Setup Home Assistant
id: homeassistant
uses: ludeeus/setup-homeassistant@main
with:
config-dir: ./test_config

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
__pycache__
.pytest_cache
.vscode
.idea
requirements.txt

398
.pylintrc Normal file
View File

@ -0,0 +1,398 @@
# This Pylint rcfile contains a best-effort configuration to uphold the
# best-practices and style described in the Google Python style guide:
# https://google.github.io/styleguide/pyguide.html
#
# Its canonical open-source location is:
# https://google.github.io/styleguide/pylintrc
[MAIN]
# Files or directories to be skipped. They should be base names, not paths.
ignore=third_party
# Files or directories matching the regex patterns are skipped. The regex
# matches against base names, not paths.
ignore-patterns=
# Pickle collected data for later comparisons.
persistent=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Use multiple processes to speed up Pylint.
jobs=4
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=R,
abstract-method,
apply-builtin,
arguments-differ,
attribute-defined-outside-init,
backtick,
bad-option-value,
basestring-builtin,
buffer-builtin,
c-extension-no-member,
consider-using-enumerate,
cmp-builtin,
cmp-method,
coerce-builtin,
coerce-method,
delslice-method,
div-method,
eq-without-hash,
execfile-builtin,
file-builtin,
filter-builtin-not-iterating,
fixme,
getslice-method,
global-statement,
hex-method,
idiv-method,
implicit-str-concat,
import-error,
import-self,
import-star-module-level,
input-builtin,
intern-builtin,
invalid-str-codec,
locally-disabled,
long-builtin,
long-suffix,
map-builtin-not-iterating,
misplaced-comparison-constant,
missing-function-docstring,
metaclass-assignment,
next-method-called,
next-method-defined,
no-absolute-import,
no-init, # added
no-member,
no-name-in-module,
no-self-use,
nonzero-method,
oct-method,
old-division,
old-ne-operator,
old-octal-literal,
old-raise-syntax,
parameter-unpacking,
print-statement,
raising-string,
range-builtin-not-iterating,
raw_input-builtin,
rdiv-method,
reduce-builtin,
relative-import,
reload-builtin,
round-builtin,
setslice-method,
signature-differs,
standarderror-builtin,
suppressed-message,
sys-max-int,
trailing-newlines,
unichr-builtin,
unicode-builtin,
unnecessary-pass,
unpacking-in-except,
useless-else-on-loop,
useless-suppression,
using-cmp-argument,
wrong-import-order,
xrange-builtin,
zip-builtin-not-iterating,
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names=main,_
# Bad variable names which should always be refused, separated by a comma
bad-names=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
# Regular expression matching correct function names
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
# Regular expression matching correct variable names
variable-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct constant names
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression matching correct attribute names
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
# Regular expression matching correct argument names
argument-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct class attribute names
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression matching correct inline iteration names
inlinevar-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct class names
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
# Regular expression matching correct module names
module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
# Regular expression matching correct method names
method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=12
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# TODO(https://github.com/pylint-dev/pylint/issues/3352): Direct pylint to exempt
# lines made too long by directives to pytype.
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=(?x)(
^\s*(\#\ )?<?https?://\S+>?$|
^\s*(from\s+\S+\s+)?import\s+.+$)
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=yes
# Maximum number of lines in a module
max-module-lines=99999
# String used as indentation unit. The internal Google style guide mandates 2
# spaces. Google's externaly-published style guide says 4, consistent with
# PEP 8. Here, we use 4 spaces.
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=TODO
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=yes
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging,absl.logging,tensorflow.io.logging
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,
TERMIOS,
Bastion,
rexec,
sets
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant, absl
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls,
class_
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs

33
LICENSE.md Normal file
View File

@ -0,0 +1,33 @@
# 许可证
版权声明 (C) 2024 小米公司。
在本许可证下提供的 Home Assistant 米家集成Xiaomi Home Integration和相关米家云服务 API 接口,包括源代码和目标代码(统称为“授权作品”)的所有权及知识产权归小米所有。小米在此授予您一项个人的、有限的、非排他的、不可转让的、不可转授权的、免费的权利,仅限于您为非商业性目的使用 Home Assistant 而复制、使用、修改、分发授权作品。为避免疑义本许可证未授权您将授权作品用于任何其他用途包括但不限于开发应用程序APP、Web 服务以及其他形式的软件等。
您在重新分发授权作品时,无论修改与否,无论以源码形式或目标代码形式,您均需保留本授权作品中的版权标识、免责声明及本许可证的副本。
授权作品是按“现状”分发的,小米不对授权作品承担任何明示或暗示的保证或担保,包括但不限于对授权作品没有错误或疏漏、持续性、可靠性、适用于某一特定用途或不侵权等的保证、声明或承诺。在任何情况下,对于因使用授权作品或无法使用授权作品而引起的任何直接、间接、特殊、偶然或后果性损害或损失,您需自行承担全部责任。
本许可证中未明确授予的所有权利均予保留,除本许可证明确授予您的权利外,小米未以任何形式授权您使用小米及小米关联公司的商标、著作权或其他任何形式的知识产权,例如在未获得小米另行书面许可的情况下,您不得使用“小米”、“米家”等与小米相关的字样或其他会使得公众联想到小米的字样对您使用授权作品的软件或搭载授权作品的硬件做任何形式的宣传或推广。
在下述情况下,小米有权立即终止对您依据本许可证获得的授权:
1. 您对小米或其关联公司的专利或其他知识产权提起专利无效、诉讼或其他主张;或,
2. 您生产、制造(含委托制造)、销售(含委托销售)模仿或复制小米产品(包含小米关联公司的产品)的山寨产品。
---
# License
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant Integration and related Xiaomi cloud service API interface provided under this license, including source code and object code (collectively, "Licensed Work"), are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi hereby grants you a personal, limited, non-exclusive, non-transferable, non-sublicensable, and royalty-free license to reproduce, use, modify, and distribute the Licensed Work only for your use of Home Assistant for non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize you to use the Licensed Work for any other purpose, including but not limited to use Licensed Work to develop applications (APP), Web services, and other forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without modifications, whether in source or object form, provided that you must give any other recipients of the Licensed Work a copy of this License and retain all copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible for any direct, indirect, special, incidental, or consequential damages or losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License. Except for the rights expressly granted by Xiaomi under this License, Xiaomi does not authorize you in any form to use the trademarks, copyrights, or other forms of intellectual property rights of Xiaomi and its affiliates, including, without limitation, without obtaining other written permission from Xiaomi, you shall not use "Xiaomi", "Xiaomi home", "Mijia" and other words related to Xiaomi or words that may make the public associate with Xiaomi in any form to publicize or promote the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this License in the event:
1. You assert patent invalidation, litigation, or other claims against patents or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock off Xiaomi or its affiliates' products.

13
LegalNotice.md Normal file
View File

@ -0,0 +1,13 @@
# 法律声明
版权声明 (C) 2024 小米。
Home Assistant 米家集成Xiaomi Home Integration所使用的米家云服务 API 接口(以下简称小米云接口)的所有权及其知识产权为小米所有。您仅限于在[米家集成许可证](./LICENSE.md)规定的范围内使用,任何超出前述许可证规定范围外的行为,包括但不限于在非 Home Assistant 平台上使用小米云接口、以及基于商业目的在 Home Assistant 平台上使用小米云接口等行为均应被视为侵权行为,小米有权对您使用的小米云接口采取包括但不限于停止使用、删除、屏蔽、断开连接等措施,同时保留向您追究相关法律责任的权利。
小米拥有本声明的最终解释权。
---
# Legal Notice
Copyright (C) 2024 Xiaomi Corporation.
All rights, title, interest and intellectual property rights of the Xiaomi Cloud Service API interface (hereinafter referred to as Xiaomi Cloud Interface) provided to use the Home Assistant Xiaomi Home Integration shall be solely owned by Xiaomi. You are only permitted to use the Xiaomi Cloud Interface within the scope specified in the [Xiaomi Home Integration License](./LICENSE.md). Any behavior beyond the scope of the aforesaid license, including but not limited to using the Xiaomi Cloud Interface on non-Home Assistant platforms and using the Xiaomi Cloud Interface on the Home Assistant platform for any commercial purposes, shall be deemed as infringement. Xiaomi has the right to take measures, including but not limited to stopping usage, deleting, blocking and disconnecting the Xiaomi Cloud Interface used by You, and also reserves the right to pursue relevant legal responsibilities against You.
Xiaomi reserves the right of the final interpretation of this notice.

394
README.md Normal file
View File

@ -0,0 +1,394 @@
# Xiaomi Home Integration for Home Assistant
[English](./README.md) | [简体中文](./doc/README_zh.md)
Xiaomi Home Integration is an integrated component of Home Assistant supported by Xiaomi official. It allows you to use Xiaomi IoT smart devices in Home Assistant.
## Installation
> Home Assistant version requirement:
>
> - Core $\geq$ 2024.12.1
> - Operating System $\geq$ 14.0
### Method 1: Git clone from GitHub
```bash
cd config
git clone https://github.com/XiaoMi/ha_xiaomi_home.git
cd ha_xiaomi_home
./install.sh /config
```
We recommend this installation method, for it is convenient to switch to a tag when updating `xiaomi_home` to a certain version.
For example, update to version v1.0.0
```bash
cd config/ha_xiaomi_home
git checkout v1.0.0
./install.sh /config
```
### Method 2: [HACS](https://hacs.xyz/)
HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category: Integration > ADD
> Xiaomi Home has not been added to the HACS store as a default yet. It's coming soon.
### Method 3: Manually installation via [Samba](https://github.com/home-assistant/addons/tree/master/samba) / [FTPS](https://github.com/hassio-addons/addon-ftp)
Download and copy `custom_components/xiaomi_home` folder to `config/custom_components` folder in your Home Assistant.
## Configuration
### Login
[Settings > Devices & services > ADD INTEGRATION](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > Search `Xiaomi Home` > NEXT > Click here to login > Sign in with Xiaomi account
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
### Add MIoT Devices
After logging in successfully, a dialog box named "Select Home and Devices" pops up. You can select the home containing the device that you want to import in Home Assistant.
### Multiple User Login
After a Xiaomi account login and its user configuration are completed, you can continue to add other Xiaomi accounts in the configured Xiaomi Home Integration page.
Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > ADD HUB > NEXT > Click here to login > Sign in with Xiaomi account
[![Open your Home Assistant instance and show an integration.](https://my.home-assistant.io/badges/integration.svg)](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
### Update Configurations
You can change the configurations in the "Configuration Options" dialog box, in which you can update your user nickname and the list of the devices importing from Xiaomi Home APP, etc.
Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Select the option to update
### Debug Mode for Action
You can manually send Action command message with parameters to the device when the debug mode for action is activated. The user interface for sending the Action command with parameters is shown as a Text entity.
Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Debug mode for action
## Security
Xiaomi Home Integration and the affiliated cloud interface is provided by Xiaomi officially. You need to use your Xiaomi account to login to get your device list. Xiaomi Home Integration implements OAuth 2.0 login process, which does not keep your account password in the Home Assistant application. However, due to the limitation of the Home Assistant platform, the user information (including device information, certificates, tokens, etc.) of your Xiaomi account will be saved in the Home Assistant configuration file in clear text after successful login. You need to ensure that your Home Assistant configuration file is properly stored. The exposure of your configuration file may result in others logging in with your identity.
## FAQ
- Does Xiaomi Home Integration support all Xiaomi Home devices?
Xiaomi Home Integration currently supports most categories of Home device. Only a few categories are not supported. They are Bluetooth device, infrared device and virtual device.
- Does Xiaomi Home Integration support multiple Xiaomi accounts?
Yes, it supports multiple Xiaomi accounts. Futhermore, Xiaomi Home Integration allows that devices belonging to different accounts can be added to a same area.
- Does Xiaomi Home Integration support local control?
Local control is implemented by [Xiaomi Central Hub Gateway](https://www.mi.com/shop/buy/detail?product_id=15755&cfrom=search) (firmware version 3.4.0_0000 above) or Xiaomi home devices with built-in central hub gateway (software version 0.8.0 above) inside. If you do not have a Xiaomi central hub gateway or other devices having central hub gateway function, all control commands are sent through Xiaomi Cloud. The firmware for Xiaomi central hub gateway including the built-in central hub gateway supporting Home Assistant local control feature has not been released yet. Please refer to MIoT team's notification for upgrade plans.
Xiaomi central hub gateway is only available in mainland China. In other regions, it is not available.
Xiaomi Home Integration can also implement partial local control by enabling Xiaomi LAN control function. Xiaomi LAN control function can only control IP devices (devices connected to the router via WiFi or ethernet cable) in the same local area network as Home Assistant. It cannot control BLE Mesh, ZigBee, etc. devices. This function may cause some abnormalities. We recommend not to use this function. Xiaomi LAN control function is enabled by [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Update LAN control configuration
Xiaomi LAN control function is not restricted by region. It is available in all regions. However, if there is a central gateway in the local area network where Home Assistant is located, even Xiaomi LAN control function is enabled in the integration, it will not take effect.
- In which regions is Xiaomi Home Integration available?
Xiaomi Home Integration can be used in the mainland of China, Europe, India, Russia, Singapore, and USA. As user data in Xiaomi Cloud of different regions is isolated, you need to choose your region when importing MIoT devices in the configuration process. Xiaomi Home Integration allows you to import devices of different regions to a same area.
## Principle of Messaging
### Control through the Cloud
<div align=center>
<img src="./doc/images/cloud_control.jpg" width=300>
Image 1: Cloud control architecture
</div>
Xiaomi Home Integration subscribes to the interested device messages on the MQTT Broker in MIoT Cloud. When a device property changes or a device event occurs, the device sends an upstream message to MIoT Cloud, and the MQTT Broker pushes the subscribed device message to Xiaomi Home Integration. Because Xiaomi Home Integration does not need to poll to obtain the current device property value in the cloud, it can immediately receive the notification message when the properties change or the events occur. Thanks to the message subscription mechanism, Xiaomi Home Integration only queries the properties of all devices from the cloud once when the integration configuration is completed, which puts little access pressure on the cloud.
Xiaomi Home Integration sends command messages to the devices via the HTTP interface of MIoT Cloud to control devices. The device reacts and responds after receiving the downstream message sent forward by MIoT Cloud.
### Control locally
<div align=center>
<img src="./doc/images/local_control.jpg" width=300>
Image 2: Local control architecture
</div>
Xiaomi central hub gateway contains a standard MQTT Broker, which implements a complete subscribe-publish mechanism. Xiaomi Home Integration subscribes to the interested device messages through Xiaomi central hub gateway. When a device property changes or a device event occurs, the device sends an upstream message to Xiaomi central hub gateway, and the MQTT Broker pushes the subscribed device message to Xiaomi Home Integration.
When Xiaomi Home Integration needs to control a device, it publishes a device command message to the MQTT Broker, which is then forwarded to the device by Xiaomi central hub gateway. The device reacts and responds after receiving the downstream message from the gateway.
## Mapping Relationship between MIoT-Spec-V2 and Home Assistant Entity
[MIoT-Spec-V2](https://iot.mi.com/v2/new/doc/introduction/knowledge/spec) is the abbreviation for MIoT Specification Version 2, which is an IoT protocol formulated by Xiaomi IoT platform to give a standard functional description of IoT devices. It includes function definition (referred to as data model by other IoT platforms), interaction model, message format, and encoding.
In MIoT-Spec-V2 protocol, a product is defined as a device. A device contains several services. A service may have some properties, events and actions. Xiaomi Home Integration creates Home Assistant entities according to MIoT-Spec-V2. The conversion relationship is as follows.
### General Conversion
- Property
| format | access | value-list | value-range | Entity in Home Assistant |
| ------------ | --------------------- | ------------ | ----------- | ------------------------ |
| writable | string | - | - | Text |
| writable | bool | - | - | Switch |
| writable | not string & not bool | existent | - | Select |
| writable | not string & not bool | non-existent | existent | Number |
| not writable | - | - | - | Sensor |
- Event
MIoT-Spec-V2 event is transformed to Event entity in Home Assistant. The event's parameters are also passed to entity's `_trigger_event`.
- Action
| in | Entity in Home Assistant |
| --------- | ------------------------ |
| empty | Button |
| not empty | Notify |
If the debug mode for action is activated, the Text entity will be created when the "in" field in the action spec is not empty.
The "Attribute" item in the entity details page displays the format of the input parameter which is an ordered list, enclosed in square brackets []. The string elements in the list are enclosed in double quotation marks "".
For example, the "Attributes" item in the details page of the Notify entity converted by the "Intelligent Speaker Execute Text Directive" action of xiaomi.wifispeaker.s12 siid=5, aiid=5 instance shows the action params as `[Text Content(str), Silent Execution(bool)]`. A properly formatted input is `["Hello", true]`.
### Specific Conversion
MIoT-Spec-V2 uses URN for defining types. The format is `urn:<namespace>:<type>:<name>:<value>[:<vendor-product>:<version>]`, in which `name` is a human-readable word or phrase describing the instance of device, service, property, event and action. Xiaomi Home Integration first determines whether to convert the MIoT-Spec-V2 instance into a specific Home Assistant entity based on the instance's name. For the instance that does not meet the specific conversion rules, general conversion rules are used for conversion.
`namespace` is the namespace of MIoT-Spec-V2 instance. When its value is miot-spec-v2, it means that the specification is defined by Xiaomi. When its value is bluetooth-spec, it means that the specification is defined by Bluetooth Special Interest Group (SIG). When its value is not miot-spec-v2 or bluetooth-spec, it means that the specification is defined by other vendors. If MIoT-Spec-V2 `namespace` is not miot-spec-v2, a star mark `*` is added in front of the entity's name .
- Device
The conversion follows `SPEC_DEVICE_TRANS_MAP`.
```
{
'<device instance name>':{
'required':{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
}
}
},
'optional':{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
}
}
},
'entity': str
}
}
```
The "required" field under "device instance name" indicates the required services of the device. The "optional" field under "device instance name" indicates the optional services of the device. The "entity" field indicates the Home Assistant entity to be created. The "required" and the "optional" field under "service instance name" are required and optional properties, events and actions of the service respectively. The value of "property instance name" under "required" "properties" field is the access mode of the property. The condition for a successful match is that the value of "property instance name" is a subset of the access mode of the corresponding MIoT-Spec-V2 property instance.
Home Assistant entity will not be created if MIoT-Spec-V2 device instance does not contain all required services, properties, events or actions.
- Service
The conversion follows `SPEC_SERVICE_TRANS_MAP`.
```
{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'entity': str
}
}
```
The "required" field under "service instance name" indicates the required properties, events and actions of the service. The "optional" field indicates the optional properties, events and actions of the service. The "entity" field indicates the Home Assistant entity to be created. The value of "property instance name" under "required" "properties" field is the access mode of the property. The condition for a successful match is that the value of "property instance name" is a subset of the access mode of the corresponding MIoT-Spec-V2 property instance.
Home Assistant entity will not be created if MIoT-Spec-V2 service instance does not contain all required properties, events or actions.
- Property
The conversion follows `SPEC_PROP_TRANS_MAP`.
```
{
'entities':{
'<entity name>':{
'format': set<str>,
'access': set<str>
}
},
'properties': {
'<property instance name>':{
'device_class': str,
'entity': str
}
}
}
```
The "format" field under "entity name" represents the data format of the property, and matching with one value indicates a successful match. The "access" field under "entity name" represents the access mode of the property, and matching with all values is considered a successful match.
The "entity" field under "property instance name", of which value is one of entity name under "entities" field, indicates the Home Assistant entity to be created. The "device_class" field under "property instance name" indicates the Home Assistant entity's `_attr_device_class`.
- Event
The conversion follows `SPEC_EVENT_TRANS_MAP`.
```
{
'<event instance name>': str
}
```
The value of the event instance name indicates `_attr_device_class` of the Home Assistant entity to be created.
### MIoT-Spec-V2 Filter
`spec_filter.json` is used to filter out the MIoT-Spec-V2 instance that will not be converted to Home Assistant entity.
The format of `spec_filter.json` is as follows.
```
{
"<MIoT-Spec-V2 device instance>":{
"services": list<service_iid: str>,
"properties": list<service_iid.property_iid: str>,
"events": list<service_iid.event_iid: str>,
"actions": list<service_iid.action_iid: str>,
}
}
```
The key of `spec_filter.json` dictionary is the urn excluding the "version " field of the MIoT-Spec-V2 device instance. The firmware of different versions of the same product may be associated with the MIoT-Spec-V2 device instances of different versions. It is required that the MIoT-Spec-V2 instance of a higher version must contain all MIoT-Spec-V2 instances of the lower versions when a vendor defines the MIoT-Spec-V2 of its product on MIoT platform. Thus, the key of `spec_filter.json` does not need to specify the version number of MIoT-Spec-V2 device instance.
The value of "services", "properties", "events" or "actions" fields under "device instance" is the instance id (iid) of the service, property, event or action that will be ignored in the conversion process. Wildcard matching is supported.
Example:
```
{
"urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1":{
"services": ["*"] # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2 device instance.
},
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
"services": ["3"], # Filter out the service whose iid=3.
"properties": ["4.*"] # Filter out all properties in the service whose iid=4.
"events": ["4.1"], # Filter out the iid=1 event in the iid=4 service.
"actions": ["4.1"] # Filter out the iid=1 action in the iid=4 service.
}
}
```
Device information service (urn:miot-spec-v2:service:device-information:00007801) of all devices will never be converted to Home Assistant entity.
## 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.
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.
The format of `multi_lang.json` is as follows.
```
{
"<MIoT-Spec-V2 device instance>": {
"<language code>": {
"<instance code>": <translation: str>
}
}
}
```
The key of `multi_lang.json` dictionary is the urn excluding the "version" field of the MIoT-Spec-V2 device instance.
The language code is zh-Hans, zh-Hant, en, es, ru, fr, de, or ja, corresponding to the 8 selectable languages mentioned above.
The instance code is the code of the MIoT-Spec-V2 instance, which is in the format of:
```
service:<siid> # service
service:<siid>:property:<piid> # property
service:<siid>:property:<piid>:valuelist:<value> # the value in value-list of a property
service:<siid>:event:<eiid> # event
service:<siid>:action:<aiid> # action
```
siid, piid, eiid, aiid and value are all decimal three-digit integers.
Example:
```
{
"urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1": {
"zh-Hant": {
"service:002": "養生壺",
"service:002:property:001": "工作狀態",
"service:002:property:001:valuelist:000": "待機中",
"service:002:action:002": "停止烹飪",
"service:005:event:001": "烹飪完成"
}
}
}
```
## Documents
- [License](./LICENSE.md)
- Contribution Guidelines: [English](./doc/CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
- [ChangeLog](./doc/CHANGELOG.md)
- Development Documents: https://developers.home-assistant.io/docs/creating_component_index
## Directory Structure
- miot: core code.
- miot/miot_client: Adding a login user in the integration needs adding a miot_client instance.
- miot/miot_cloud: Contains functions related to the cloud service, including OAuth login process, HTTP interface functions (to get the user information, to send the device control command, etc.)
- miot/miot_device: Device entity, including device information, processing logic of property, event and action.
- miot/miot_mips: Message bus for subscribing and publishing method.
- miot/miot_spec: Parse MIoT-Spec-V2.
- miot/miot_lan: Device LAN control, including device discovery, device control, etc.
- miot/miot_mdns: Central hub gateway service LAN discovery.
- miot/miot_network: Obtain network status and network information.
- miot/miot_storage: Used for integrated file storage.
- miot/test: Test scripts.
- config_flow: Config flow.

View File

@ -0,0 +1,309 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
The Xiaomi Home integration Init File.
"""
from __future__ import annotations
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.components import persistent_notification
from homeassistant.helpers import device_registry, entity_registry
from .miot.miot_storage import (
DeviceManufacturer, MIoTStorage, MIoTCert)
from .miot.miot_spec import (
MIoTSpecInstance, MIoTSpecParser, MIoTSpecService)
from .miot.const import (
DEFAULT_INTEGRATION_LANGUAGE, DOMAIN, SUPPORTED_PLATFORMS)
from .miot.miot_error import MIoTOauthError
from .miot.miot_device import MIoTDevice
from .miot.miot_client import MIoTClient, get_miot_instance_async
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, hass_config: dict) -> bool:
hass.data.setdefault(DOMAIN, {})
# {[entry_id:str]: MIoTClient}, miot client instance
hass.data[DOMAIN].setdefault('miot_clients', {})
# {[entry_id:str]: list[MIoTDevice]}
hass.data[DOMAIN].setdefault('devices', {})
# {[entry_id:str]: entities}
hass.data[DOMAIN].setdefault('entities', {})
for platform in SUPPORTED_PLATFORMS:
hass.data[DOMAIN]['entities'][platform] = []
return True
async def async_setup_entry(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up an entry."""
def ha_persistent_notify(
notify_id: str, title: Optional[str] = None,
message: Optional[str] = None
) -> None:
"""Send messages in Notifications dialog box."""
if title:
persistent_notification.async_create(
hass=hass, message=message,
title=title, notification_id=notify_id)
else:
persistent_notification.async_dismiss(
hass=hass, notification_id=notify_id)
entry_id = config_entry.entry_id
entry_data = dict(config_entry.data)
ha_persistent_notify(
notify_id=f'{entry_id}.oauth_error', title=None, message=None)
try:
miot_client: MIoTClient = await get_miot_instance_async(
hass=hass, entry_id=entry_id,
entry_data=entry_data,
persistent_notify=ha_persistent_notify)
# Spec parser
spec_parser = MIoTSpecParser(
lang=entry_data.get(
'integration_language', DEFAULT_INTEGRATION_LANGUAGE),
storage=miot_client.miot_storage,
loop=miot_client.main_loop
)
await spec_parser.init_async()
# Manufacturer
manufacturer: DeviceManufacturer = DeviceManufacturer(
storage=miot_client.miot_storage,
loop=miot_client.main_loop)
await manufacturer.init_async()
miot_devices: list[MIoTDevice] = []
er = entity_registry.async_get(hass=hass)
for did, info in miot_client.device_list.items():
spec_instance: MIoTSpecInstance = await spec_parser.parse(
urn=info['urn'])
if spec_instance is None:
_LOGGER.error('spec content is None, %s, %s', did, info)
continue
device: MIoTDevice = MIoTDevice(
miot_client=miot_client,
device_info={
**info, 'manufacturer': manufacturer.get_name(
info.get('manufacturer', ''))},
spec_instance=spec_instance)
miot_devices.append(device)
device.spec_transform()
# Remove filter entities and non-standard entities
for platform in SUPPORTED_PLATFORMS:
# ONLY support filter spec service translate entity
if platform in device.entity_list:
filter_entities = list(filter(
lambda entity: (
isinstance(entity.spec, MIoTSpecService)
and (
entity.spec.need_filter
or (
miot_client.hide_non_standard_entities
and entity.spec.proprietary))
),
device.entity_list[platform]))
for entity in filter_entities:
device.entity_list[platform].remove(entity)
entity_id = device.gen_service_entity_id(
ha_domain=platform, siid=entity.spec.iid)
if er.async_get(entity_id_or_uuid=entity_id):
er.async_remove(entity_id=entity_id)
if platform in device.prop_list:
filter_props = list(filter(
lambda prop: (
prop.need_filter or (
miot_client.hide_non_standard_entities
and prop.proprietary)),
device.prop_list[platform]))
for prop in filter_props:
device.prop_list[platform].remove(prop)
entity_id = device.gen_prop_entity_id(
ha_domain=platform, spec_name=prop.name,
siid=prop.service.iid, piid=prop.iid)
if er.async_get(entity_id_or_uuid=entity_id):
er.async_remove(entity_id=entity_id)
if platform in device.event_list:
filter_events = list(filter(
lambda event: (
event.need_filter or (
miot_client.hide_non_standard_entities
and event.proprietary)),
device.event_list[platform]))
for event in filter_events:
device.event_list[platform].remove(event)
entity_id = device.gen_event_entity_id(
ha_domain=platform, spec_name=event.name,
siid=event.service.iid, eiid=event.iid)
if er.async_get(entity_id_or_uuid=entity_id):
er.async_remove(entity_id=entity_id)
if platform in device.action_list:
filter_actions = list(filter(
lambda action: (
action.need_filter or (
miot_client.hide_non_standard_entities
and action.proprietary)),
device.action_list[platform]))
for action in filter_actions:
device.action_list[platform].remove(action)
entity_id = device.gen_action_entity_id(
ha_domain=platform, spec_name=action.name,
siid=action.service.iid, aiid=action.iid)
if er.async_get(entity_id_or_uuid=entity_id):
er.async_remove(entity_id=entity_id)
# Remove non-standard action debug entity
if platform == 'notify':
entity_id = device.gen_action_entity_id(
ha_domain='text', spec_name=action.name,
siid=action.service.iid, aiid=action.iid)
if er.async_get(entity_id_or_uuid=entity_id):
er.async_remove(entity_id=entity_id)
# Action debug
if miot_client.action_debug:
if 'notify' in device.action_list:
# Add text entity for debug action
device.action_list['action_text'] = (
device.action_list['notify'])
else:
# Remove text entity for debug action
for action in device.action_list.get('notify', []):
entity_id = device.gen_action_entity_id(
ha_domain='text', spec_name=action.name,
siid=action.service.iid, aiid=action.iid)
if er.async_get(entity_id_or_uuid=entity_id):
er.async_remove(entity_id=entity_id)
hass.data[DOMAIN]['devices'][config_entry.entry_id] = miot_devices
await hass.config_entries.async_forward_entry_setups(
config_entry, SUPPORTED_PLATFORMS)
# Remove the deleted devices
devices_remove = (await miot_client.miot_storage.load_user_config_async(
uid=config_entry.data['uid'],
cloud_server=config_entry.data['cloud_server'],
keys=['devices_remove'])).get('devices_remove', [])
if isinstance(devices_remove, list) and devices_remove:
dr = device_registry.async_get(hass)
for did in devices_remove:
device_entry = dr.async_get_device(
identifiers={(
DOMAIN,
MIoTDevice.gen_did_tag(
cloud_server=config_entry.data['cloud_server'],
did=did))},
connections=None)
if not device_entry:
_LOGGER.error('remove device not found, %s', did)
continue
dr.async_remove_device(device_id=device_entry.id)
_LOGGER.info(
'delete device entry, %s, %s', did, device_entry.id)
await miot_client.miot_storage.update_user_config_async(
uid=config_entry.data['uid'],
cloud_server=config_entry.data['cloud_server'],
config={'devices_remove': []})
await spec_parser.deinit_async()
await manufacturer.deinit_async()
except MIoTOauthError as oauth_error:
ha_persistent_notify(
notify_id=f'{entry_id}.oauth_error',
title='Xiaomi Home Oauth Error',
message=f'Please re-add.\r\nerror: {oauth_error}'
)
except Exception as err:
raise err
return True
async def async_unload_entry(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Unload the entry."""
entry_id = config_entry.entry_id
# Unload the platform
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, SUPPORTED_PLATFORMS)
if unload_ok:
hass.data[DOMAIN]['entities'].pop(entry_id, None)
hass.data[DOMAIN]['devices'].pop(entry_id, None)
# Remove integration data
miot_client: MIoTClient = hass.data[DOMAIN]['miot_clients'].pop(
entry_id, None)
if miot_client:
await miot_client.deinit_async()
del miot_client
return True
async def async_remove_entry(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Remove the entry."""
entry_data = dict(config_entry.data)
uid: str = entry_data['uid']
cloud_server: str = entry_data['cloud_server']
miot_storage: MIoTStorage = hass.data[DOMAIN]['miot_storage']
miot_cert: MIoTCert = MIoTCert(
storage=miot_storage, uid=uid, cloud_server=cloud_server)
# Clean device list
await miot_storage.remove_async(
domain='miot_devices', name=f'{uid}_{cloud_server}', type_=dict)
# Clean user configuration
await miot_storage.update_user_config_async(
uid=uid, cloud_server=cloud_server, config=None)
# Clean cert file
await miot_cert.remove_user_cert_async()
await miot_cert.remove_user_key_async()
return True

View File

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Binary sensor entities for Xiaomi Home.
"""
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.binary_sensor import BinarySensorEntity
from .miot.miot_spec import MIoTSpecProperty
from .miot.miot_device import MIoTDevice, MIoTPropertyEntity
from .miot.const import DOMAIN
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for prop in miot_device.prop_list.get('binary_sensor', []):
new_entities.append(BinarySensor(
miot_device=miot_device, spec=prop))
if new_entities:
async_add_entities(new_entities)
class BinarySensor(MIoTPropertyEntity, BinarySensorEntity):
"""Binary sensor entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
"""Initialize the BinarySensor."""
super().__init__(miot_device=miot_device, spec=spec)
# Set device_class
self._attr_device_class = spec.device_class
@property
def is_on(self) -> bool:
"""On/Off state. True if the binary sensor is on, False otherwise."""
return self._value is True

View File

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Button entities for Xiaomi Home.
"""
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.button import ButtonEntity
from .miot.miot_device import MIoTActionEntity, MIoTDevice
from .miot.miot_spec import MIoTSpecAction
from .miot.const import DOMAIN
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for action in miot_device.action_list.get('button', []):
new_entities.append(Button(miot_device=miot_device, spec=action))
if new_entities:
async_add_entities(new_entities)
class Button(MIoTActionEntity, ButtonEntity):
"""Button entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecAction) -> None:
"""Initialize the Button."""
super().__init__(miot_device=miot_device, spec=spec)
# Use default device class
async def async_press(self) -> None:
"""Press the button."""
return await self.action_async()

View File

@ -0,0 +1,470 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Climate entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.climate import (
SWING_ON,
SWING_OFF,
SWING_BOTH,
SWING_VERTICAL,
SWING_HORIZONTAL,
ATTR_TEMPERATURE,
HVACMode,
ClimateEntity,
ClimateEntityFeature
)
from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
from .miot.miot_spec import MIoTSpecProperty
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for data in miot_device.entity_list.get('climate', []):
new_entities.append(
AirConditioner(miot_device=miot_device, entity_data=data))
if new_entities:
async_add_entities(new_entities)
class AirConditioner(MIoTServiceEntity, ClimateEntity):
"""Air conditioner entities for Xiaomi Home."""
# service: air-conditioner
_prop_on: Optional[MIoTSpecProperty]
_prop_mode: Optional[MIoTSpecProperty]
_prop_target_temp: Optional[MIoTSpecProperty]
_prop_target_humi: Optional[MIoTSpecProperty]
# service: fan-control
_prop_fan_on: Optional[MIoTSpecProperty]
_prop_fan_level: Optional[MIoTSpecProperty]
_prop_horizontal_swing: Optional[MIoTSpecProperty]
_prop_vertical_swing: Optional[MIoTSpecProperty]
# service: environment
_prop_env_temp: Optional[MIoTSpecProperty]
_prop_env_humi: Optional[MIoTSpecProperty]
# service: air-condition-outlet-matching
_prop_ac_state: Optional[MIoTSpecProperty]
_value_ac_state: Optional[dict[str, int]]
_hvac_mode_map: Optional[dict[int, HVACMode]]
_fan_mode_map: Optional[dict[int, str]]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
"""Initialize the Climate."""
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_icon = 'mdi:air-conditioner'
self._attr_supported_features = ClimateEntityFeature(0)
self._attr_swing_mode = None
self._attr_swing_modes = []
self._prop_on = None
self._prop_mode = None
self._prop_target_temp = None
self._prop_target_humi = None
self._prop_fan_on = None
self._prop_fan_level = None
self._prop_horizontal_swing = None
self._prop_vertical_swing = None
self._prop_env_temp = None
self._prop_env_humi = None
self._prop_ac_state = None
self._value_ac_state = None
self._hvac_mode_map = None
self._fan_mode_map = None
# properties
for prop in entity_data.props:
if prop.name == 'on':
if prop.service.name == 'air-conditioner':
self._attr_supported_features |= (
ClimateEntityFeature.TURN_ON)
self._attr_supported_features |= (
ClimateEntityFeature.TURN_OFF)
self._prop_on = prop
elif prop.service.name == 'fan-control':
self._attr_swing_modes.append(SWING_ON)
self._prop_fan_on = prop
else:
_LOGGER.error(
'unknown on property, %s', self.entity_id)
elif prop.name == 'mode':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'invalid mode value_list, %s', self.entity_id)
continue
self._hvac_mode_map = {}
for item in prop.value_list:
if item['name'].lower() in {'off', 'idle'}:
self._hvac_mode_map[item['value']] = HVACMode.OFF
elif item['name'].lower() in {'auto'}:
self._hvac_mode_map[item['value']] = HVACMode.AUTO
elif item['name'].lower() in {'cool'}:
self._hvac_mode_map[item['value']] = HVACMode.COOL
elif item['name'].lower() in {'heat'}:
self._hvac_mode_map[item['value']] = HVACMode.HEAT
elif item['name'].lower() in {'dry'}:
self._hvac_mode_map[item['value']] = HVACMode.DRY
elif item['name'].lower() in {'fan'}:
self._hvac_mode_map[item['value']] = HVACMode.FAN_ONLY
self._attr_hvac_modes = list(self._hvac_mode_map.values())
self._prop_mode = 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 == 'target-humidity':
if not isinstance(prop.value_range, dict):
_LOGGER.error(
'invalid target-humidity value_range format, %s',
self.entity_id)
continue
self._attr_min_humidity = prop.value_range['min']
self._attr_max_humidity = prop.value_range['max']
self._attr_supported_features |= (
ClimateEntityFeature.TARGET_HUMIDITY)
self._prop_target_humi = prop
elif prop.name == 'fan-level':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'invalid fan-level value_list, %s', self.entity_id)
continue
self._fan_mode_map = {
item['value']: item['description']
for item in prop.value_list}
self._attr_fan_modes = list(self._fan_mode_map.values())
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
self._prop_fan_level = prop
elif prop.name == 'horizontal-swing':
self._attr_swing_modes.append(SWING_HORIZONTAL)
self._prop_horizontal_swing = prop
elif prop.name == 'vertical-swing':
self._attr_swing_modes.append(SWING_VERTICAL)
self._prop_vertical_swing = prop
elif prop.name == 'temperature':
self._prop_env_temp = prop
elif prop.name == 'relative-humidity':
self._prop_env_humi = prop
elif prop.name == 'ac-state':
self._prop_ac_state = prop
self._value_ac_state = {}
self.sub_prop_changed(
prop=prop, handler=self.__ac_state_changed)
# hvac modes
if HVACMode.OFF not in self._attr_hvac_modes:
self._attr_hvac_modes.append(HVACMode.OFF)
# swing modes
if (
SWING_HORIZONTAL in self._attr_swing_modes
and SWING_VERTICAL in self._attr_swing_modes
):
self._attr_swing_modes.append(SWING_BOTH)
if self._attr_swing_modes:
self._attr_swing_modes.insert(0, SWING_OFF)
self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
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."""
if hvac_mode == HVACMode.OFF and self._prop_on:
if not await self.set_property_async(
prop=self._prop_on, value=False):
raise RuntimeError(
f'set climate prop.on failed, {hvac_mode}, '
f'{self.entity_id}')
return
mode_value = self.get_map_value(
map_=self._hvac_mode_map, description=hvac_mode)
if (
mode_value is None or
not await self.set_property_async(
prop=self._prop_mode, value=mode_value)
):
raise RuntimeError(
f'set climate prop.mode failed, {hvac_mode}, {self.entity_id}')
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_humidity(self, humidity):
"""Set new target humidity."""
if humidity > self.max_humidity:
humidity = self.max_humidity
elif humidity < self.min_humidity:
humidity = self.min_humidity
await self.set_property_async(
prop=self._prop_target_humi, value=humidity)
async def async_set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
if swing_mode == SWING_BOTH:
if await self.set_property_async(
prop=self._prop_horizontal_swing, value=True, update=False):
self.set_prop_value(self._prop_horizontal_swing, value=True)
if await self.set_property_async(
prop=self._prop_vertical_swing, value=True, update=False):
self.set_prop_value(self._prop_vertical_swing, value=True)
elif swing_mode == SWING_HORIZONTAL:
if await self.set_property_async(
prop=self._prop_horizontal_swing, value=True, update=False):
self.set_prop_value(self._prop_horizontal_swing, value=True)
elif swing_mode == SWING_VERTICAL:
if await self.set_property_async(
prop=self._prop_vertical_swing, value=True, update=False):
self.set_prop_value(self._prop_vertical_swing, value=True)
elif swing_mode == SWING_ON:
if await self.set_property_async(
prop=self._prop_fan_on, value=True, update=False):
self.set_prop_value(self._prop_fan_on, value=True)
elif swing_mode == SWING_OFF:
if self._prop_fan_on and await self.set_property_async(
prop=self._prop_fan_on, value=False, update=False):
self.set_prop_value(self._prop_fan_on, value=False)
if self._prop_horizontal_swing and await self.set_property_async(
prop=self._prop_horizontal_swing, value=False,
update=False):
self.set_prop_value(self._prop_horizontal_swing, value=False)
if self._prop_vertical_swing and await self.set_property_async(
prop=self._prop_vertical_swing, value=False, update=False):
self.set_prop_value(self._prop_vertical_swing, value=False)
else:
raise RuntimeError(
f'unknown swing_mode, {swing_mode}, {self.entity_id}')
self.async_write_ha_state()
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
mode_value = self.get_map_value(
map_=self._fan_mode_map, description=fan_mode)
if mode_value is None or not await self.set_property_async(
prop=self._prop_fan_level, value=mode_value):
raise RuntimeError(
f'set climate prop.fan_mode failed, {fan_mode}, '
f'{self.entity_id}')
@ 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 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
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. e.g., heat, cool mode."""
if self._prop_on and self.get_prop_value(prop=self._prop_on) is False:
return HVACMode.OFF
return self.get_map_description(
map_=self._hvac_mode_map,
key=self.get_prop_value(prop=self._prop_mode))
@ property
def fan_mode(self) -> Optional[str]:
"""Return the fan mode.
Requires ClimateEntityFeature.FAN_MODE.
"""
return self.get_map_description(
map_=self._fan_mode_map,
key=self.get_prop_value(prop=self._prop_fan_level))
@ property
def swing_mode(self) -> Optional[str]:
"""Return the swing mode.
Requires ClimateEntityFeature.SWING_MODE.
"""
horizontal: bool = (
self.get_prop_value(prop=self._prop_horizontal_swing)
if self._prop_horizontal_swing else None)
vertical: bool = (
self.get_prop_value(prop=self._prop_vertical_swing)
if self._prop_vertical_swing else None)
if horizontal and vertical:
return SWING_BOTH
if horizontal:
return SWING_HORIZONTAL
if vertical:
return SWING_VERTICAL
if self._prop_fan_on:
if self.get_prop_value(prop=self._prop_fan_on):
return SWING_ON
else:
return SWING_OFF
return None
def __ac_state_changed(self, prop: MIoTSpecProperty, value: any) -> None:
del prop
if not isinstance(value, str):
_LOGGER.error(
'ac_status value format error, %s', value)
return
v_ac_state = {}
v_split = value.split('_')
for item in v_split:
if len(item) < 2:
_LOGGER.error('ac_status value error, %s', item)
continue
try:
v_ac_state[item[0]] = int(item[1:])
except ValueError:
_LOGGER.error('ac_status value error, %s', item)
# P: status. 0: on, 1: off
if 'P' in v_ac_state and self._prop_on:
self.set_prop_value(prop=self._prop_on,
value=v_ac_state['P'] == 0)
# M: model. 0: cool, 1: heat, 2: auto, 3: fan, 4: dry
if 'M' in v_ac_state and self._prop_mode:
mode: Optional[HVACMode] = {
0: HVACMode.COOL,
1: HVACMode.HEAT,
2: HVACMode.AUTO,
3: HVACMode.FAN_ONLY,
4: HVACMode.DRY
}.get(v_ac_state['M'], None)
if mode:
self.set_prop_value(
prop=self._prop_mode, value=self.get_map_value(
map_=self._hvac_mode_map, description=mode))
# T: target temperature
if 'T' in v_ac_state and self._prop_target_temp:
self.set_prop_value(prop=self._prop_target_temp,
value=v_ac_state['T'])
# S: fan level. 0: auto, 1: low, 2: media, 3: high
if 'S' in v_ac_state and self._prop_fan_level:
self.set_prop_value(prop=self._prop_fan_level,
value=v_ac_state['S'])
# D: swing mode. 0: on, 1: off
if 'D' in v_ac_state and len(self._attr_swing_modes) == 2:
if (
SWING_HORIZONTAL in self._attr_swing_modes
and self._prop_horizontal_swing
):
self.set_prop_value(
prop=self._prop_horizontal_swing,
value=v_ac_state['D'] == 0)
elif (
SWING_VERTICAL in self._attr_swing_modes
and self._prop_vertical_swing
):
self.set_prop_value(
prop=self._prop_vertical_swing,
value=v_ac_state['D'] == 0)
self._value_ac_state.update(v_ac_state)
_LOGGER.debug(
'ac_state update, %s', self._value_ac_state)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,239 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Cover entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.cover import (
ATTR_POSITION,
CoverEntity,
CoverEntityFeature,
CoverDeviceClass
)
from .miot.miot_spec import MIoTSpecProperty
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
from .miot.const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for data in miot_device.entity_list.get('cover', []):
if data.spec.name == 'curtain':
data.spec.device_class = CoverDeviceClass.CURTAIN
elif data.spec.name == 'window-opener':
data.spec.device_class = CoverDeviceClass.WINDOW
new_entities.append(
Cover(miot_device=miot_device, entity_data=data))
if new_entities:
async_add_entities(new_entities)
class Cover(MIoTServiceEntity, CoverEntity):
"""Cover entities for Xiaomi Home."""
# pylint: disable=unused-argument
_prop_motor_control: Optional[MIoTSpecProperty]
_prop_motor_value_open: Optional[int]
_prop_motor_value_close: Optional[int]
_prop_motor_value_pause: Optional[int]
_prop_status: Optional[MIoTSpecProperty]
_prop_status_opening: Optional[bool]
_prop_status_closing: Optional[bool]
_prop_status_stop: Optional[bool]
_prop_current_position: Optional[MIoTSpecProperty]
_prop_target_position: Optional[MIoTSpecProperty]
_prop_position_value_min: Optional[int]
_prop_position_value_max: Optional[int]
_prop_position_value_range: Optional[int]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
"""Initialize the Cover."""
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_device_class = entity_data.spec.device_class
self._attr_supported_color_modes = set()
self._attr_supported_features = CoverEntityFeature(0)
self._prop_motor_control = None
self._prop_motor_value_open = None
self._prop_motor_value_close = None
self._prop_motor_value_pause = None
self._prop_status = None
self._prop_current_position = None
self._prop_target_position = None
self._prop_position_value_min = None
self._prop_position_value_max = None
self._prop_position_value_range = None
# properties
for prop in entity_data.props:
if prop.name == 'motor-control':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'motor-control value_list is None, %s', self.entity_id)
continue
for item in prop.value_list:
if item['name'].lower() in ['open']:
self._attr_supported_features |= (
CoverEntityFeature.OPEN)
self._prop_motor_value_open = item['value']
elif item['name'].lower() in ['close']:
self._attr_supported_features |= (
CoverEntityFeature.CLOSE)
self._prop_motor_value_close = item['value']
elif item['name'].lower() in ['pause']:
self._attr_supported_features |= (
CoverEntityFeature.STOP)
self._prop_motor_value_pause = item['value']
self._prop_motor_control = prop
elif prop.name == 'status':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'status value_list is None, %s', self.entity_id)
continue
for item in prop.value_list:
if item['name'].lower() in ['opening']:
self._prop_status_opening = item['value']
elif item['name'].lower() in ['closing']:
self._prop_status_closing = item['value']
elif item['name'].lower() in ['stop']:
self._prop_status_stop = item['value']
self._prop_status = prop
elif prop.name == 'current-position':
self._prop_current_position = prop
elif prop.name == 'target-position':
if not isinstance(prop.value_range, dict):
_LOGGER.error(
'invalid target-position value_range format, %s',
self.entity_id)
continue
self._prop_position_value_min = prop.value_range['min']
self._prop_position_value_max = prop.value_range['max']
self._prop_position_value_range = (
self._prop_position_value_max -
self._prop_position_value_min)
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
self._prop_target_position = prop
async def async_open_cover(self, **kwargs) -> None:
"""Open the cover."""
await self.set_property_async(
self._prop_motor_control, self._prop_motor_value_open)
async def async_close_cover(self, **kwargs) -> None:
"""Close the cover."""
await self.set_property_async(
self._prop_motor_control, self._prop_motor_value_close)
async def async_stop_cover(self, **kwargs) -> None:
"""Stop the cover."""
await self.set_property_async(
self._prop_motor_control, self._prop_motor_value_pause)
async def async_set_cover_position(self, **kwargs) -> None:
"""Set the position of the cover."""
pos = kwargs.get(ATTR_POSITION, None)
if pos is None:
return None
pos = round(pos*self._prop_position_value_range/100)
return await self.set_property_async(
prop=self._prop_target_position, value=pos)
@property
def current_cover_position(self) -> Optional[int]:
"""Return the current position.
0: the cover is closed, 100: the cover is fully opened, None: unknown.
"""
pos = self.get_prop_value(prop=self._prop_current_position)
if pos is None:
return None
return round(pos*100/self._prop_position_value_range)
@property
def is_opening(self) -> Optional[bool]:
"""Return if the cover is opening."""
if self._prop_status is None:
return None
return self.get_prop_value(
prop=self._prop_status) == self._prop_status_opening
@property
def is_closing(self) -> Optional[bool]:
"""Return if the cover is closing."""
if self._prop_status is None:
return None
return self.get_prop_value(
prop=self._prop_status) == self._prop_status_closing
@property
def is_closed(self) -> Optional[bool]:
"""Return if the cover is closed."""
return self.get_prop_value(prop=self._prop_current_position) == 0

View File

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Event entities for Xiaomi Home.
"""
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.event import EventEntity
from .miot.miot_spec import MIoTSpecEvent
from .miot.miot_device import MIoTDevice, MIoTEventEntity
from .miot.const import DOMAIN
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for event in miot_device.event_list.get('event', []):
new_entities.append(Event(miot_device=miot_device, spec=event))
if new_entities:
async_add_entities(new_entities)
class Event(MIoTEventEntity, EventEntity):
"""Event entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecEvent) -> None:
"""Initialize the Event."""
super().__init__(miot_device=miot_device, spec=spec)
# Set device_class
self._attr_device_class = spec.device_class
def on_event_occurred(self, name: str, arguments: list[dict[int, any]]):
"""An event is occurred."""
self._trigger_event(event_type=name, event_attributes=arguments)

View File

@ -0,0 +1,264 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Fan entities for Xiaomi Home.
"""
from __future__ import annotations
from typing import Any, Optional
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.util.percentage import (
percentage_to_ranged_value,
ranged_value_to_percentage
)
from .miot.miot_spec import MIoTSpecProperty
from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for data in miot_device.entity_list.get('fan', []):
new_entities.append(Fan(miot_device=miot_device, entity_data=data))
if new_entities:
async_add_entities(new_entities)
class Fan(MIoTServiceEntity, FanEntity):
"""Fan entities for Xiaomi Home."""
# pylint: disable=unused-argument
_prop_on: Optional[MIoTSpecProperty]
_prop_fan_level: Optional[MIoTSpecProperty]
_prop_mode: Optional[MIoTSpecProperty]
_prop_horizontal_swing: Optional[MIoTSpecProperty]
_speed_min: Optional[int]
_speed_max: Optional[int]
_speed_step: Optional[int]
_mode_list: Optional[dict[any, any]]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
"""Initialize the Fan."""
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_preset_modes = []
self._attr_supported_features = FanEntityFeature(0)
self._prop_on = None
self._prop_fan_level = None
self._prop_mode = None
self._prop_horizontal_swing = None
self._speed_min = 65535
self._speed_max = 0
self._speed_step = 1
self._mode_list = None
# properties
for prop in entity_data.props:
if prop.name == 'on':
self._attr_supported_features |= FanEntityFeature.TURN_ON
self._attr_supported_features |= FanEntityFeature.TURN_OFF
self._prop_on = prop
elif prop.name == 'fan-level':
if isinstance(prop.value_range, dict):
# Fan level with value-range
self._speed_min = prop.value_range['min']
self._speed_max = prop.value_range['max']
self._speed_step = prop.value_range['step']
self._attr_speed_count = self._speed_max - self._speed_min+1
self._attr_supported_features |= FanEntityFeature.SET_SPEED
self._prop_fan_level = prop
elif (
self._prop_fan_level is None
and isinstance(prop.value_list, list)
and prop.value_list
):
# Fan level with value-list
for item in prop.value_list:
self._speed_min = min(self._speed_min, item['value'])
self._speed_max = max(self._speed_max, item['value'])
self._attr_speed_count = self._speed_max - self._speed_min+1
self._attr_supported_features |= FanEntityFeature.SET_SPEED
self._prop_fan_level = prop
elif prop.name == 'mode':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'mode value_list is None, %s', self.entity_id)
continue
self._mode_list = {
item['value']: item['description']
for item in prop.value_list}
self._attr_preset_modes = list(self._mode_list.values())
self._attr_supported_features |= FanEntityFeature.PRESET_MODE
self._prop_mode = prop
elif prop.name == 'horizontal-swing':
self._attr_supported_features |= FanEntityFeature.OSCILLATE
self._prop_horizontal_swing = prop
def __get_mode_description(self, key: int) -> Optional[str]:
if self._mode_list is None:
return None
return self._mode_list.get(key, None)
def __get_mode_value(self, description: str) -> Optional[int]:
if self._mode_list is None:
return None
for key, value in self._mode_list.items():
if value == description:
return key
return None
async def async_turn_on(
self, percentage: int = None, preset_mode: str = None, **kwargs: Any
) -> None:
"""Turn the fan on.
Shall set the percentage or the preset_mode attr to complying
if applicable.
"""
# on
await self.set_property_async(prop=self._prop_on, value=True)
# percentage
if percentage:
await self.set_property_async(
prop=self._prop_fan_level,
value=int(percentage*self._attr_speed_count/100))
# preset_mode
if preset_mode:
await self.set_property_async(
self._prop_mode,
value=self.__get_mode_value(description=preset_mode))
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
await self.set_property_async(prop=self._prop_on, value=False)
async def async_toggle(self, **kwargs: Any) -> None:
"""Toggle the fan."""
await self.set_property_async(prop=self._prop_on, value=not self.is_on)
async def async_set_percentage(self, percentage: int) -> None:
"""Set the percentage of the fan speed."""
if percentage > 0:
await self.set_property_async(
prop=self._prop_fan_level,
value=int(percentage_to_ranged_value(
low_high_range=(self._speed_min, self._speed_max),
percentage=percentage)))
if not self.is_on:
# If the fan is off, turn it on.
await self.set_property_async(prop=self._prop_on, value=True)
else:
await self.set_property_async(prop=self._prop_on, value=False)
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode."""
await self.set_property_async(
self._prop_mode,
value=self.__get_mode_value(description=preset_mode))
async def async_set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
async def async_oscillate(self, oscillating: bool) -> None:
"""Oscillate the fan."""
await self.set_property_async(
prop=self._prop_horizontal_swing, value=oscillating)
@property
def is_on(self) -> Optional[bool]:
"""Return if the fan is on. """
return self.get_prop_value(
prop=self._prop_on) if self._prop_on else None
@property
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode,
e.g., auto, smart, eco, favorite."""
return (
self.__get_mode_description(
key=self.get_prop_value(prop=self._prop_mode))
if self._prop_mode else None)
@property
def percentage(self) -> Optional[int]:
"""Return the current percentage of the fan speed."""
fan_level = self.get_prop_value(prop=self._prop_fan_level)
return ranged_value_to_percentage(
low_high_range=(self._speed_min, self._speed_max),
value=fan_level) if fan_level else None
@property
def oscillating(self) -> Optional[bool]:
"""Return if the fan is oscillating."""
return (
self.get_prop_value(
prop=self._prop_horizontal_swing)
if self._prop_horizontal_swing else None)
@property
def percentage_step(self) -> float:
"""Return the step of the fan speed."""
return self._speed_step

View File

@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Humidifier entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.humidifier import (
HumidifierEntity,
HumidifierDeviceClass,
HumidifierEntityFeature
)
from .miot.miot_spec import MIoTSpecProperty
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
from .miot.const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for data in miot_device.entity_list.get('humidifier', []):
data.device_class = HumidifierDeviceClass.HUMIDIFIER
new_entities.append(
Humidifier(miot_device=miot_device, entity_data=data))
for data in miot_device.entity_list.get('dehumidifier', []):
data.device_class = HumidifierDeviceClass.DEHUMIDIFIER
new_entities.append(Humidifier(
miot_device=miot_device, entity_data=data))
if new_entities:
async_add_entities(new_entities)
class Humidifier(MIoTServiceEntity, HumidifierEntity):
"""Humidifier entities for Xiaomi Home."""
# pylint: disable=unused-argument
_prop_on: Optional[MIoTSpecProperty]
_prop_mode: Optional[MIoTSpecProperty]
_prop_target_humidity: Optional[MIoTSpecProperty]
_prop_humidity: Optional[MIoTSpecProperty]
_mode_list: dict[any, any]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
"""Initialize the Humidifier."""
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_device_class = entity_data.device_class
self._attr_supported_features = HumidifierEntityFeature(0)
self._prop_on = None
self._prop_mode = None
self._prop_target_humidity = None
self._prop_humidity = None
self._mode_list = None
# properties
for prop in entity_data.props:
# on
if prop.name == 'on':
self._prop_on = prop
# target-humidity
elif prop.name == 'target-humidity':
if not isinstance(prop.value_range, dict):
_LOGGER.error(
'invalid target-humidity value_range format, %s',
self.entity_id)
continue
self._attr_min_humidity = prop.value_range['min']
self._attr_max_humidity = prop.value_range['max']
self._prop_target_humidity = prop
# mode
elif prop.name == 'mode':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'mode value_list is None, %s', self.entity_id)
continue
self._mode_list = {
item['value']: item['description']
for item in prop.value_list}
self._attr_available_modes = list(
self._mode_list.values())
self._attr_supported_features |= HumidifierEntityFeature.MODES
self._prop_mode = prop
# relative-humidity
elif prop.name == 'relative-humidity':
self._prop_humidity = prop
async def async_turn_on(self, **kwargs):
"""Turn the humidifier on."""
await self.set_property_async(prop=self._prop_on, value=True)
async def async_turn_off(self, **kwargs):
"""Turn the humidifier off."""
await self.set_property_async(prop=self._prop_on, value=False)
async def async_set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
await self.set_property_async(
prop=self._prop_target_humidity, value=humidity)
async def async_set_mode(self, mode: str) -> None:
"""Set new target preset mode."""
await self.set_property_async(
prop=self._prop_mode, value=self.__get_mode_value(description=mode))
@property
def is_on(self) -> Optional[bool]:
"""Return if the humidifier is on."""
return self.get_prop_value(prop=self._prop_on)
@property
def current_humidity(self) -> Optional[int]:
"""Return the current humidity."""
return self.get_prop_value(prop=self._prop_humidity)
@property
def target_humidity(self) -> Optional[int]:
"""Return the target humidity."""
return self.get_prop_value(prop=self._prop_target_humidity)
@property
def mode(self) -> Optional[str]:
"""Return the current preset mode."""
return self.__get_mode_description(
key=self.get_prop_value(prop=self._prop_mode))
def __get_mode_description(self, key: int) -> Optional[str]:
"""Convert mode value to description."""
if self._mode_list is None:
return None
return self._mode_list.get(key, None)
def __get_mode_value(self, description: str) -> Optional[int]:
"""Convert mode description to value."""
if self._mode_list is None:
return None
for key, value in self._mode_list.items():
if value == description:
return key
return None

View File

@ -0,0 +1,300 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Light entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR,
ATTR_HS_COLOR,
ATTR_EFFECT,
LightEntity,
LightEntityFeature,
ColorMode
)
from homeassistant.util.color import (
value_to_brightness,
brightness_to_value,
color_hs_to_RGB
)
from .miot.miot_spec import MIoTSpecProperty
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
from .miot.const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for data in miot_device.entity_list.get('light', []):
new_entities.append(
Light(miot_device=miot_device, entity_data=data))
if new_entities:
async_add_entities(new_entities)
class Light(MIoTServiceEntity, LightEntity):
"""Light entities for Xiaomi Home."""
_prop_on: Optional[MIoTSpecProperty]
_prop_brightness: Optional[MIoTSpecProperty]
_prop_color_temp: Optional[MIoTSpecProperty]
_prop_color: Optional[MIoTSpecProperty]
_prop_mode: Optional[MIoTSpecProperty]
_brightness_scale: Optional[tuple[int, int]]
_mode_list: Optional[dict[any, any]]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
"""Initialize the Light."""
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_color_mode = None
self._attr_supported_color_modes = set()
self._attr_supported_features = LightEntityFeature(0)
if miot_device.did.startswith('group.'):
self._attr_icon = 'mdi:lightbulb-group'
self._prop_on = None
self._prop_brightness = None
self._prop_color_temp = None
self._prop_color = None
self._prop_mode = None
self._brightness_scale = None
self._mode_list = None
# properties
for prop in entity_data.props:
# on
if prop.name == 'on':
self._prop_on = prop
# brightness
if prop.name == 'brightness':
if isinstance(prop.value_range, dict):
self._brightness_scale = (
prop.value_range['min'], prop.value_range['max'])
self._prop_brightness = prop
elif (
self._mode_list is None
and isinstance(prop.value_list, list)
and prop.value_list
):
# For value-list brightness
self._mode_list = {
item['value']: item['description']
for item in prop.value_list}
self._attr_effect_list = list(self._mode_list.values())
self._attr_supported_features |= LightEntityFeature.EFFECT
self._prop_mode = prop
else:
_LOGGER.error(
'invalid brightness format, %s', self.entity_id)
continue
# color-temperature
if prop.name == 'color-temperature':
if not isinstance(prop.value_range, dict):
_LOGGER.error(
'invalid color-temperature value_range format, %s',
self.entity_id)
continue
self._attr_min_color_temp_kelvin = prop.value_range['min']
self._attr_max_color_temp_kelvin = prop.value_range['max']
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
self._attr_color_mode = ColorMode.COLOR_TEMP
self._prop_color_temp = prop
# color
if prop.name == 'color':
self._attr_supported_color_modes.add(ColorMode.RGB)
self._attr_color_mode = ColorMode.RGB
self._prop_color = prop
# mode
if prop.name == 'mode':
mode_list = None
if (
isinstance(prop.value_list, list)
and prop.value_list
):
mode_list = {
item['value']: item['description']
for item in prop.value_list}
elif isinstance(prop.value_range, dict):
mode_list = {}
for value in range(
prop.value_range['min'], prop.value_range['max']):
mode_list[value] = f'{value}'
if mode_list:
self._mode_list = mode_list
self._attr_effect_list = list(self._mode_list.values())
self._attr_supported_features |= LightEntityFeature.EFFECT
self._prop_mode = prop
else:
_LOGGER.error('invalid mode format, %s', self.entity_id)
continue
if not self._attr_supported_color_modes:
if self._prop_brightness:
self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS)
self._attr_color_mode = ColorMode.BRIGHTNESS
elif self._prop_on:
self._attr_supported_color_modes.add(ColorMode.ONOFF)
self._attr_color_mode = ColorMode.ONOFF
def __get_mode_description(self, key: int) -> Optional[str]:
"""Convert mode value to description."""
if self._mode_list is None:
return None
return self._mode_list.get(key, None)
def __get_mode_value(self, description: str) -> Optional[int]:
"""Convert mode description to value."""
if self._mode_list is None:
return None
for key, value in self._mode_list.items():
if value == description:
return key
return None
@property
def is_on(self) -> Optional[bool]:
"""Return if the light is on."""
value_on = self.get_prop_value(prop=self._prop_on)
# Dirty logic for lumi.gateway.mgl03 indicator light
if isinstance(value_on, int):
value_on = value_on == 1
return value_on
@property
def brightness(self) -> Optional[int]:
"""Return the brightness."""
brightness_value = self.get_prop_value(prop=self._prop_brightness)
if brightness_value is None:
return None
return value_to_brightness(self._brightness_scale, brightness_value)
@property
def color_temp_kelvin(self) -> Optional[int]:
"""Return the color temperature."""
return self.get_prop_value(prop=self._prop_color_temp)
@ property
def rgb_color(self) -> Optional[tuple[int, int, int]]:
"""Return the rgb color value."""
rgb = self.get_prop_value(prop=self._prop_color)
if rgb is None:
return None
r = (rgb >> 16) & 0xFF
g = (rgb >> 8) & 0xFF
b = rgb & 0xFF
return r, g, b
@ property
def effect(self) -> Optional[str]:
"""Return the current mode."""
return self.__get_mode_description(
key=self.get_prop_value(prop=self._prop_mode))
async def async_turn_on(self, **kwargs) -> None:
"""Turn the light on.
Shall set attributes in kwargs if applicable.
"""
result: bool = False
# on
# Dirty logic for lumi.gateway.mgl03 indicator light
value_on = True if self._prop_on.format_ == 'bool' else 1
result = await self.set_property_async(
prop=self._prop_on, value=value_on)
# brightness
if ATTR_BRIGHTNESS in kwargs:
brightness = brightness_to_value(
self._brightness_scale, kwargs[ATTR_BRIGHTNESS])
result = await self.set_property_async(
prop=self._prop_brightness, value=brightness)
# color-temperature
if ATTR_COLOR_TEMP_KELVIN in kwargs:
result = await self.set_property_async(
prop=self._prop_color_temp,
value=kwargs[ATTR_COLOR_TEMP_KELVIN])
self._attr_color_mode = ColorMode.COLOR_TEMP
# rgb color
if ATTR_RGB_COLOR in kwargs:
r = kwargs[ATTR_RGB_COLOR][0]
g = kwargs[ATTR_RGB_COLOR][1]
b = kwargs[ATTR_RGB_COLOR][2]
rgb = (r << 16) | (g << 8) | b
result = await self.set_property_async(
prop=self._prop_color, value=rgb)
self._attr_color_mode = ColorMode.RGB
# mode
if ATTR_EFFECT in kwargs:
result = await self.set_property_async(
prop=self._prop_mode,
value=self.__get_mode_value(description=kwargs[ATTR_EFFECT]))
return result
async def async_turn_off(self, **kwargs) -> None:
"""Turn the light off."""
# Dirty logic for lumi.gateway.mgl03 indicator light
value_on = False if self._prop_on.format_ == 'bool' else 0
return await self.set_property_async(prop=self._prop_on, value=value_on)

View File

@ -0,0 +1,32 @@
{
"domain": "xiaomi_home",
"name": "Xiaomi Home",
"codeowners": [
"@XiaoMi"
],
"config_flow": true,
"dependencies": [
"http",
"persistent_notification",
"ffmpeg",
"zeroconf"
],
"documentation": "https://github.com/XiaoMi/ha_xiaomi_home/blob/main/README.md",
"integration_type": "hub",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/XiaoMi/ha_xiaomi_home/issues",
"loggers": [
"Xiaomi Home"
],
"requirements": [
"construct>=2.10.56",
"paho-mqtt<=2.0.0",
"numpy",
"cryptography",
"psutil"
],
"version": "v0.1.0",
"zeroconf": [
"_miot-central._tcp.local."
]
}

View File

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Common utilities.
"""
import json
import random
from typing import Optional
import hashlib
from paho.mqtt.client import MQTTMatcher
def calc_group_id(uid: str, home_id: str) -> str:
"""Calculate the group ID based on a user ID and a home ID."""
return hashlib.sha1(
f'{uid}central_service{home_id}'.encode('utf-8')).hexdigest()[:16]
def load_json_file(json_file: str) -> dict:
"""Load a JSON file."""
with open(json_file, 'r', encoding='utf-8') as f:
return json.load(f)
def randomize_int(value: int, ratio: float) -> int:
"""Randomize an integer value."""
return int(value * (1 - ratio + random.random()*2*ratio))
class MIoTMatcher(MQTTMatcher):
"""MIoT Pub/Sub topic matcher."""
def iter_all_nodes(self) -> any:
"""Return an iterator on all nodes with their paths and contents."""
def rec(node, path):
# pylint: disable=protected-access
if node._content:
yield ('/'.join(path), node._content)
for part, child in node._children.items():
yield from rec(child, path + [part])
return rec(self._root, [])
def get(self, topic: str) -> Optional[any]:
try:
return self[topic]
except KeyError:
return None

View File

@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Constants.
"""
DOMAIN: str = 'xiaomi_home'
DEFAULT_NAME: str = 'Xiaomi Home'
DEFAULT_NICK_NAME: str = 'Xiaomi'
MIHOME_HTTP_API_TIMEOUT: int = 30
MIHOME_MQTT_KEEPALIVE: int = 60
# seconds, 3 days
MIHOME_CERT_EXPIRE_MARGIN: int = 3600*24*3
NETWORK_REFRESH_INTERVAL: int = 30
OAUTH2_CLIENT_ID: str = '2882303761520251711'
OAUTH2_AUTH_URL: str = 'https://account.xiaomi.com/oauth2/authorize'
DEFAULT_OAUTH2_API_HOST: str = 'ha.api.io.mi.com'
# seconds, 14 days
SPEC_STD_LIB_EFFECTIVE_TIME = 3600*24*14
# seconds, 14 days
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',
'text',
'vacuum',
'water_heater',
]
DEFAULT_CLOUD_SERVER: str = 'cn'
CLOUD_SERVERS: dict = {
'cn': '中国大陆',
'de': 'Europe',
'i2': 'India',
'ru': 'Russia',
'sg': 'Singapore',
'us': 'United States'
}
SUPPORT_CENTRAL_GATEWAY_CTRL: list = ['cn']
DEFAULT_INTEGRATION_LANGUAGE: str = 'en'
INTEGRATION_LANGUAGES = {
'zh-Hans': '简体中文',
'zh-Hant': '繁體中文',
'en': 'English',
'es': 'Español',
'ru': 'Русский',
'fr': 'Français',
'de': 'Deutsch',
'ja': '日本語'
}
DEFAULT_CTRL_MODE: str = 'auto'
# Registered in Xiaomi OAuth 2.0 Service
# DO NOT CHANGE UNLESS YOU HAVE AN ADMINISTRATOR PERMISSION
OAUTH_REDIRECT_URL: str = 'http://homeassistant.local:8123'
MIHOME_CA_CERT_STR: str = '-----BEGIN CERTIFICATE-----\n' \
'MIIBazCCAQ+gAwIBAgIEA/UKYDAMBggqhkjOPQQDAgUAMCIxEzARBgNVBAoTCk1p\n' \
'amlhIFJvb3QxCzAJBgNVBAYTAkNOMCAXDTE2MTEyMzAxMzk0NVoYDzIwNjYxMTEx\n' \
'MDEzOTQ1WjAiMRMwEQYDVQQKEwpNaWppYSBSb290MQswCQYDVQQGEwJDTjBZMBMG\n' \
'ByqGSM49AgEGCCqGSM49AwEHA0IABL71iwLa4//4VBqgRI+6xE23xpovqPCxtv96\n' \
'2VHbZij61/Ag6jmi7oZ/3Xg/3C+whglcwoUEE6KALGJ9vccV9PmjLzAtMAwGA1Ud\n' \
'EwQFMAMBAf8wHQYDVR0OBBYEFJa3onw5sblmM6n40QmyAGDI5sURMAwGCCqGSM49\n' \
'BAMCBQADSAAwRQIgchciK9h6tZmfrP8Ka6KziQ4Lv3hKfrHtAZXMHPda4IYCIQCG\n' \
'az93ggFcbrG9u2wixjx1HKW4DUA5NXZG0wWQTpJTbQ==\n' \
'-----END CERTIFICATE-----\n' \
'-----BEGIN CERTIFICATE-----\n' \
'MIIBjzCCATWgAwIBAgIBATAKBggqhkjOPQQDAjAiMRMwEQYDVQQKEwpNaWppYSBS\n' \
'b290MQswCQYDVQQGEwJDTjAgFw0yMjA2MDkxNDE0MThaGA8yMDcyMDUyNzE0MTQx\n' \
'OFowLDELMAkGA1UEBhMCQ04xHTAbBgNVBAoMFE1JT1QgQ0VOVFJBTCBHQVRFV0FZ\n' \
'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdYrzbnp/0x/cZLZnuEDXTFf8mhj4\n' \
'CVpZPwgj9e9Ve5r3K7zvu8Jjj7JF1JjQYvEC6yhp1SzBgglnK4L8xQzdiqNQME4w\n' \
'HQYDVR0OBBYEFCf9+YBU7pXDs6K6CAQPRhlGJ+cuMB8GA1UdIwQYMBaAFJa3onw5\n' \
'sblmM6n40QmyAGDI5sURMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIh\n' \
'AKUv+c8v98vypkGMTzMwckGjjVqTef8xodsy6PhcSCq+AiA/n9mDs62hAo5zXyJy\n' \
'Bs1s7mqXPf1XgieoxIvs1MqyiA==\n' \
'-----END CERTIFICATE-----\n'
MIHOME_CA_CERT_SHA256: str = \
'8b7bf306be3632e08b0ead308249e5f2b2520dc921ad143872d5fcc7c68d6759'

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "Geräte",
"found_central_gateway": ", lokales zentrales Gateway gefunden"
},
"control_mode": {
"auto": "automatisch",
"cloud": "Cloud"
},
"room_name_rule": {
"none": "nicht synchronisieren",
"home_room": "Hausname und Raumname (Xiaomi Home Schlafzimmer)",
"room": "Raumname (Schlafzimmer)",
"home": "Hausname (Xiaomi Home)"
},
"option_status": {
"enable": "aktivieren",
"disable": "deaktivieren"
},
"lan_ctrl_config": {
"notice_net_dup": "\r\n**[Hinweis]** Es wurden mehrere Netzwerkkarten erkannt, die möglicherweise mit demselben Netzwerk verbunden sind. Bitte achten Sie auf die Auswahl.",
"net_unavailable": "Schnittstelle nicht verfügbar"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Ungültige Authentifizierungsinformationen, Cloud-Verbindung nicht verfügbar, bitte betreten Sie die Xiaomi Home-Integrationsseite und klicken Sie auf 'Optionen', um die Authentifizierung erneut durchzuführen",
"invalid_device_cache": "Ungültige Gerätecache-Informationen, bitte betreten Sie die Xiaomi Home-Integrationsseite und klicken Sie auf 'Optionen->Geräteliste aktualisieren', um den lokalen Gerätecache zu aktualisieren",
"invalid_cert_info": "Ungültiges Benutzerzertifikat, lokale zentrale Verbindung nicht verfügbar, bitte betreten Sie die Xiaomi Home-Integrationsseite und klicken Sie auf 'Optionen', um die Authentifizierung erneut durchzuführen",
"device_cloud_error": "Fehler beim Abrufen von Geräteinformationen aus der Cloud, bitte überprüfen Sie die lokale Netzwerkverbindung",
"xiaomi_home_error_title": "Xiaomi Home-Integrationsfehler",
"xiaomi_home_error": "Fehler **{nick_name}({uid}, {cloud_server})** festgestellt, bitte betreten Sie die Optionen-Seite, um die Konfiguration erneut durchzuführen.\n\n**Fehlermeldung**: \n{message}",
"device_list_changed_title": "Xiaomi Home-Geräteliste geändert",
"device_list_changed": "Änderung der Geräteinformationen **{nick_name}({uid}, {cloud_server})** festgestellt, bitte betreten Sie die Integrations-Optionen-Seite, klicken Sie auf 'Optionen->Geräteliste aktualisieren', um den lokalen Gerätecache zu aktualisieren.\n\nAktueller Netzwerkstatus: {network_status}\n{message}\n",
"device_list_add": "\n**{count} neue Geräte:** \n{message}",
"device_list_del": "\n**{count} Geräte nicht verfügbar:** \n{message}",
"device_list_offline": "\n**{count} Geräte offline:** \n{message}",
"network_status_online": "Online",
"network_status_offline": "Offline",
"device_exec_error": "Fehler bei der Ausführung"
}
},
"error": {
"common": {
"-10000": "Unbekannter Fehler",
"-10001": "Dienst nicht verfügbar",
"-10002": "Ungültiger Parameter",
"-10003": "Unzureichende Ressourcen",
"-10004": "Interner Fehler",
"-10005": "Unzureichende Berechtigungen",
"-10006": "Ausführungszeitüberschreitung",
"-10007": "Gerät offline oder nicht vorhanden",
"-10020": "Nicht autorisiert (OAuth2)",
"-10030": "Ungültiges Token (HTTP)",
"-10040": "Ungültiges Nachrichtenformat",
"-10050": "Ungültiges Zertifikat",
"-704000000": "Unbekannter Fehler",
"-704010000": "Nicht autorisiert (Gerät wurde möglicherweise gelöscht)",
"-704014006": "Gerätebeschreibung nicht gefunden",
"-704030013": "Eigenschaft nicht lesbar",
"-704030023": "Eigenschaft nicht beschreibbar",
"-704030033": "Eigenschaft nicht abonnierbar",
"-704040002": "Dienst existiert nicht",
"-704040003": "Eigenschaft existiert nicht",
"-704040004": "Ereignis existiert nicht",
"-704040005": "Aktion existiert nicht",
"-704040999": "Funktion nicht online",
"-704042001": "Gerät existiert nicht",
"-704042011": "Gerät offline",
"-704053036": "Gerätebetrieb zeitüberschreitung",
"-704053100": "Gerät kann diese Operation im aktuellen Zustand nicht ausführen",
"-704083036": "Gerätebetrieb zeitüberschreitung",
"-704090001": "Gerät existiert nicht",
"-704220008": "Ungültige ID",
"-704220025": "Aktionsparameteranzahl stimmt nicht überein",
"-704220035": "Aktionsparameterfehler",
"-704220043": "Eigenschaftswertfehler",
"-704222034": "Aktionsrückgabewertfehler",
"-705004000": "Unbekannter Fehler",
"-705004501": "Unbekannter Fehler",
"-705201013": "Eigenschaft nicht lesbar",
"-705201015": "Aktionsausführungsfehler",
"-705201023": "Eigenschaft nicht beschreibbar",
"-705201033": "Eigenschaft nicht abonnierbar",
"-706012000": "Unbekannter Fehler",
"-706012013": "Eigenschaft nicht lesbar",
"-706012015": "Aktionsausführungsfehler",
"-706012023": "Eigenschaft nicht beschreibbar",
"-706012033": "Eigenschaft nicht abonnierbar",
"-706012043": "Eigenschaftswertfehler",
"-706014006": "Gerätebeschreibung nicht gefunden"
}
}
}

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "Devices",
"found_central_gateway": ", Found Local Central Hub Gateway"
},
"control_mode": {
"auto": "Auto",
"cloud": "Cloud"
},
"room_name_rule": {
"none": "Do not synchronize",
"home_room": "Home Name and Room Name (Xiaomi Home Bedroom)",
"room": "Room Name (Bedroom)",
"home": "Home Name (Xiaomi Home)"
},
"option_status": {
"enable": "Enable",
"disable": "Disable"
},
"lan_ctrl_config": {
"notice_net_dup": "\r\n**[Notice]** Multiple network cards detected that may be connected to the same network. Please pay attention to the selection.",
"net_unavailable": "Interface unavailable"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Authentication information is invalid, cloud link will be unavailable, please enter the Xiaomi Home integration page, click 'Options' to re-authenticate",
"invalid_device_cache": "Cache device information is abnormal, please enter the Xiaomi Home integration page, click 'Options->Update device list', update the local cache",
"invalid_cert_info": "Invalid user certificate, local central link will be unavailable, please enter the Xiaomi Home integration page, click 'Options' to re-authenticate",
"device_cloud_error": "An exception occurred when obtaining device information from the cloud, please check the local network connection",
"xiaomi_home_error_title": "Xiaomi Home Integration Error",
"xiaomi_home_error": "Detected **{nick_name}({uid}, {cloud_server})** error, please enter the options page to reconfigure.\n\n**Error message**: \n{message}",
"device_list_changed_title": "Xiaomi Home device list changes",
"device_list_changed": "Detected **{nick_name}({uid}, {cloud_server})** device information has changed, please enter the integration options page, click `Options->Update device list`, update local device information.\n\nCurrent network status: {network_status}\n{message}\n",
"device_list_add": "\n**{count} new devices:** \n{message}",
"device_list_del": "\n**{count} devices unavailable:** \n{message}",
"device_list_offline": "\n**{count} devices offline:** \n{message}",
"network_status_online": "Online",
"network_status_offline": "Offline",
"device_exec_error": "Execution error"
}
},
"error": {
"common": {
"-10000": "Unknown error",
"-10001": "Service unavailable",
"-10002": "Invalid parameter",
"-10003": "Insufficient resources",
"-10004": "Internal error",
"-10005": "Insufficient permissions",
"-10006": "Execution timeout",
"-10007": "Device offline or does not exist",
"-10020": "Unauthorized (OAuth2)",
"-10030": "Invalid token (HTTP)",
"-10040": "Invalid message format",
"-10050": "Invalid certificate",
"-704000000": "Unknown error",
"-704010000": "Unauthorized (device may have been deleted)",
"-704014006": "Device description not found",
"-704030013": "Property not readable",
"-704030023": "Property not writable",
"-704030033": "Property not subscribable",
"-704040002": "Service does not exist",
"-704040003": "Property does not exist",
"-704040004": "Event does not exist",
"-704040005": "Action does not exist",
"-704040999": "Feature not online",
"-704042001": "Device does not exist",
"-704042011": "Device offline",
"-704053036": "Device operation timeout",
"-704053100": "Device cannot perform this operation in the current state",
"-704083036": "Device operation timeout",
"-704090001": "Device does not exist",
"-704220008": "Invalid ID",
"-704220025": "Action parameter count mismatch",
"-704220035": "Action parameter error",
"-704220043": "Property value error",
"-704222034": "Action return value error",
"-705004000": "Unknown error",
"-705004501": "Unknown error",
"-705201013": "Property not readable",
"-705201015": "Action execution error",
"-705201023": "Property not writable",
"-705201033": "Property not subscribable",
"-706012000": "Unknown error",
"-706012013": "Property not readable",
"-706012015": "Action execution error",
"-706012023": "Property not writable",
"-706012033": "Property not subscribable",
"-706012043": "Property value error",
"-706014006": "Device description not found"
}
}
}

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "dispositivos",
"found_central_gateway": ", se encontró la puerta de enlace central local"
},
"control_mode": {
"auto": "automático",
"cloud": "nube"
},
"room_name_rule": {
"none": "no sincronizar",
"home_room": "nombre de la casa y nombre de la habitación (Xiaomi Home Dormitorio)",
"room": "nombre de la habitación (Dormitorio)",
"home": "nombre de la casa (Xiaomi Home)"
},
"option_status": {
"enable": "habilitar",
"disable": "deshabilitar"
},
"lan_ctrl_config": {
"notice_net_dup": "\r\n**[Aviso]** Se detectaron varias tarjetas de red que pueden estar conectadas a la misma red. Por favor, preste atención a la selección.",
"net_unavailable": "Interfaz no disponible"
}
},
"miot": {
"client": {
"invalid_oauth_info": "La información de autenticación es inválida, la conexión en la nube no estará disponible, por favor, vaya a la página de integración de Xiaomi Home, haga clic en 'Opciones' para volver a autenticar",
"invalid_device_cache": "La información de caché del dispositivo es anormal, por favor, vaya a la página de integración de Xiaomi Home, haga clic en 'Opciones -> Actualizar lista de dispositivos' para actualizar la información del dispositivo local",
"invalid_cert_info": "Certificado de usuario inválido, la conexión del centro local no estará disponible, por favor, vaya a la página de integración de Xiaomi Home, haga clic en 'Opciones' para volver a autenticar",
"device_cloud_error": "Error al obtener la información del dispositivo desde la nube, por favor, compruebe la conexión de red local",
"xiaomi_home_error_title": "Error de integración de Xiaomi Home",
"xiaomi_home_error": "Se detectó un error en **{nick_name}({uid}, {cloud_server})**, por favor, vaya a la página de opciones para reconfigurar.\n\n**Mensaje de error**: \n{message}",
"device_list_changed_title": "Cambio en la lista de dispositivos de Xiaomi Home",
"device_list_changed": "Se detectó un cambio en la información del dispositivo **{nick_name}({uid}, {cloud_server})**, por favor, vaya a la página de integración, haga clic en 'Opciones -> Actualizar lista de dispositivos' para actualizar la información del dispositivo local.\n\nEstado actual de la red: {network_status}\n{message}\n",
"device_list_add": "\n**{count} nuevos dispositivos:** \n{message}",
"device_list_del": "\n**{count} dispositivos no disponibles:** \n{message}",
"device_list_offline": "\n**{count} dispositivos sin conexión:** \n{message}",
"network_status_online": "En línea",
"network_status_offline": "Desconectado",
"device_exec_error": "Error de ejecución"
}
},
"error": {
"common": {
"-10000": "Error desconocido",
"-10001": "Servicio no disponible",
"-10002": "Parámetro inválido",
"-10003": "Recursos insuficientes",
"-10004": "Error interno",
"-10005": "Permisos insuficientes",
"-10006": "Tiempo de ejecución agotado",
"-10007": "Dispositivo fuera de línea o no existe",
"-10020": "No autorizado (OAuth2)",
"-10030": "Token inválido (HTTP)",
"-10040": "Formato de mensaje inválido",
"-10050": "Certificado inválido",
"-704000000": "Error desconocido",
"-704010000": "No autorizado (el dispositivo puede haber sido eliminado)",
"-704014006": "Descripción del dispositivo no encontrada",
"-704030013": "Propiedad no legible",
"-704030023": "Propiedad no escribible",
"-704030033": "Propiedad no suscribible",
"-704040002": "Servicio no existe",
"-704040003": "Propiedad no existe",
"-704040004": "Evento no existe",
"-704040005": "Acción no existe",
"-704040999": "Función no en línea",
"-704042001": "Dispositivo no existe",
"-704042011": "Dispositivo fuera de línea",
"-704053036": "Tiempo de operación del dispositivo agotado",
"-704053100": "El dispositivo no puede realizar esta operación en el estado actual",
"-704083036": "Tiempo de operación del dispositivo agotado",
"-704090001": "Dispositivo no existe",
"-704220008": "ID inválido",
"-704220025": "Número de parámetros de acción no coincide",
"-704220035": "Error de parámetro de acción",
"-704220043": "Error de valor de propiedad",
"-704222034": "Error de valor de retorno de acción",
"-705004000": "Error desconocido",
"-705004501": "Error desconocido",
"-705201013": "Propiedad no legible",
"-705201015": "Error de ejecución de acción",
"-705201023": "Propiedad no escribible",
"-705201033": "Propiedad no suscribible",
"-706012000": "Error desconocido",
"-706012013": "Propiedad no legible",
"-706012015": "Error de ejecución de acción",
"-706012023": "Propiedad no escribible",
"-706012033": "Propiedad no suscribible",
"-706012043": "Error de valor de propiedad",
"-706014006": "Descripción del dispositivo no encontrada"
}
}
}

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "appareils",
"found_central_gateway": ", passerelle centrale locale trouvée"
},
"control_mode": {
"auto": "automatique",
"cloud": "cloud"
},
"room_name_rule": {
"none": "ne pas synchroniser",
"home_room": "nom de la maison et nom de la pièce (Xiaomi Home Chambre)",
"room": "nom de la pièce (Chambre)",
"home": "nom de la maison (Xiaomi Home)"
},
"option_status": {
"enable": "activer",
"disable": "désactiver"
},
"lan_ctrl_config": {
"notice_net_dup": "\r\n**[Remarque]** Plusieurs cartes réseau détectées qui peuvent être connectées au même réseau. Veuillez faire attention à la sélection.",
"net_unavailable": "Interface non disponible"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Informations d'authentification non valides, le lien cloud ne sera pas disponible, veuillez accéder à la page d'intégration Xiaomi Home, cliquez sur \"Options\" pour vous réauthentifier",
"invalid_device_cache": "Informations de cache de périphérique non valides, veuillez accéder à la page d'intégration Xiaomi Home, cliquez sur `Options-> Mettre à jour la liste des appareils`, pour mettre à jour les informations locales des appareils",
"invalid_cert_info": "Certificat utilisateur non valide, le lien central local ne sera pas disponible, veuillez accéder à la page d'intégration Xiaomi Home, cliquez sur \"Options\" pour vous réauthentifier",
"device_cloud_error": "Erreur lors de la récupération des informations de l'appareil à partir du cloud, veuillez vérifier la connexion réseau locale",
"xiaomi_home_error_title": "Erreur d'intégration Xiaomi Home",
"xiaomi_home_error": "Erreur détectée sur **{nick_name}({uid}, {cloud_server})**, veuillez accéder à la page d'options pour reconfigurer.\n\n**Message d'erreur**: \n{message}",
"device_list_changed_title": "Changements dans la liste des appareils Xiaomi Home",
"device_list_changed": "Changements détectés sur **{nick_name}({uid}, {cloud_server})**, veuillez accéder à la page d'intégration, cliquez sur `Options-> Mettre à jour la liste des appareils`, pour mettre à jour les informations locales des appareils.\n\nÉtat actuel du réseau : {network_status}\n{message}\n",
"device_list_add": "\n**{count} nouveaux appareils :** \n{message}",
"device_list_del": "\n**{count} appareils non disponibles :** \n{message}",
"device_list_offline": "\n**{count} appareils hors ligne :** \n{message}",
"network_status_online": "En ligne",
"network_status_offline": "Hors ligne",
"device_exec_error": "Erreur d'exécution"
}
},
"error": {
"common": {
"-10000": "Erreur inconnue",
"-10001": "Service indisponible",
"-10002": "Paramètre invalide",
"-10003": "Ressources insuffisantes",
"-10004": "Erreur interne",
"-10005": "Permissions insuffisantes",
"-10006": "Délai d'exécution dépassé",
"-10007": "Appareil hors ligne ou n'existe pas",
"-10020": "Non autorisé (OAuth2)",
"-10030": "Jeton invalide (HTTP)",
"-10040": "Format de message invalide",
"-10050": "Certificat invalide",
"-704000000": "Erreur inconnue",
"-704010000": "Non autorisé (l'appareil peut avoir été supprimé)",
"-704014006": "Description de l'appareil introuvable",
"-704030013": "Propriété non lisible",
"-704030023": "Propriété non inscriptible",
"-704030033": "Propriété non abonnable",
"-704040002": "Service n'existe pas",
"-704040003": "Propriété n'existe pas",
"-704040004": "Événement n'existe pas",
"-704040005": "Action n'existe pas",
"-704040999": "Fonction non en ligne",
"-704042001": "Appareil n'existe pas",
"-704042011": "Appareil hors ligne",
"-704053036": "Délai d'opération de l'appareil dépassé",
"-704053100": "L'appareil ne peut pas effectuer cette opération dans l'état actuel",
"-704083036": "Délai d'opération de l'appareil dépassé",
"-704090001": "Appareil n'existe pas",
"-704220008": "ID invalide",
"-704220025": "Nombre de paramètres d'action ne correspond pas",
"-704220035": "Erreur de paramètre d'action",
"-704220043": "Erreur de valeur de propriété",
"-704222034": "Erreur de valeur de retour d'action",
"-705004000": "Erreur inconnue",
"-705004501": "Erreur inconnue",
"-705201013": "Propriété non lisible",
"-705201015": "Erreur d'exécution d'action",
"-705201023": "Propriété non inscriptible",
"-705201033": "Propriété non abonnable",
"-706012000": "Erreur inconnue",
"-706012013": "Propriété non lisible",
"-706012015": "Erreur d'exécution d'action",
"-706012023": "Propriété non inscriptible",
"-706012033": "Propriété non abonnable",
"-706012043": "Erreur de valeur de propriété",
"-706014006": "Description de l'appareil introuvable"
}
}
}

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "デバイス",
"found_central_gateway": "、ローカル中央ゲートウェイが見つかりました"
},
"control_mode": {
"auto": "自動",
"cloud": "クラウド"
},
"room_name_rule": {
"none": "同期しない",
"home_room": "家の名前と部屋の名前 Xiaomi Home 寝室)",
"room": "部屋の名前(寝室)",
"home": "家の名前 Xiaomi Home"
},
"option_status": {
"enable": "有効",
"disable": "無効"
},
"lan_ctrl_config": {
"notice_net_dup": "\r\n**[注意]** 複数のネットワークカードが同じネットワークに接続されている可能性があります。選択に注意してください。",
"net_unavailable": "インターフェースが利用できません"
}
},
"miot": {
"client": {
"invalid_oauth_info": "認証情報が無効です。クラウドリンクは利用できません。Xiaomi Home統合ページに入り、[オプション]をクリックして再認証してください",
"invalid_device_cache": "キャッシュデバイス情報が異常です。Xiaomi Home統合ページに入り、[オプション->デバイスリストの更新]をクリックして、ローカルキャッシュを更新してください",
"invalid_cert_info": "無効なユーザー証明書です。ローカルセントラルリンクは利用できません。Xiaomi Home統合ページに入り、[オプション]をクリックして再認証してください",
"device_cloud_error": "クラウドからデバイス情報を取得する際に例外が発生しました。ローカルネットワーク接続を確認してください",
"xiaomi_home_error_title": "Xiaomi Home統合エラー",
"xiaomi_home_error": "エラーが検出されました **{nick_name}({uid}, {cloud_server})** 、オプションページに入り再構成してください。\n\n**エラーメッセージ**: \n{message}",
"device_list_changed_title": "Xiaomi Homeデバイスリストの変更",
"device_list_changed": "変更が検出されました **{nick_name}({uid}, {cloud_server})** デバイス情報が変更されました。統合オプションページに入り、`オプション->デバイスリストの更新`をクリックして、ローカルデバイス情報を更新してください。\n\n現在のネットワーク状態{network_status}\n{message}\n",
"device_list_add": "\n**{count} 新しいデバイス:** \n{message}",
"device_list_del": "\n**{count} デバイスが利用できません:** \n{message}",
"device_list_offline": "\n**{count} デバイスがオフライン:** \n{message}",
"network_status_online": "オンライン",
"network_status_offline": "オフライン",
"device_exec_error": "実行エラー"
}
},
"error": {
"common": {
"-10000": "不明なエラー",
"-10001": "サービス利用不可",
"-10002": "無効なパラメータ",
"-10003": "リソース不足",
"-10004": "内部エラー",
"-10005": "権限不足",
"-10006": "実行タイムアウト",
"-10007": "デバイスがオフラインまたは存在しない",
"-10020": "未認証OAuth2",
"-10030": "無効なトークンHTTP",
"-10040": "無効なメッセージ形式",
"-10050": "無効な証明書",
"-704000000": "不明なエラー",
"-704010000": "未認証(デバイスが削除された可能性があります)",
"-704014006": "デバイスの説明が見つかりません",
"-704030013": "プロパティが読み取れません",
"-704030023": "プロパティが書き込めません",
"-704030033": "プロパティが購読できません",
"-704040002": "サービスが存在しません",
"-704040003": "プロパティが存在しません",
"-704040004": "イベントが存在しません",
"-704040005": "アクションが存在しません",
"-704040999": "機能がオンラインではありません",
"-704042001": "デバイスが存在しません",
"-704042011": "デバイスがオフラインです",
"-704053036": "デバイス操作タイムアウト",
"-704053100": "デバイスが現在の状態でこの操作を実行できません",
"-704083036": "デバイス操作タイムアウト",
"-704090001": "デバイスが存在しません",
"-704220008": "無効なID",
"-704220025": "アクションパラメータの数が一致しません",
"-704220035": "アクションパラメータエラー",
"-704220043": "プロパティ値エラー",
"-704222034": "アクションの戻り値エラー",
"-705004000": "不明なエラー",
"-705004501": "不明なエラー",
"-705201013": "プロパティが読み取れません",
"-705201015": "アクション実行エラー",
"-705201023": "プロパティが書き込めません",
"-705201033": "プロパティが購読できません",
"-706012000": "不明なエラー",
"-706012013": "プロパティが読み取れません",
"-706012015": "アクション実行エラー",
"-706012023": "プロパティが書き込めません",
"-706012033": "プロパティが購読できません",
"-706012043": "プロパティ値エラー",
"-706014006": "デバイスの説明が見つかりません"
}
}
}

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "устройства",
"found_central_gateway": ", найден локальный центральный шлюз"
},
"control_mode": {
"auto": "автоматический",
"cloud": "облако"
},
"room_name_rule": {
"none": "не синхронизировать",
"home_room": "название дома и название комнаты (Xiaomi Home Спальня)",
"room": "название комнаты (Спальня)",
"home": "название дома (Xiaomi Home)"
},
"option_status": {
"enable": "Включить",
"disable": "Отключить"
},
"lan_ctrl_config": {
"notice_net_dup": "\r\n**[Уведомление]** Обнаружено несколько сетевых карт, которые могут быть подключены к одной и той же сети. Пожалуйста, обратите внимание на выбор.",
"net_unavailable": "Интерфейс недоступен"
}
},
"miot": {
"client": {
"invalid_oauth_info": "Информация об аутентификации недействительна, облако будет недоступно, пожалуйста, войдите на страницу интеграции Xiaomi Home, нажмите 'Опции' для повторной аутентификации",
"invalid_device_cache": "Кэш информации об устройстве ненормальный, пожалуйста, войдите на страницу интеграции Xiaomi Home, нажмите 'Опции->Обновить список устройств', обновите локальный кэш",
"invalid_cert_info": "Недействительный пользовательский сертификат, локальное центральное соединение будет недоступно, пожалуйста, войдите на страницу интеграции Xiaomi Home, нажмите 'Опции' для повторной аутентификации",
"device_cloud_error": "При получении информации об устройстве из облака произошло исключение, пожалуйста, проверьте локальное сетевое соединение",
"xiaomi_home_error_title": "Ошибка интеграции Xiaomi Home",
"xiaomi_home_error": "Обнаружена ошибка **{nick_name}({uid}, {cloud_server})**, пожалуйста, войдите на страницу опций для повторной настройки.\n\n**Сообщение об ошибке**: \n{message}",
"device_list_changed_title": "Изменения в списке устройств Xiaomi Home",
"device_list_changed": "Обнаружены изменения в информации об устройствах **{nick_name}({uid}, {cloud_server})**, пожалуйста, войдите на страницу интеграции, нажмите `Опции->Обновить список устройств`, обновите локальную информацию об устройствах.\n\nТекущий статус сети: {network_status}\n{message}\n",
"device_list_add": "\n**{count} новых устройств:** \n{message}",
"device_list_del": "\n**{count} устройств недоступно:** \n{message}",
"device_list_offline": "\n**{count} устройств недоступно:** \n{message}",
"network_status_online": "В сети",
"network_status_offline": "Не в сети",
"device_exec_error": "Ошибка выполнения"
}
},
"error": {
"common": {
"-10000": "Неизвестная ошибка",
"-10001": "Сервис недоступен",
"-10002": "Недопустимый параметр",
"-10003": "Недостаточно ресурсов",
"-10004": "Внутренняя ошибка",
"-10005": "Недостаточно прав",
"-10006": "Тайм-аут выполнения",
"-10007": "Устройство не в сети или не существует",
"-10020": "Неавторизовано (OAuth2)",
"-10030": "Недействительный токен (HTTP)",
"-10040": "Недопустимый формат сообщения",
"-10050": "Недействительный сертификат",
"-704000000": "Неизвестная ошибка",
"-704010000": "Неавторизовано (устройство могло быть удалено)",
"-704014006": "Описание устройства не найдено",
"-704030013": "Свойство не читается",
"-704030023": "Свойство не записывается",
"-704030033": "Свойство не подписывается",
"-704040002": "Сервис не существует",
"-704040003": "Свойство не существует",
"-704040004": "Событие не существует",
"-704040005": "Действие не существует",
"-704040999": "Функция не в сети",
"-704042001": "Устройство не существует",
"-704042011": "Устройство не в сети",
"-704053036": "Тайм-аут операции устройства",
"-704053100": "Устройство не может выполнить эту операцию в текущем состоянии",
"-704083036": "Тайм-аут операции устройства",
"-704090001": "Устройство не существует",
"-704220008": "Недействительный ID",
"-704220025": "Несоответствие количества параметров действия",
"-704220035": "Ошибка параметра действия",
"-704220043": "Ошибка значения свойства",
"-704222034": "Ошибка возвращаемого значения действия",
"-705004000": "Неизвестная ошибка",
"-705004501": "Неизвестная ошибка",
"-705201013": "Свойство не читается",
"-705201015": "Ошибка выполнения действия",
"-705201023": "Свойство не записывается",
"-705201033": "Свойство не подписывается",
"-706012000": "Неизвестная ошибка",
"-706012013": "Свойство не читается",
"-706012015": "Ошибка выполнения действия",
"-706012023": "Свойство не записывается",
"-706012033": "Свойство не подписывается",
"-706012043": "Ошибка значения свойства",
"-706014006": "Описание устройства не найдено"
}
}
}

View File

@ -0,0 +1,95 @@
{
"config": {
"other": {
"devices": "个设备",
"found_central_gateway": ",发现本地中枢网关"
},
"control_mode": {
"auto": "自动",
"cloud": "云端"
},
"room_name_rule": {
"none": "不同步",
"home_room": "家庭名 和 房间名 (米家 卧室)",
"room": "房间名 (卧室)",
"home": "家庭名 (米家)"
},
"option_status": {
"enable": "启用",
"disable": "禁用"
},
"lan_ctrl_config": {
"notice_net_dup": "\r\n**[提示]** 检测到多个网卡可能连接同一个网络,请注意选择。",
"net_unavailable": "接口不可用"
}
},
"miot": {
"client": {
"invalid_oauth_info": "认证信息失效,云端链路将不可用,请进入 Xiaomi Home 集成页面,点击“选项”重新认证",
"invalid_device_cache": "缓存设备信息异常,请进入 Xiaomi Home 集成页面,点击`选项->更新设备列表`,更新本地设备信息",
"invalid_cert_info": "无效的用户证书,本地中枢链路将不可用,请进入 Xiaomi Home 集成页面,点击“选项”重新认证",
"device_cloud_error": "从云端获取设备信息异常,请检查本地网络连接",
"xiaomi_home_error_title": "Xiaomi Home 集成错误",
"xiaomi_home_error": "检测到 **{nick_name}({uid}, {cloud_server})** 出现错误,请进入选项页面重新配置。\n\n**错误信息**: \n{message}",
"device_list_changed_title": "Xiaomi Home设备列表变化",
"device_list_changed": "检测到 **{nick_name}({uid}, {cloud_server})** 设备信息发生变化,请进入集成选项页面,点击`选项->更新设备列表`,更新本地设备信息。\n\n当前网络状态{network_status}\n{message}\n",
"device_list_add": "\n**{count} 个新增设备**: \n{message}",
"device_list_del": "\n**{count} 个设备不可用**: \n{message}",
"device_list_offline": "\n**{count} 个设备离线**: \n{message}",
"network_status_online": "在线",
"network_status_offline": "离线",
"device_exec_error": "执行错误"
}
},
"error": {
"common": {
"-10000": "未知错误",
"-10001": "服务不可用",
"-10002": "参数无效",
"-10003": "资源不足",
"-10004": "内部错误",
"-10005": "权限不足",
"-10006": "执行超时",
"-10007": "设备离线或者不存在",
"-10020": "未授权OAuth2",
"-10030": "无效的tokenHTTP",
"-10040": "无效的消息格式",
"-10050": "无效的证书",
"-704000000": "未知错误",
"-704010000": "未授权(设备可能被删除)",
"-704014006": "没找到设备描述",
"-704030013": "Property不可读",
"-704030023": "Property不可写",
"-704030033": "Property不可订阅",
"-704040002": "Service不存在",
"-704040003": "Property不存在",
"-704040004": "Event不存在",
"-704040005": "Action不存在",
"-704040999": "功能未上线",
"-704042001": "Device不存在",
"-704042011": "设备离线",
"-704053036": "设备操作超时",
"-704053100": "设备在当前状态下无法执行此操作",
"-704083036": "设备操作超时",
"-704090001": "Device不存在",
"-704220008": "无效的ID",
"-704220025": "Action参数个数不匹配",
"-704220035": "Action参数错误",
"-704220043": "Property值错误",
"-704222034": "Action返回值错误",
"-705004000": "未知错误",
"-705004501": "未知错误",
"-705201013": "Property不可读",
"-705201015": "Action执行错误",
"-705201023": "Property不可写",
"-705201033": "Property不可订阅",
"-706012000": "未知错误",
"-706012013": "Property不可读",
"-706012015": "Action执行错误",
"-706012023": "Property不可写",
"-706012033": "Property不可订阅",
"-706012043": "Property值错误",
"-706014006": "没找到设备描述"
}
}
}

View File

@ -0,0 +1,97 @@
{
"config": {
"other": {
"devices": "個設備",
"found_central_gateway": ",發現本地中樞網關"
},
"control_mode": {
"auto": "自動",
"cloud": "雲端"
},
"room_name_rule": {
"none": "不同步",
"home_room": "家庭名 和 房間名 (米家 臥室)",
"room": "房間名 (臥室)",
"home": "家庭名 (米家)"
},
"option_status": {
"enable": "啟用",
"disable": "禁用"
},
"lan_ctrl_config": {
"notice_net_dup": "\r\n**[提示]** 檢測到多個網卡可能連接同一個網絡,請注意選擇。",
"net_unavailable": "接口不可用"
}
},
"miot": {
"client": {
"invalid_oauth_info": "認證信息失效,雲端鏈路將不可用,請進入 Xiaomi Home 集成頁面,點擊“選項”重新認證",
"invalid_device_cache": "緩存設備信息異常,請進入 Xiaomi Home 集成頁面,點擊`選項->更新設備列表`,更新本地設備信息",
"invalid_cert_info": "無效的用戶證書,本地中樞鏈路將不可用,請進入 Xiaomi Home 集成頁面,點擊“選項”重新認證",
"device_cloud_error": "從雲端獲取設備信息異常,請檢查本地網絡連接",
"xiaomi_home_error_title": "Xiaomi Home 集成錯誤",
"xiaomi_home_error": "檢測到 **{nick_name}({uid}, {cloud_server})** 出現錯誤,請進入選項頁面重新配置。\n\n**錯誤信息**: \n{message}",
"device_list_changed_title": "Xiaomi Home設備列表變化",
"device_list_changed": "檢測到 **{nick_name}({uid}, {cloud_server})** 設備信息發生變化,請進入集成選項頁面,點擊`選項->更新設備列表`,更新本地設備信息。\n\n當前網絡狀態{network_status}\n{message}\n",
"device_list_add": "\n**{count} 個新增設備:** \n{message}",
"device_list_del": "\n**{count} 個設備不可用:** \n{message}",
"device_list_offline": "\n**{count} 個設備離線:** \n{message}",
"network_status_online": "在線",
"network_status_offline": "離線",
"device_exec_error": "執行錯誤"
}
},
"error": {
"common": {
"-1": "未知錯誤",
"-10000": "未知錯誤",
"-10001": "服務不可用",
"-10002": "無效參數",
"-10003": "資源不足",
"-10004": "內部錯誤",
"-10005": "權限不足",
"-10006": "執行超時",
"-10007": "設備離線或者不存在",
"-10020": "無效的消息格式"
},
"gw": {},
"lan": {},
"cloud": {
"-704000000": "未知錯誤",
"-704010000": "未授權(設備可能被刪除)",
"-704014006": "沒找到設備描述",
"-704030013": "Property不可讀",
"-704030023": "Property不可寫",
"-704030033": "Property不可訂閱",
"-704040002": "Service不存在",
"-704040003": "Property不存在",
"-704040004": "Event不存在",
"-704040005": "Action不存在",
"-704040999": "功能未上線",
"-704042001": "Device不存在",
"-704042011": "設備離線",
"-704053036": "設備操作超時",
"-704053100": "設備在當前狀態下無法執行此操作",
"-704083036": "設備操作超時",
"-704090001": "Device不存在",
"-704220008": "無效的ID",
"-704220025": "Action參數個數不匹配",
"-704220035": "Action參數錯誤",
"-704220043": "Property值錯誤",
"-704222034": "Action返回值錯誤",
"-705004000": "未知錯誤",
"-705004501": "未知錯誤",
"-705201013": "Property不可讀",
"-705201015": "Action執行錯誤",
"-705201023": "Property不可寫",
"-705201033": "Property不可訂閱",
"-706012000": "未知錯誤",
"-706012013": "Property不可讀",
"-706012015": "Action執行錯誤",
"-706012023": "Property不可寫",
"-706012033": "Property不可訂閱",
"-706012043": "Property值錯誤",
"-706014006": "沒找到設備描述"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,809 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
MIoT http client.
"""
import asyncio
import base64
import json
import logging
import re
import time
from functools import partial
from typing import Optional
from urllib.parse import urlencode
import requests
from .common import calc_group_id
from .const import (
DEFAULT_OAUTH2_API_HOST,
MIHOME_HTTP_API_TIMEOUT,
OAUTH2_AUTH_URL)
from .miot_error import MIoTErrorCode, MIoTHttpError, MIoTOauthError
_LOGGER = logging.getLogger(__name__)
TOKEN_EXPIRES_TS_RATIO = 0.7
class MIoTOauthClient:
"""oauth agent url, default: product env."""
_main_loop: asyncio.AbstractEventLoop = None
_oauth_host: str = None
_client_id: int
_redirect_url: str
def __init__(
self, client_id: str, redirect_url: str, cloud_server: str,
loop: Optional[asyncio.AbstractEventLoop] = None
) -> None:
self._main_loop = loop or asyncio.get_running_loop()
if client_id is None or client_id.strip() == '':
raise MIoTOauthError('invalid client_id')
if not redirect_url:
raise MIoTOauthError('invalid redirect_url')
if not cloud_server:
raise MIoTOauthError('invalid cloud_server')
self._client_id = int(client_id)
self._redirect_url = redirect_url
if cloud_server == 'cn':
self._oauth_host = DEFAULT_OAUTH2_API_HOST
else:
self._oauth_host = f'{cloud_server}.{DEFAULT_OAUTH2_API_HOST}'
async def __call_async(self, func):
return await self._main_loop.run_in_executor(executor=None, func=func)
def set_redirect_url(self, redirect_url: str) -> None:
if not isinstance(redirect_url, str) or redirect_url.strip() == '':
raise MIoTOauthError('invalid redirect_url')
self._redirect_url = redirect_url
def gen_auth_url(
self,
redirect_url: Optional[str] = None,
state: Optional[str] = None,
scope: Optional[list] = None,
skip_confirm: Optional[bool] = False,
) -> str:
"""get auth url
Args:
redirect_url
state
scope (list, optional):
开放数据接口权限 ID可以传递多个用空格分隔具体值可以参考开放
[数据接口权限列表](https://dev.mi.com/distribute/doc/details?pId=1518).
Defaults to None.\n
skip_confirm (bool, optional):
默认值为true授权有效期内的用户在已登录情况下不显示授权页面直接通过
如果需要用户每次手动授权设置为false. Defaults to True.\n
Returns:
str: _description_
"""
params: dict = {
'redirect_uri': redirect_url or self._redirect_url,
'client_id': self._client_id,
'response_type': 'code',
}
if state:
params['state'] = state
if scope:
params['scope'] = ' '.join(scope).strip()
params['skip_confirm'] = skip_confirm
encoded_params = urlencode(params)
return f'{OAUTH2_AUTH_URL}?{encoded_params}'
def _get_token(self, data) -> dict:
http_res = requests.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:
raise MIoTOauthError(
'unauthorized(401)', MIoTErrorCode.CODE_OAUTH_UNAUTHORIZED)
if http_res.status_code != 200:
raise MIoTOauthError(
f'invalid http status code, {http_res.status_code}')
res_obj = http_res.json()
if (
not res_obj
or res_obj.get('code', None) != 0
or 'result' not in res_obj
or not all(
key in res_obj['result']
for key in ['access_token', 'refresh_token', 'expires_in'])
):
raise MIoTOauthError(f'invalid http response, {http_res.text}')
return {
**res_obj['result'],
'expires_ts': int(
time.time() +
(res_obj['result'].get('expires_in', 0)*TOKEN_EXPIRES_TS_RATIO))
}
def get_access_token(self, code: str) -> dict:
"""get access token by authorization code
Args:
code (str): auth code
Returns:
str: _description_
"""
if not isinstance(code, str):
raise MIoTOauthError('invalid code')
return self._get_token(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:
"""get access token by refresh token.
Args:
refresh_token (str): refresh_token
Returns:
str: _description_
"""
if not isinstance(refresh_token, str):
raise MIoTOauthError('invalid refresh_token')
return self._get_token(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."""
GET_PROP_AGGREGATE_INTERVAL: float = 0.2
GET_PROP_MAX_REQ_COUNT = 150
_main_loop: asyncio.AbstractEventLoop
_host: str
_base_url: str
_client_id: str
_access_token: str
_get_prop_timer: asyncio.TimerHandle
_get_prop_list: dict[str, dict[str, asyncio.Future | str | bool]]
def __init__(
self, cloud_server: str, client_id: str, access_token: str,
loop: Optional[asyncio.AbstractEventLoop] = None
) -> None:
self._main_loop = loop or asyncio.get_running_loop()
self._host = None
self._base_url = None
self._client_id = None
self._access_token = None
self._get_prop_timer: asyncio.TimerHandle = None
self._get_prop_list = {}
if (
not isinstance(cloud_server, str)
or not isinstance(client_id, str)
or not isinstance(access_token, str)
):
raise MIoTHttpError('invalid params')
self.update_http_header(
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)
def update_http_header(
self, cloud_server: Optional[str] = None,
client_id: Optional[str] = None,
access_token: Optional[str] = None
) -> None:
if isinstance(cloud_server, str):
if cloud_server == 'cn':
self._host = DEFAULT_OAUTH2_API_HOST
else:
self._host = f'{cloud_server}.{DEFAULT_OAUTH2_API_HOST}'
self._base_url = f'https://{self._host}'
if isinstance(client_id, str):
self._client_id = client_id
if isinstance(access_token, str):
self._access_token = access_token
@property
def __api_session(self) -> requests.Session:
session = requests.Session()
session.headers.update({
'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(
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:
raise MIoTHttpError(
'mihome api get failed, unauthorized(401)',
MIoTErrorCode.CODE_HTTP_INVALID_ACCESS_TOKEN)
if http_res.status_code != 200:
raise MIoTHttpError(
f'mihome api get failed, {http_res.status_code}, '
f'{url_path}, {params}')
res_obj: dict = http_res.json()
if res_obj.get('code', None) != 0:
raise MIoTHttpError(
f'invalid response code, {res_obj.get("code",None)}, '
f'{res_obj.get("message","")}')
_LOGGER.debug(
'mihome api get, %s%s, %s -> %s',
self._base_url, url_path, params, res_obj)
return res_obj
def mihome_api_post(
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:
raise MIoTHttpError(
'mihome api get failed, unauthorized(401)',
MIoTErrorCode.CODE_HTTP_INVALID_ACCESS_TOKEN)
if http_res.status_code != 200:
raise MIoTHttpError(
f'mihome api post failed, {http_res.status_code}, '
f'{url_path}, {data}')
res_obj: dict = http_res.json()
if res_obj.get('code', None) != 0:
raise MIoTHttpError(
f'invalid response code, {res_obj.get("code",None)}, '
f'{res_obj.get("message","")}')
_LOGGER.debug(
'mihome api post, %s%s, %s -> %s',
self._base_url, url_path, data, res_obj)
return res_obj
def get_user_info(self) -> dict:
http_res = requests.get(
url='https://open.account.xiaomi.com/user/profile',
params={'clientId': self._client_id,
'token': self._access_token},
headers={'content-type': 'application/x-www-form-urlencoded'},
timeout=MIHOME_HTTP_API_TIMEOUT
)
res_obj = http_res.json()
if (
not res_obj
or res_obj.get('code', None) != 0
or 'data' not in res_obj
or 'miliaoNick' not in res_obj['data']
):
raise MIoTOauthError(f'invalid http response, {http_res.text}')
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]:
if not isinstance(csr, str):
raise MIoTHttpError('invalid params')
res_obj: dict = self.mihome_api_post(
url_path='/app/v2/ha/oauth/get_central_crt',
data={
'csr': str(base64.b64encode(csr.encode('utf-8')), 'utf-8')
}
)
if 'result' not in res_obj:
raise MIoTHttpError('invalid response result')
cert: str = res_obj['result'].get('cert', None)
if not isinstance(cert, str):
raise MIoTHttpError('invalid cert')
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(
url_path='/app/v2/homeroom/get_dev_room_page',
data={
'start_id': max_id,
'limit': 150,
},
)
if 'result' not in res_obj and 'info' not in res_obj['result']:
raise MIoTHttpError('invalid response result')
home_list: dict = {}
for home in res_obj['result']['info']:
if 'id' not in home:
_LOGGER.error(
'get dev room page error, invalid home, %s', home)
continue
home_list[str(home['id'])] = {'dids': home.get(
'dids', None) or [], 'room_info': {}}
for room in home.get('roomlist', []):
if 'id' not in room:
_LOGGER.error(
'get dev room page error, invalid room, %s', room)
continue
home_list[str(home['id'])]['room_info'][str(room['id'])] = {
'dids': room.get('dids', None) or []}
if (
res_obj['result'].get('has_more', False)
and isinstance(res_obj['result'].get('max_id', None), str)
):
next_list = self.__get_dev_room_page(
max_id=res_obj['result']['max_id'])
for home_id, info in next_list.items():
home_list.setdefault(home_id, {'dids': [], 'room_info': {}})
home_list[home_id]['dids'].extend(info['dids'])
for room_id, info in info['room_info'].items():
home_list[home_id]['room_info'].setdefault(
room_id, {'dids': []})
home_list[home_id]['room_info'][room_id]['dids'].extend(
info['dids'])
return home_list
def get_homeinfos(self) -> dict:
res_obj = self.mihome_api_post(
url_path='/app/v2/homeroom/gethome',
data={
'limit': 150,
'fetch_share': True,
'fetch_share_dev': True,
'plat_form': 0,
'app_ver': 9,
},
)
if 'result' not in res_obj:
raise MIoTHttpError('invalid response result')
uid: str = None
home_infos: dict = {}
for device_source in ['homelist', 'share_home_list']:
home_infos.setdefault(device_source, {})
for home in res_obj['result'].get(device_source, []):
if (
'id' not in home
or 'name' not in home
or 'roomlist' not in home
):
continue
if uid is None and device_source == 'homelist':
uid = str(home['uid'])
home_infos[device_source][home['id']] = {
'home_id': home['id'],
'home_name': home['name'],
'city_id': home.get('city_id', None),
'longitude': home.get('longitude', None),
'latitude': home.get('latitude', None),
'address': home.get('address', None),
'dids': home.get('dids', []),
'room_info': {
room['id']: {
'room_id': room['id'],
'room_name': room['name'],
'dids': room.get('dids', [])
}
for room in home.get('roomlist', [])
},
'group_id': calc_group_id(
uid=home['uid'], home_id=home['id']),
'uid': str(home['uid'])
}
home_infos['uid'] = uid
if (
res_obj['result'].get('has_more', False)
and isinstance(res_obj['result'].get('max_id', None), str)
):
more_list = self.__get_dev_room_page(
max_id=res_obj['result']['max_id'])
for home_id, info in more_list.items():
if home_id not in home_infos['homelist']:
_LOGGER.info('unknown home, %s, %s', home_id, info)
continue
home_infos['homelist'][home_id]['dids'].extend(info['dids'])
for room_id, info in info['room_info'].items():
home_infos['homelist'][home_id]['room_info'].setdefault(
room_id, {'dids': []})
home_infos['homelist'][home_id]['room_info'][
room_id]['dids'].extend(info['dids'])
return {
'uid': uid,
'home_list': home_infos.get('homelist', {}),
'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(
self, dids: list[str], start_did: str = None
) -> dict[str, dict]:
req_data: dict = {
'limit': 200,
'get_split_device': True,
'dids': dids
}
if start_did:
req_data['start_did'] = start_did
device_infos: dict = {}
res_obj = self.mihome_api_post(
url_path='/app/v2/home/device_list_page',
data=req_data
)
if 'result' not in res_obj:
raise MIoTHttpError('invalid response result')
res_obj = res_obj['result']
for device in res_obj.get('list', []) or []:
did = device.get('did', None)
name = device.get('name', None)
urn = device.get('spec_type', None)
model = device.get('model', None)
if did is None or name is None or urn is None or model is None:
_LOGGER.error(
'get_device_list, cloud, invalid device, %s', device)
continue
device_infos[did] = {
'did': did,
'uid': device.get('uid', None),
'name': name,
'urn': urn,
'model': model,
'connect_type': device.get('pid', -1),
'token': device.get('token', None),
'online': device.get('isOnline', False),
'icon': device.get('icon', None),
'parent_id': device.get('parent_id', None),
'manufacturer': model.split('.')[0],
# 2: xiao-ai, 1: general speaker
'voice_ctrl': device.get('voice_ctrl', 0),
'rssi': device.get('rssi', None),
'owner': device.get('owner', None),
'pid': device.get('pid', None),
'local_ip': device.get('local_ip', None),
'ssid': device.get('ssid', None),
'bssid': device.get('bssid', None),
'order_time': device.get('orderTime', 0),
'fw_version': device.get('extra', {}).get(
'fw_version', 'unknown'),
}
if isinstance(device.get('extra', None), dict) and device['extra']:
device_infos[did]['fw_version'] = device['extra'].get(
'fw_version', None)
device_infos[did]['mcu_version'] = device['extra'].get(
'mcu_version', None)
device_infos[did]['platform'] = device['extra'].get(
'platform', None)
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(
dids=dids, start_did=next_start_did))
return device_infos
async def get_devices_with_dids_async(
self, dids: list[str]
) -> dict[str, dict]:
results: list[dict[str, dict]] = await asyncio.gather(
*[self.__call_async(
partial(self.__get_device_list_page, dids[index:index+150]))
for index in range(0, len(dids), 150)])
devices = {}
for result in results:
if result is None:
return None
devices.update(result)
return devices
async def get_devices_async(
self, home_ids: list[str] = None
) -> dict[str, dict]:
homeinfos = await self.get_homeinfos_async()
homes: dict[str, dict[str, any]] = {}
devices: dict[str, dict] = {}
for device_type in ['home_list', 'share_home_list']:
homes.setdefault(device_type, {})
for home_id, home_info in (homeinfos.get(
device_type, None) or {}).items():
if isinstance(home_ids, list) and home_id not in home_ids:
continue
homes[device_type].setdefault(
home_id, {
'home_name': home_info['home_name'],
'uid': home_info['uid'],
'group_id': home_info['group_id'],
'room_info': {}
})
devices.update({did: {
'home_id': home_id,
'home_name': home_info['home_name'],
'room_id': home_id,
'room_name': home_info['home_name'],
'group_id': home_info['group_id']
} for did in home_info.get('dids', [])})
for room_id, room_info in home_info.get('room_info').items():
homes[device_type][home_id]['room_info'][
room_id] = room_info['room_name']
devices.update({
did: {
'home_id': home_id,
'home_name': home_info['home_name'],
'room_id': room_id,
'room_name': room_info['room_name'],
'group_id': home_info['group_id']
} for did in room_info.get('dids', [])})
dids = sorted(list(devices.keys()))
results: dict[str, dict] = await self.get_devices_with_dids_async(
dids=dids)
for did in dids:
if did not in results:
devices.pop(did, None)
_LOGGER.error('get device info failed, %s', did)
continue
devices[did].update(results[did])
# Whether sub devices
match_str = re.search(r'\.s\d+$', did)
if not match_str:
continue
device = devices.pop(did, None)
parent_did = did.replace(match_str.group(), '')
if parent_did in devices:
devices[parent_did].setdefault('sub_devices', {})
devices[parent_did]['sub_devices'][match_str.group()[
1:]] = device
else:
_LOGGER.error(
'unknown sub devices, %s, %s', did, parent_did)
return {
'uid': homeinfos['uid'],
'homes': homes,
'devices': devices
}
def get_props(self, params: list) -> list:
"""
params = [{"did": "xxxx", "siid": 2, "piid": 1},
{"did": "xxxxxx", "siid": 2, "piid": 2}]
"""
res_obj = self.mihome_api_post(
url_path='/app/v2/miotspec/prop/get',
data={
'datasource': 1,
'params': params
},
)
if 'result' not in res_obj:
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(
params=[{'did': did, 'siid': siid, 'piid': piid}])
if not results:
return None
result = results[0]
if 'value' not in result:
return None
return result['value']
async def __get_prop_handler(self) -> bool:
props_req: set[str] = set()
props_buffer: list[dict] = []
for key, item in self._get_prop_list.items():
if item.get('tag', False):
continue
# NOTICE: max req prop
if len(props_req) >= self.GET_PROP_MAX_REQ_COUNT:
break
item['tag'] = True
props_buffer.append(item['param'])
props_req.add(key)
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))
for result in results:
if not all(
key in result for key in ['did', 'siid', 'piid', 'value']):
continue
key = f'{result["did"]}.{result["siid"]}.{result["piid"]}'
prop_obj = self._get_prop_list.pop(key, None)
if prop_obj is None:
_LOGGER.error('get prop error, key not exists, %s', result)
continue
prop_obj['fut'].set_result(result['value'])
props_req.remove(key)
for key in props_req:
prop_obj = self._get_prop_list.pop(key, None)
if prop_obj is None:
continue
prop_obj['fut'].set_result(None)
if props_req:
_LOGGER.error(
'get prop from cloud failed, %s, %s', len(key), props_req)
if self._get_prop_list:
self._get_prop_timer = self._main_loop.call_later(
self.GET_PROP_AGGREGATE_INTERVAL,
lambda: self._main_loop.create_task(
self.__get_prop_handler()))
else:
self._get_prop_timer = None
return True
async def get_prop_async(
self, did: str, siid: int, piid: int, immediately: bool = False
) -> any:
if immediately:
return await self.__call_async(
partial(self.get_prop, did, siid, piid))
key: str = f'{did}.{siid}.{piid}'
prop_obj = self._get_prop_list.get(key, None)
if prop_obj:
return await prop_obj['fut']
fut = self._main_loop.create_future()
self._get_prop_list[key] = {
'param': {'did': did, 'siid': siid, 'piid': piid},
'fut': fut
}
if self._get_prop_timer is None:
self._get_prop_timer = self._main_loop.call_later(
self.GET_PROP_AGGREGATE_INTERVAL,
lambda: self._main_loop.create_task(
self.__get_prop_handler()))
return await fut
def set_prop(self, params: list) -> list:
"""
params = [{"did": "xxxx", "siid": 2, "piid": 1, "value": False}]
"""
res_obj = self.mihome_api_post(
url_path='/app/v2/miotspec/prop/set',
data={
'params': params
},
timeout=15
)
if 'result' not in res_obj:
raise MIoTHttpError('invalid response result')
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(
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(
url_path='/app/v2/miotspec/action',
data={
'params': {
'did': did,
'siid': siid,
'aiid': aiid,
'in': [item['value'] for item in in_list]}
},
timeout=15
)
if 'result' not in res_obj:
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))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
MIoT error code and exception.
"""
from enum import Enum
class MIoTErrorCode(Enum):
"""MIoT error code."""
# Base error code
CODE_UNKNOWN = -10000
CODE_UNAVAILABLE = -10001
CODE_INVALID_PARAMS = -10002
CODE_RESOURCE_ERROR = -10003
CODE_INTERNAL_ERROR = -10004
CODE_UNAUTHORIZED_ACCESS = -10005
CODE_TIMEOUT = -10006
# OAuth error code
CODE_OAUTH_UNAUTHORIZED = -10020
# Http error code
CODE_HTTP_INVALID_ACCESS_TOKEN = -10030
# MIoT mips error code
CODE_MIPS_INVALID_RESULT = -10040
# MIoT cert error code
CODE_CERT_INVALID_CERT = -10050
# MIoT spec error code, -10060
# MIoT storage error code, -10070
# MIoT ev error code, -10080
# Mips service error code, -10090
# Config flow error code, -10100
# Options flow error code , -10110
# MIoT lan error code, -10120
class MIoTError(Exception):
"""MIoT error."""
code: MIoTErrorCode
message: any
def __init__(
self, message: any, code: MIoTErrorCode = MIoTErrorCode.CODE_UNKNOWN
) -> None:
self.message = message
self.code = code
super().__init__(self.message)
def to_str(self) -> str:
return f'{{"code":{self.code.value},"message":"{self.message}"}}'
def to_dict(self) -> dict:
return {"code": self.code.value, "message": self.message}
class MIoTOauthError(MIoTError):
...
class MIoTHttpError(MIoTError):
...
class MIoTMipsError(MIoTError):
...
class MIoTDeviceError(MIoTError):
...
class MIoTSpecError(MIoTError):
...
class MIoTStorageError(MIoTError):
...
class MIoTCertError(MIoTError):
...
class MIoTClientError(MIoTError):
...
class MIoTEvError(MIoTError):
...
class MipsServiceError(MIoTError):
...
class MIoTConfigError(MIoTError):
...
class MIoTOptionsError(MIoTError):
...

View File

@ -0,0 +1,320 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
MIoT event loop.
"""
import selectors
import heapq
import time
import traceback
from typing import Callable, TypeVar
import logging
import threading
from .miot_error import MIoTEvError
_LOGGER = logging.getLogger(__name__)
TimeoutHandle = TypeVar('TimeoutHandle')
class MIoTFdHandler:
"""File descriptor handler."""
fd: int
read_handler: Callable[[any], None]
read_handler_ctx: any
write_handler: Callable[[any], None]
write_handler_ctx: any
def __init__(
self, fd: int,
read_handler: Callable[[any], None] = None,
read_handler_ctx: any = None,
write_handler: Callable[[any], None] = None,
write_handler_ctx: any = None
) -> None:
self.fd = fd
self.read_handler = read_handler
self.read_handler_ctx = read_handler_ctx
self.write_handler = write_handler
self.write_handler_ctx = write_handler_ctx
class MIoTTimeout:
"""Timeout handler."""
key: TimeoutHandle
target: int
handler: Callable[[any], None]
handler_ctx: any
def __init__(
self, key: str = None, target: int = None,
handler: Callable[[any], None] = None,
handler_ctx: any = None
) -> None:
self.key = key
self.target = target
self.handler = handler
self.handler_ctx = handler_ctx
def __lt__(self, other):
return self.target < other.target
class MIoTEventLoop:
"""MIoT event loop."""
_poll_fd: selectors.DefaultSelector
_fd_handlers: dict[str, MIoTFdHandler]
_timer_heap: list[MIoTTimeout]
_timer_handlers: dict[str, MIoTTimeout]
_timer_handle_seed: int
# Label if the current fd handler is freed inside a read handler to
# avoid invalid reading.
_fd_handler_freed_in_read_handler: bool
def __init__(self) -> None:
self._poll_fd = selectors.DefaultSelector()
self._timer_heap = []
self._timer_handlers = {}
self._timer_handle_seed = 1
self._fd_handlers = {}
self._fd_handler_freed_in_read_handler = False
def loop_forever(self) -> None:
"""Run an event loop in current thread."""
next_timeout: int
while True:
next_timeout = 0
# Handle timer
now_ms: int = self.__get_monotonic_ms
while len(self._timer_heap) > 0:
timer: MIoTTimeout = self._timer_heap[0]
if timer is None:
break
if timer.target <= now_ms:
heapq.heappop(self._timer_heap)
del self._timer_handlers[timer.key]
if timer.handler:
timer.handler(timer.handler_ctx)
else:
next_timeout = timer.target-now_ms
break
# Are there any files to listen to
if next_timeout == 0 and self._fd_handlers:
next_timeout = None # None == infinite
# Wait for timers & fds
if next_timeout == 0:
# Neither timer nor fds exist, exit loop
break
# Handle fd event
events = self._poll_fd.select(
timeout=next_timeout/1000.0 if next_timeout else next_timeout)
for key, mask in events:
fd_handler: MIoTFdHandler = key.data
if fd_handler is None:
continue
self._fd_handler_freed_in_read_handler = False
fd_key = str(id(fd_handler.fd))
if fd_key not in self._fd_handlers:
continue
if (
mask & selectors.EVENT_READ > 0
and fd_handler.read_handler
):
fd_handler.read_handler(fd_handler.read_handler_ctx)
if (
mask & selectors.EVENT_WRITE > 0
and self._fd_handler_freed_in_read_handler is False
and fd_handler.write_handler
):
fd_handler.write_handler(fd_handler.write_handler_ctx)
def loop_stop(self) -> None:
"""Stop the event loop."""
if self._poll_fd:
self._poll_fd.close()
self._poll_fd = None
self._fd_handlers = {}
self._timer_heap = []
self._timer_handlers = {}
def set_timeout(
self, timeout_ms: int, handler: Callable[[any], None],
handler_ctx: any = None
) -> TimeoutHandle:
"""Set a timer."""
if timeout_ms is None or handler is None:
raise MIoTEvError('invalid params')
new_timeout: MIoTTimeout = MIoTTimeout()
new_timeout.key = self.__get_next_timeout_handle
new_timeout.target = self.__get_monotonic_ms + timeout_ms
new_timeout.handler = handler
new_timeout.handler_ctx = handler_ctx
heapq.heappush(self._timer_heap, new_timeout)
self._timer_handlers[new_timeout.key] = new_timeout
return new_timeout.key
def clear_timeout(self, timer_key: TimeoutHandle) -> None:
"""Stop and remove the timer."""
if timer_key is None:
return
timer: MIoTTimeout = self._timer_handlers.pop(timer_key, None)
if timer:
self._timer_heap = list(self._timer_heap)
self._timer_heap.remove(timer)
heapq.heapify(self._timer_heap)
def set_read_handler(
self, fd: int, handler: Callable[[any], None], handler_ctx: any = None
) -> bool:
"""Set a read handler for a file descriptor.
Returns:
bool: True, success. False, failed.
"""
self.__set_handler(
fd, is_read=True, handler=handler, handler_ctx=handler_ctx)
def set_write_handler(
self, fd: int, handler: Callable[[any], None], handler_ctx: any = None
) -> bool:
"""Set a write handler for a file descriptor.
Returns:
bool: True, success. False, failed.
"""
self.__set_handler(
fd, is_read=False, handler=handler, handler_ctx=handler_ctx)
def __set_handler(
self, fd, is_read: bool, handler: Callable[[any], None],
handler_ctx: any = None
) -> bool:
"""Set a handler."""
if fd is None:
raise MIoTEvError('invalid params')
fd_key: str = str(id(fd))
fd_handler = self._fd_handlers.get(fd_key, None)
if fd_handler is None:
fd_handler = MIoTFdHandler(fd=fd)
fd_handler.fd = fd
self._fd_handlers[fd_key] = fd_handler
read_handler_existed = fd_handler.read_handler is not None
write_handler_existed = fd_handler.write_handler is not None
if is_read is True:
fd_handler.read_handler = handler
fd_handler.read_handler_ctx = handler_ctx
else:
fd_handler.write_handler = handler
fd_handler.write_handler_ctx = handler_ctx
if fd_handler.read_handler is None and fd_handler.write_handler is None:
# Remove from epoll and map
try:
self._poll_fd.unregister(fd)
except (KeyError, ValueError, OSError) as e:
del e
self._fd_handlers.pop(fd_key, None)
# May be inside a read handler, if not, this has no effect
self._fd_handler_freed_in_read_handler = True
elif read_handler_existed is False and write_handler_existed is False:
# Add to epoll
events = 0x0
if fd_handler.read_handler:
events |= selectors.EVENT_READ
if fd_handler.write_handler:
events |= selectors.EVENT_WRITE
try:
self._poll_fd.register(fd, events=events, data=fd_handler)
except (KeyError, ValueError, OSError) as e:
_LOGGER.error(
'%s, register fd, error, %s, %s, %s, %s, %s',
threading.current_thread().name,
'read' if is_read else 'write',
fd_key, handler, e, traceback.format_exc())
self._fd_handlers.pop(fd_key, None)
return False
elif (
read_handler_existed != (fd_handler.read_handler is not None)
or write_handler_existed != (fd_handler.write_handler is not None)
):
# Modify epoll
events = 0x0
if fd_handler.read_handler:
events |= selectors.EVENT_READ
if fd_handler.write_handler:
events |= selectors.EVENT_WRITE
try:
self._poll_fd.modify(fd, events=events, data=fd_handler)
except (KeyError, ValueError, OSError) as e:
_LOGGER.error(
'%s, modify fd, error, %s, %s, %s, %s, %s',
threading.current_thread().name,
'read' if is_read else 'write',
fd_key, handler, e, traceback.format_exc())
self._fd_handlers.pop(fd_key, None)
return False
return True
@property
def __get_next_timeout_handle(self) -> str:
# Get next timeout handle, that is not larger than the maximum
# value of UINT64 type.
self._timer_handle_seed += 1
# uint64 max
self._timer_handle_seed %= 0xFFFFFFFFFFFFFFFF
return str(self._timer_handle_seed)
@property
def __get_monotonic_ms(self) -> int:
"""Get monotonic ms timestamp."""
return int(time.monotonic()*1000)

View File

@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
MIoT internationalization translation.
"""
import asyncio
import logging
import os
from typing import Optional
from .common import load_json_file
_LOGGER = logging.getLogger(__name__)
class MIoTI18n:
"""MIoT Internationalization Translation.
Translate by Copilot, which does not guarantee the accuracy of the
translation. If there is a problem with the translation, please submit
the ISSUE feedback. After the review, we will modify it as soon as possible.
"""
_main_loop: asyncio.AbstractEventLoop
_lang: str
_data: dict
def __init__(
self, lang: str, loop: Optional[asyncio.AbstractEventLoop]
) -> None:
self._main_loop = loop or asyncio.get_event_loop()
self._lang = lang
self._data = None
async def init_async(self) -> None:
if self._data:
return
data = None
self._data = {}
try:
data = await self._main_loop.run_in_executor(
None, load_json_file,
os.path.join(
os.path.dirname(os.path.abspath(__file__)),
f'i18n/{self._lang}.json'))
except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.error('load file error, %s', err)
return
# Check if the file is a valid JSON file
if not isinstance(data, dict):
_LOGGER.error('valid file, %s', data)
return
self._data = data
async def deinit_async(self) -> None:
self._data = None
def translate(
self, key: str, replace: Optional[dict[str, str]] = None
) -> str | dict | None:
result = self._data
for item in key.split('.'):
if item not in result:
return None
result = result[item]
if isinstance(result, str) and replace:
for k, v in replace.items():
result = result.replace('{'+k+'}', str(v))
return result or None

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
MIoT central hub gateway service discovery.
"""
import asyncio
import base64
import binascii
import copy
from enum import Enum
from typing import Callable, Optional
import logging
from zeroconf import (
DNSQuestionType,
IPVersion,
ServiceStateChange,
Zeroconf)
from zeroconf.asyncio import (
AsyncServiceInfo,
AsyncZeroconf,
AsyncServiceBrowser)
from .miot_error import MipsServiceError
_LOGGER = logging.getLogger(__name__)
MIPS_MDNS_TYPE = '_miot-central._tcp.local.'
MIPS_MDNS_REQUEST_TIMEOUT_MS = 5000
MIPS_MDNS_UPDATE_INTERVAL_S = 600
class MipsServiceState(Enum):
ADDED = 1
REMOVED = 2
UPDATED = 3
class MipsServiceData:
"""Mips service data."""
profile: str
profile_bin: bytes
name: str
addresses: list[str]
port: int
type: str
server: str
did: str
group_id: str
role: int
suite_mqtt: bool
def __init__(self, service_info: AsyncServiceInfo) -> None:
if service_info is None:
raise MipsServiceError('invalid params')
properties = service_info.decoded_properties
if properties is None:
raise MipsServiceError('invalid service properties')
self.profile = properties.get('profile', None)
if self.profile is None:
raise MipsServiceError('invalid service profile')
self.profile_bin = base64.b64decode(self.profile)
self.name = service_info.name
self.addresses = service_info.parsed_addresses(
version=IPVersion.V4Only)
if not self.addresses:
raise MipsServiceError('invalid addresses')
self.addresses.sort()
self.port = service_info.port
self.type = service_info.type
self.server = service_info.server
# Parse profile
self.did = str(int.from_bytes(self.profile_bin[1:9]))
self.group_id = binascii.hexlify(
self.profile_bin[9:17][::-1]).decode('utf-8')
self.role = int(self.profile_bin[20] >> 4)
self.suite_mqtt = ((self.profile_bin[22] >> 1) & 0x01) == 0x01
def valid_service(self) -> bool:
if self.role != 1:
return False
return self.suite_mqtt
def to_dict(self) -> dict:
return {
'name': self.name,
'addresses': self.addresses,
'port': self.port,
'type': self.type,
'server': self.server,
'did': self.did,
'group_id': self.group_id,
'role': self.role,
'suite_mqtt': self.suite_mqtt
}
def __str__(self) -> str:
return str(self.to_dict())
class MipsService:
"""MIPS service discovery."""
_aiozc: AsyncZeroconf
_main_loop: asyncio.AbstractEventLoop
_aio_browser: AsyncServiceBrowser
_services: dict[str, dict]
# key = (key, group_id)
_sub_list: dict[(str, str), Callable[[
str, MipsServiceState, dict], asyncio.Future]]
def __init__(
self, aiozc: AsyncZeroconf,
loop: Optional[asyncio.AbstractEventLoop] = None
) -> None:
self._aiozc = aiozc
self._main_loop = loop or asyncio.get_running_loop()
self._aio_browser = None
self._services = {}
self._sub_list = {}
async def init_async(self) -> None:
await self._aiozc.zeroconf.async_wait_for_start()
self._aio_browser = AsyncServiceBrowser(
zeroconf=self._aiozc.zeroconf,
type_=MIPS_MDNS_TYPE,
handlers=[self.__on_service_state_change],
question_type=DNSQuestionType.QM)
async def deinit_async(self) -> None:
await self._aio_browser.async_cancel()
self._services = {}
self._sub_list = {}
def get_services(self, group_id: Optional[str] = None) -> dict[str, dict]:
"""get mips services.
Args:
group_id (str, optional): _description_. Defaults to None.
Returns: {
[group_id:str]: {
"name": str,
"addresses": list[str],
"port": number,
"type": str,
"server": str,
"version": int,
"did": str,
"group_id": str,
"role": int,
"suite_mqtt": bool
}
}
"""
if group_id:
if group_id not in self._services:
return {}
return {group_id: copy.deepcopy(self._services[group_id])}
return copy.deepcopy(self._services)
def sub_service_change(
self, key: str, group_id: str,
handler: Callable[[str, MipsServiceState, dict], asyncio.Future]
) -> None:
if key is None or group_id is None or handler is None:
raise MipsServiceError('invalid params')
self._sub_list[(key, group_id)] = handler
def unsub_service_change(self, key: str) -> None:
if key is None:
return
for keys in list(self._sub_list.keys()):
if key == keys[0]:
self._sub_list.pop(keys, None)
def __on_service_state_change(
self, zeroconf: Zeroconf, service_type: str, name: str,
state_change: ServiceStateChange
) -> None:
_LOGGER.debug(
'mips service state changed, %s, %s, %s',
state_change, name, service_type)
if state_change is ServiceStateChange.Removed:
for item in list(self._services.values()):
if item['name'] != name:
continue
service_data = self._services.pop(item['group_id'], None)
self.__call_service_change(
state=MipsServiceState.REMOVED, data=service_data)
return
self._main_loop.create_task(
self.__request_service_info_async(zeroconf, service_type, name))
async def __request_service_info_async(
self, zeroconf: Zeroconf, service_type: str, name: str
) -> None:
info = AsyncServiceInfo(service_type, name)
await info.async_request(
zeroconf, MIPS_MDNS_REQUEST_TIMEOUT_MS,
question_type=DNSQuestionType.QU)
try:
service_data = MipsServiceData(info)
if not service_data.valid_service():
raise MipsServiceError(
'no primary role, no support mqtt connection')
if service_data.group_id in self._services:
# Update mips service
buffer_data = self._services[service_data.group_id]
if (
service_data.did != buffer_data['did']
or service_data.addresses != buffer_data['addresses']
or service_data.port != buffer_data['port']
):
self._services[service_data.group_id].update(
service_data.to_dict())
self.__call_service_change(
state=MipsServiceState.UPDATED,
data=service_data.to_dict())
else:
# Add mips service
self._services[service_data.group_id] = service_data.to_dict()
self.__call_service_change(
state=MipsServiceState.ADDED,
data=self._services[service_data.group_id])
except MipsServiceError as error:
_LOGGER.error('invalid mips service, %s, %s', error, info)
def __call_service_change(
self, state: MipsServiceState, data: dict = None
) -> None:
_LOGGER.info('call service change, %s, %s', state, data)
for keys in list(self._sub_list.keys()):
if keys[1] in [data['group_id'], '*']:
self._main_loop.create_task(
self._sub_list[keys](data['group_id'], state, data))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,295 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
MIoT network utilities.
"""
import asyncio
import logging
import platform
import socket
from dataclasses import dataclass
from enum import Enum, auto
import subprocess
from typing import Callable, Optional
import psutil
import ipaddress
_LOGGER = logging.getLogger(__name__)
class InterfaceStatus(Enum):
"""Interface status."""
ADD = 0
UPDATE = auto()
REMOVE = auto()
@dataclass
class NetworkInfo:
"""Network information."""
name: str
ip: str
netmask: str
net_seg: str
class MIoTNetwork:
"""MIoT network utilities."""
PING_ADDRESS_LIST = [
'1.2.4.8', # CNNIC sDNS
'8.8.8.8', # Google Public DNS
'233.5.5.5', # AliDNS
'1.1.1.1', # Cloudflare DNS
'114.114.114.114', # 114 DNS
'208.67.222.222', # OpenDNS
'9.9.9.9', # Quad9 DNS
]
_main_loop: asyncio.AbstractEventLoop
_refresh_interval: int
_refresh_task: asyncio.Task
_refresh_timer: asyncio.TimerHandle
_network_status: bool
_network_info: dict[str, NetworkInfo]
_sub_list_network_status: dict[str, Callable[[bool], asyncio.Future]]
_sub_list_network_info: dict[str, Callable[[
InterfaceStatus, NetworkInfo], asyncio.Future]]
_ping_address_priority: int
_done_event: asyncio.Event
def __init__(
self, loop: Optional[asyncio.AbstractEventLoop] = None
) -> None:
self._main_loop = loop or asyncio.get_running_loop()
self._refresh_interval = None
self._refresh_task = None
self._refresh_timer = None
self._network_status = False
self._network_info = {}
self._sub_list_network_status = {}
self._sub_list_network_info = {}
self._ping_address_priority = 0
self._done_event = asyncio.Event()
@property
def network_status(self) -> bool:
return self._network_status
@property
def network_info(self) -> dict[str, NetworkInfo]:
return self._network_info
async def deinit_async(self) -> None:
if self._refresh_task:
self._refresh_task.cancel()
self._refresh_task = None
if self._refresh_timer:
self._refresh_timer.cancel()
self._refresh_timer = None
self._refresh_interval = None
self._network_status = False
self._network_info.clear()
self._sub_list_network_status.clear()
self._sub_list_network_info.clear()
self._done_event.clear()
def sub_network_status(
self, key: str, handler: Callable[[bool], asyncio.Future]
) -> None:
self._sub_list_network_status[key] = handler
def unsub_network_status(self, key: str) -> None:
self._sub_list_network_status.pop(key, None)
def sub_network_info(
self, key: str,
handler: Callable[[InterfaceStatus, NetworkInfo], asyncio.Future]
) -> None:
self._sub_list_network_info[key] = handler
def unsub_network_info(self, key: str) -> None:
self._sub_list_network_info.pop(key, None)
async def init_async(self, refresh_interval: int = 30) -> bool:
self._refresh_interval = refresh_interval
self.__refresh_timer_handler()
# MUST get network info before starting
return await self._done_event.wait()
async def refresh_async(self) -> None:
self.__refresh_timer_handler()
async def get_network_status_async(self, timeout: int = 6) -> bool:
return await self._main_loop.run_in_executor(
None, self.__get_network_status, False, timeout)
async def get_network_info_async(self) -> dict[str, NetworkInfo]:
return await self._main_loop.run_in_executor(
None, self.__get_network_info)
def __calc_network_address(self, ip: str, netmask: str) -> str:
return str(ipaddress.IPv4Network(
f'{ip}/{netmask}', strict=False).network_address)
def __ping(
self, address: Optional[str] = None, timeout: int = 6
) -> bool:
param = '-n' if platform.system().lower() == 'windows' else '-c'
command = ['ping', param, '1', address]
try:
output = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True, timeout=timeout)
return output.returncode == 0
except Exception: # pylint: disable=broad-exception-caught
return False
def __get_network_status(
self, with_retry: bool = True, timeout: int = 6
) -> bool:
if self._ping_address_priority >= len(self.PING_ADDRESS_LIST):
self._ping_address_priority = 0
if self.__ping(
self.PING_ADDRESS_LIST[self._ping_address_priority], timeout):
return True
if not with_retry:
return False
for index in range(len(self.PING_ADDRESS_LIST)):
if index == self._ping_address_priority:
continue
if self.__ping(self.PING_ADDRESS_LIST[index], timeout):
self._ping_address_priority = index
return True
return False
def __get_network_info(self) -> dict[str, NetworkInfo]:
interfaces = psutil.net_if_addrs()
results: dict[str, NetworkInfo] = {}
for name, addresses in interfaces.items():
# Skip hassio and docker* interface
if name == 'hassio' or name.startswith('docker'):
continue
for address in addresses:
if (
address.family != socket.AF_INET
or not address.address
or not address.netmask
):
continue
# skip lo interface
if address.address == '127.0.0.1':
continue
results[name] = NetworkInfo(
name=name,
ip=address.address,
netmask=address.netmask,
net_seg=self.__calc_network_address(
address.address, address.netmask))
return results
def __call_network_info_change(
self, status: InterfaceStatus, info: NetworkInfo
) -> None:
for handler in self._sub_list_network_info.values():
self._main_loop.create_task(handler(status, info))
async def __update_status_and_info_async(self, timeout: int = 6) -> None:
try:
status: bool = await self._main_loop.run_in_executor(
None, self.__get_network_status, timeout)
infos = await self._main_loop.run_in_executor(
None, self.__get_network_info)
if self._network_status != status:
for handler in self._sub_list_network_status.values():
self._main_loop.create_task(handler(status))
self._network_status = status
for name in list(self._network_info.keys()):
info = infos.pop(name, None)
if info:
# Update
if (
info.ip != self._network_info[name].ip
or info.netmask != self._network_info[name].netmask
):
self._network_info[name] = info
self.__call_network_info_change(
InterfaceStatus.UPDATE, info)
else:
# Remove
self.__call_network_info_change(
InterfaceStatus.REMOVE,
self._network_info.pop(name, None))
# Add
for name, info in infos.items():
self._network_info[name] = info
self.__call_network_info_change(InterfaceStatus.ADD, info)
if not self._done_event.is_set():
self._done_event.set()
except asyncio.CancelledError:
_LOGGER.error('update_status_and_info task was cancelled')
def __refresh_timer_handler(self) -> None:
if self._refresh_timer:
self._refresh_timer.cancel()
self._refresh_timer = None
if self._refresh_task is None or self._refresh_task.done():
self._refresh_task = self._main_loop.create_task(
self.__update_status_and_info_async())
self._refresh_timer = self._main_loop.call_later(
self._refresh_interval, self.__refresh_timer_handler)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,235 @@
{
"data": {
"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: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: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:dryer:00000027": "open_close",
"urn:miot-spec-v2:property:eco:00000024": "open_close",
"urn:miot-spec-v2:property:glimmer-full-color:00000089": "open_close",
"urn:miot-spec-v2:property:guard-mode:000000B6": "open_close",
"urn:miot-spec-v2:property:heater:00000026": "open_close",
"urn:miot-spec-v2:property:heating:000000C7": "open_close",
"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:motion-detection:00000056": "open_close",
"urn:miot-spec-v2:property:motion-tracking:0000008A": "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:preheat:00000103": "open_close",
"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:soft-wind:000000CF": "open_close",
"urn:miot-spec-v2:property:speed-control:000000E8": "open_close",
"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",
"urn:miot-spec-v2:property:valve-switch:000000FE": "open_close",
"urn:miot-spec-v2:property:ventilation:000000CE": "open_close",
"urn:miot-spec-v2:property:vertical-swing:00000018": "open_close",
"urn:miot-spec-v2:property:wake-up-mode:00000107": "open_close",
"urn:miot-spec-v2:property:water-pump:000000F2": "open_close",
"urn:miot-spec-v2:property:watering:000000CC": "open_close",
"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"
},
"translate": {
"default": {
"zh-Hans": {
"true": "真",
"false": "假"
},
"zh-Hant": {
"true": "真",
"false": "假"
},
"en": {
"true": "True",
"false": "False"
},
"de": {
"true": "Wahr",
"false": "Falsch"
},
"es": {
"true": "Verdadero",
"false": "Falso"
},
"fr": {
"true": "Vrai",
"false": "Faux"
},
"ru": {
"true": "Истина",
"false": "Ложь"
},
"ja": {
"true": "真",
"false": "偽"
}
},
"open_close": {
"zh-Hans": {
"true": "开启",
"false": "关闭"
},
"zh-Hant": {
"true": "開啟",
"false": "關閉"
},
"en": {
"true": "Open",
"false": "Close"
},
"de": {
"true": "Öffnen",
"false": "Schließen"
},
"es": {
"true": "Abierto",
"false": "Cerrado"
},
"fr": {
"true": "Ouvert",
"false": "Fermer"
},
"ru": {
"true": "Открыть",
"false": "Закрыть"
},
"ja": {
"true": "開く",
"false": "閉じる"
}
},
"yes_no": {
"zh-Hans": {
"true": "是",
"false": "否"
},
"zh-Hant": {
"true": "是",
"false": "否"
},
"en": {
"true": "Yes",
"false": "No"
},
"de": {
"true": "Ja",
"false": "Nein"
},
"es": {
"true": "Sí",
"false": "No"
},
"fr": {
"true": "Oui",
"false": "Non"
},
"ru": {
"true": "Да",
"false": "Нет"
},
"ja": {
"true": "はい",
"false": "いいえ"
}
},
"motion_state": {
"zh-Hans": {
"true": "有人",
"false": "无人"
},
"zh-Hant": {
"true": "有人",
"false": "無人"
},
"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"
},
"fr": {
"true": "Mouvement détecté",
"false": "Aucun mouvement détecté"
},
"ru": {
"true": "Обнаружено движение",
"false": "Движение не обнаружено"
},
"ja": {
"true": "動きを検知",
"false": "動きが検出されません"
}
},
"contact_state": {
"zh-Hans": {
"true": "接触",
"false": "分离"
},
"zh-Hant": {
"true": "接觸",
"false": "分離"
},
"en": {
"true": "Contact",
"false": "No Contact"
},
"de": {
"true": "Kontakt",
"false": "Kein Kontakt"
},
"es": {
"true": "Contacto",
"false": "Sin contacto"
},
"fr": {
"true": "Contact",
"false": "Pas de contact"
},
"ru": {
"true": "Контакт",
"false": "Нет контакта"
},
"ja": {
"true": "接触",
"false": "非接触"
}
}
}
}

View File

@ -0,0 +1,158 @@
{
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
"en": {
"service:001": "Device Information",
"service:001:property:003": "Device ID",
"service:001:property:005": "Serial Number (SN)",
"service:002": "Gateway",
"service:002:event:001": "Network Changed",
"service:002:event:002": "Network Changed",
"service:002:property:001": "Access Method",
"service:002:property:001:valuelist:000": "Wired",
"service:002:property:001:valuelist:001": "5G Wireless",
"service:002:property:001:valuelist:002": "2.4G Wireless",
"service:002:property:002": "IP Address",
"service:002:property:003": "WiFi Network Name",
"service:002:property:004": "Current Time",
"service:002:property:005": "DHCP Server MAC Address",
"service:003": "Indicator Light",
"service:003:property:001": "Switch",
"service:004": "Virtual Service",
"service:004:action:001": "Generate Virtual Event",
"service:004:event:001": "Virtual Event Occurred",
"service:004:property:001": "Event Name"
},
"es": {
"service:001": "Información del dispositivo",
"service:001:property:003": "ID del dispositivo",
"service:001:property:005": "Número de serie (SN)",
"service:002": "Puerta de enlace",
"service:002:event:001": "Cambio de red",
"service:002:event:002": "Cambio de red",
"service:002:property:001": "Método de acceso",
"service:002:property:001:valuelist:000": "Cableado",
"service:002:property:001:valuelist:001": "5G inalámbrico",
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
"service:002:property:002": "Dirección IP",
"service:002:property:003": "Nombre de red WiFi",
"service:002:property:004": "Hora actual",
"service:002:property:005": "Dirección MAC del servidor DHCP",
"service:003": "Luz indicadora",
"service:003:property:001": "Interruptor",
"service:004": "Servicio virtual",
"service:004:action:001": "Generar evento virtual",
"service:004:event:001": "Ocurrió un evento virtual",
"service:004:property:001": "Nombre del evento"
},
"fr": {
"service:001": "Informations sur l'appareil",
"service:001:property:003": "ID de l'appareil",
"service:001:property:005": "Numéro de série (SN)",
"service:002": "Passerelle",
"service:002:event:001": "Changement de réseau",
"service:002:event:002": "Changement de réseau",
"service:002:property:001": "Méthode d'accès",
"service:002:property:001:valuelist:000": "Câblé",
"service:002:property:001:valuelist:001": "Sans fil 5G",
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
"service:002:property:002": "Adresse IP",
"service:002:property:003": "Nom du réseau WiFi",
"service:002:property:004": "Heure actuelle",
"service:002:property:005": "Adresse MAC du serveur DHCP",
"service:003": "Voyant lumineux",
"service:003:property:001": "Interrupteur",
"service:004": "Service virtuel",
"service:004:action:001": "Générer un événement virtuel",
"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",
"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": "DHCPサーバーMACアドレス",
"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",
"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": "DHCP伺服器MAC地址",
"service:003": "指示燈",
"service:003:property:001": "開關",
"service:004": "虛擬服務",
"service:004:action:001": "產生虛擬事件",
"service:004:event:001": "虛擬事件發生",
"service:004:property:001": "事件名稱"
}
}
}

View File

@ -0,0 +1,63 @@
{
"urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1": {
"services": [
"5"
]
},
"urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01": {
"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": {
"events": [
"2.1"
]
},
"urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1": {
"services": [
"1",
"5"
]
},
"urn:miot-spec-v2:device:light:0000A001:yeelink-color2": {
"properties": [
"3.*",
"2.5"
]
},
"urn:miot-spec-v2:device:light:0000A001:yeelink-dnlight2": {
"services": [
"3"
]
},
"urn:miot-spec-v2:device:light:0000A001:yeelink-mbulb3": {
"services": [
"3"
]
},
"urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4": {
"services": [
"10"
],
"properties": [
"9.*",
"13.*",
"15.*"
]
}
}

View File

@ -0,0 +1,392 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Conversion rules of MIoT-Spec-V2 instance to Home Assistant entity.
"""
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.components.event import EventDeviceClass
# pylint: disable=pointless-string-statement
"""SPEC_DEVICE_TRANS_MAP
{
'<device instance name>':{
'required':{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
}
}
},
'optional':{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
}
}
},
'entity': str
}
}
"""
SPEC_DEVICE_TRANS_MAP: dict[str, dict | str] = {
'humidifier': {
'required': {
'humidifier': {
'required': {
'properties': {
'on': {'read', 'write'}
}
},
'optional': {
'properties': {'mode', 'target-humidity'}
}
}
},
'optional': {
'environment': {
'required': {
'properties': {
'relative-humidity': {'read', 'write'}
}
}
}
},
'entity': 'humidifier'
},
'dehumidifier': {
'required': {
'dehumidifier': {
'required': {
'properties': {
'on': {'read', 'write'}
}
},
'optional': {
'properties': {'mode', 'target-humidity'}
}
},
},
'optional': {
'environment': {
'required': {
'properties': {
'relative-humidity': {'read', 'write'}
}
}
}
},
'entity': 'dehumidifier'
},
'vacuum': {
'required': {
'vacuum': {
'required': {
'actions': {'start-sweep', 'stop-sweeping'},
},
'optional': {
'properties': {'status', 'fan-level'},
'actions': {
'pause-sweeping',
'continue-sweep',
'stop-and-gocharge'
}
},
}
},
'optional': {
'identify': {
'required': {
'actions': {'identify'}
}
},
'battery': {
'required': {
'properties': {
'battery-level': {'read'}
},
}
},
},
'entity': 'vacuum'
},
'air-conditioner': {
'required': {
'air-conditioner': {
'required': {
'properties': {
'on': {'read', 'write'},
'mode': {'read', 'write'},
'target-temperature': {'read', 'write'}
}
},
'optional': {
'properties': {'target-humidity'}
},
}
},
'optional': {
'fan-control': {
'required': {},
'optional': {
'properties': {
'on',
'fan-level',
'horizontal-swing',
'vertical-swing'}}
},
'environment': {
'required': {},
'optional': {
'properties': {'temperature', 'relative-humidity'}
}
},
'air-condition-outlet-matching': {
'required': {},
'optional': {
'properties': {'ac-state'}
}
}
},
'entity': 'climate'
},
'air-condition-outlet': 'air-conditioner'
}
"""SPEC_SERVICE_TRANS_MAP
{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'entity': str
}
}
"""
SPEC_SERVICE_TRANS_MAP: dict[str, dict | str] = {
'light': {
'required': {
'properties': {
'on': {'read', 'write'}
}
},
'optional': {
'properties': {
'mode', 'brightness', 'color', 'color-temperature'
}
},
'entity': 'light'
},
'indicator-light': 'light',
'ambient-light': 'light',
'night-light': 'light',
'white-light': 'light',
'fan': {
'required': {
'properties': {
'on': {'read', 'write'},
'fan-level': {'read', 'write'}
}
},
'optional': {
'properties': {'mode', 'horizontal-swing'}
},
'entity': 'fan'
},
'fan-control': 'fan',
'ceiling-fan': 'fan',
'water-heater': {
'required': {
'properties': {
'on': {'read', 'write'}
}
},
'optional': {
'properties': {'on', 'temperature', 'target-temperature', 'mode'}
},
'entity': 'water_heater'
},
'curtain': {
'required': {
'properties': {
'motor-control': {'write'}
}
},
'optional': {
'properties': {
'motor-control', 'status', 'current-position', 'target-position'
}
},
'entity': 'cover'
},
'window-opener': 'curtain'
}
"""SPEC_PROP_TRANS_MAP
{
'entities':{
'<entity name>':{
'format': set<str>,
'access': set<str>
}
},
'properties': {
'<property instance name>':{
'device_class': str,
'entity': str
}
}
}
"""
SPEC_PROP_TRANS_MAP: dict[str, dict | str] = {
'entities': {
'sensor': {
'format': {'int', 'float'},
'access': {'read'}
},
'switch': {
'format': {'bool'},
'access': {'read', 'write'}
}
},
'properties': {
'temperature': {
'device_class': SensorDeviceClass.TEMPERATURE,
'entity': 'sensor'
},
'relative-humidity': {
'device_class': SensorDeviceClass.HUMIDITY,
'entity': 'sensor'
},
'air-quality-index': {
'device_class': SensorDeviceClass.AQI,
'entity': 'sensor'
},
'pm2.5-density': {
'device_class': SensorDeviceClass.PM25,
'entity': 'sensor'
},
'pm10-density': {
'device_class': SensorDeviceClass.PM10,
'entity': 'sensor'
},
'pm1': {
'device_class': SensorDeviceClass.PM1,
'entity': 'sensor'
},
'atmospheric-pressure': {
'device_class': SensorDeviceClass.ATMOSPHERIC_PRESSURE,
'entity': 'sensor'
},
'tvoc-density': {
'device_class': SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
'entity': 'sensor'
},
'voc-density': 'tvoc-density',
'battery-level': {
'device_class': SensorDeviceClass.BATTERY,
'entity': 'sensor'
},
'voltage': {
'device_class': SensorDeviceClass.VOLTAGE,
'entity': 'sensor'
},
'illumination': {
'device_class': SensorDeviceClass.ILLUMINANCE,
'entity': 'sensor'
},
'no-one-determine-time': {
'device_class': SensorDeviceClass.DURATION,
'entity': 'sensor'
},
'has-someone-duration': 'no-one-determine-time',
'no-one-duration': 'no-one-determine-time'
}
}
"""SPEC_EVENT_TRANS_MAP
{
'<event instance name>': str
}
"""
SPEC_EVENT_TRANS_MAP: dict[str, str] = {
'click': EventDeviceClass.BUTTON,
'double-click': EventDeviceClass.BUTTON,
'long-press': EventDeviceClass.BUTTON,
'motion-detected': EventDeviceClass.MOTION,
'no-motion': EventDeviceClass.MOTION,
'doorbell-ring': EventDeviceClass.DOORBELL
}
SPEC_ACTION_TRANS_MAP = {
}

View File

@ -0,0 +1,281 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
MIoT redirect web pages.
"""
def oauth_redirect_page(lang: str, status: str) -> str:
"""Return oauth redirect page."""
return '''
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="https://cdn.web-global.fds.api.mi-img.com/mcfe--mi-account/static/favicon_new.ico">
<link as="style"
href="https://font.sec.miui.com/font/css?family=MiSans:300,400,500,600,700:Chinese_Simplify,Chinese_Traditional,Latin&amp;display=swap"
rel="preload">
<title></title>
<style>
body {
background: white;
color: black;
display: flex;
justify-content: center;
align-items: center;
font-family: MiSans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
}
@media (prefers-color-scheme: dark) {
body {
background: black;
color: white;
}
}
.frame {
background: rgb(255 255 255 / 5%);
width: 360px;
padding: 40 45;
border-radius: 4px;
box-shadow: 0 20px 50px 0 hsla(0, 0%, 64%, .1);
text-align: center;
}
.logo-frame {
text-align: center;
}
.title-frame {
margin: 20px 0 20px 0;
font-size: 26px;
font-weight: 500;
line-height: 40px;
opacity: 0.8;
}
.content-frame {
font-size: 17px;
font-weight: 500;
line-height: 20px;
opacity: 0.8;
}
button {
margin-top: 20px;
background-color: #ff5c00;
border: none;
border-radius: 4px;
padding: 0 20px;
text-align: center;
width: 100%;
display: inline-block;
font-size: 18px;
font-weight: 400;
height: 60px;
line-height: 60px;
overflow: hidden;
position: relative;
text-overflow: ellipsis;
transition: all .3s cubic-bezier(.645, .045, .355, 1);
vertical-align: top;
white-space: nowrap;
cursor: pointer;
color: #fff;
}
</style>
</head>
<body>
<div class="frame">
<!-- XIAOMI LOGO-->
<div class="logo-frame">
<svg width="50" height="50" viewBox="0 0 193 193" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"><title>编组</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1"
points="1.78097075e-14 0.000125324675 192.540685 0.000125324675 192.540685 192.540058 1.78097075e-14 192.540058"></polygon>
</defs>
<g id="\u9875\u9762-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="\u7F16\u7EC4">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-2"></g>
<path d="M172.473071,20.1164903 C154.306633,2.02148701 128.188344,-1.78097075e-14 96.2706558,-1.78097075e-14 C64.312237,-1.78097075e-14 38.155724,2.0452987 19.9974318,20.1872987 C1.84352597,38.3261656 1.78097075e-14,64.4406948 1.78097075e-14,96.3640227 C1.78097075e-14,128.286724 1.84352597,154.415039 20.0049513,172.556412 C38.1638701,190.704052 64.3141169,192.540058 96.2706558,192.540058 C128.225942,192.540058 154.376815,190.704052 172.53636,172.556412 C190.694653,154.409399 192.540685,128.286724 192.540685,96.3640227 C192.540685,64.3999643 190.672721,38.2553571 172.473071,20.1164903"
id="Fill-1" fill="#FF6900" mask="url(#mask-2)"></path>
<path d="M89.1841721,131.948836 C89.1841721,132.594885 88.640263,133.130648 87.9779221,133.130648 L71.5585097,133.130648 C70.8848896,133.130648 70.338474,132.594885 70.338474,131.948836 L70.338474,89.0100961 C70.338474,88.3584078 70.8848896,87.8251513 71.5585097,87.8251513 L87.9779221,87.8251513 C88.640263,87.8251513 89.1841721,88.3584078 89.1841721,89.0100961 L89.1841721,131.948836 Z"
id="Fill-3" fill="#FFFFFF" mask="url(#mask-2)"></path>
<path d="M121.332896,131.948836 C121.332896,132.594885 120.786481,133.130648 120.121633,133.130648 L104.492393,133.130648 C103.821906,133.130648 103.275491,132.594885 103.275491,131.948836 L103.275491,131.788421 L103.275491,94.9022357 C103.259198,88.4342292 102.889491,81.7863818 99.5502146,78.445226 C96.6790263,75.5652649 91.3251562,74.9054305 85.7557276,74.7669468 L57.4242049,74.7669468 C56.7555977,74.7669468 56.2154484,75.3045896 56.2154484,75.9512649 L56.2154484,128.074424 L56.2154484,131.948836 C56.2154484,132.594885 55.6640198,133.130648 54.9954127,133.130648 L39.3555198,133.130648 C38.6875393,133.130648 38.1498964,132.594885 38.1498964,131.948836 L38.1498964,60.5996188 C38.1498964,59.9447974 38.6875393,59.4121675 39.3555198,59.4121675 L84.4786692,59.4121675 C96.2717211,59.4121675 108.599909,59.9498104 114.680036,66.0380831 C120.786481,72.1533006 121.332896,84.4595571 121.332896,96.2657682 L121.332896,131.948836 Z"
id="Fill-5" fill="#FFFFFF" mask="url(#mask-2)"></path>
<path d="M153.53056,131.948836 C153.53056,132.594885 152.978505,133.130648 152.316164,133.130648 L136.678778,133.130648 C136.010797,133.130648 135.467515,132.594885 135.467515,131.948836 L135.467515,60.5996188 C135.467515,59.9447974 136.010797,59.4121675 136.678778,59.4121675 L152.316164,59.4121675 C152.978505,59.4121675 153.53056,59.9447974 153.53056,60.5996188 L153.53056,131.948836 Z"
id="Fill-7" fill="#FFFFFF" mask="url(#mask-2)"></path>
</g>
</g>
</svg>
</div>
<!-- TITLE -->
<div class="title-frame">
<a id="titleArea"></a>
</div>
<!-- CONTENT -->
<div class="content-frame">
<a id="contentArea"></a>
</div>
<!-- BUTTON -->
<button onClick="window.close();" id="buttonArea"></button>
</div>
<script>
// get language (user language -> system language)
const locale = (localStorage.getItem('selectedLanguage')?? "''' + lang + '''").replaceAll('"','');
const language = locale.includes("-") ? locale.substring(0, locale.indexOf("-")).trim() : locale;
const status = "''' + status + '''";
console.log(locale);
// translation
let translation = {
zh: {
success: {
title: "认证完成",
content: "请关闭此页面,返回账号认证页面点击“下一步”",
button: "关闭页面"
},
fail: {
title: "认证失败",
content: "请关闭此页面,返回账号认证页面重新点击认链接进行认证。",
button: "关闭页面"
}
},
'zh-Hant': {
success: {
title: "認證完成",
content: "請關閉此頁面,返回帳號認證頁面點擊「下一步」。",
button: "關閉頁面"
},
fail: {
title: "認證失敗",
content: "請關閉此頁面,返回帳號認證頁面重新點擊認鏈接進行認證。",
button: "關閉頁面"
}
},
en: {
success: {
title: "Authentication Completed",
content: "Please close this page and return to the account authentication page to click NEXT",
button: "Close Page"
},
fail: {
title: "Authentication Failed",
content: "Please close this page and return to the account authentication page to click the authentication link again.",
button: "Close Page"
}
},
fr: {
success: {
title: "Authentification Terminée",
content: "Veuillez fermer cette page et revenir à la page d'authentification du compte pour cliquer sur « SUIVANT »",
button: "Fermer la page"
},
fail: {
title: "Échec de l'Authentification",
content: "Veuillez fermer cette page et revenir à la page d'authentification du compte pour cliquer de nouveau sur le lien d'authentification.",
button: "Fermer la page"
}
},
ru: {
success: {
title: "Подтверждение завершено",
content: "Пожалуйста, закройте эту страницу, вернитесь на страницу аутентификации учетной записи и нажмите кнопку «Далее».",
button: "Закрыть страницу"
},
fail: {
title: "Ошибка аутентификации",
content: "Пожалуйста, закройте эту страницу, вернитесь на страницу аутентификации учетной записи и повторите процесс аутентификации, щелкнув ссылку.",
button: "Закрыть страницу"
}
},
de: {
success: {
title: "Authentifizierung abgeschlossen",
content: "Bitte schließen Sie diese Seite, kehren Sie zur Kontobestätigungsseite zurück und klicken Sie auf „WEITER“.",
button: "Seite schließen"
},
fail: {
title: "Authentifizierung fehlgeschlagen",
content: "Bitte schließen Sie diese Seite, kehren Sie zur Kontobestätigungsseite zurück und wiederholen Sie den Authentifizierungsprozess, indem Sie auf den Link klicken.",
button: "Seite schließen"
}
},
es: {
success: {
title: "Autenticación completada",
content: "Por favor, cierre esta página, regrese a la página de autenticación de la cuenta y haga clic en 'SIGUIENTE'.",
button: "Cerrar página"
},
fail: {
title: "Error de autenticación",
content: "Por favor, cierre esta página, regrese a la página de autenticación de la cuenta y vuelva a hacer clic en el enlace de autenticación.",
button: "Cerrar página"
}
},
ja: {
success: {
title: "認証完了",
content: "このページを閉じて、アカウント認証ページに戻り、「次」をクリックしてください。",
button: "ページを閉じる"
},
fail: {
title: "認証失敗",
content: "このページを閉じて、アカウント認証ページに戻り、認証リンクを再度クリックしてください。",
button: "ページを閉じる"
}
}
}
// insert translate into page / match order: locale > language > english
document.title = translation[locale]?.[status]?.title ?? translation[language]?.[status]?.title ?? translation["en"]?.[status]?.title;
document.getElementById("titleArea").innerText = translation[locale]?.[status]?.title ?? translation[language]?.[status]?.title ?? translation["en"]?.[status]?.title;
document.getElementById("contentArea").innerText = translation[locale]?.[status]?.content ?? translation[language]?.[status]?.content ?? translation["en"]?.[status]?.content;
document.getElementById("buttonArea").innerText = translation[locale]?.[status]?.button ?? translation[language]?.[status]?.button ?? translation["en"]?.[status]?.button;
window.opener=null;
window.open('','_self');
window.close();
</script>
</body>
</html>
'''

View File

@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Notify entities for Xiaomi Home.
"""
from __future__ import annotations
import json
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.notify import NotifyEntity
from .miot.miot_spec import MIoTSpecAction
from .miot.miot_device import MIoTDevice, MIoTActionEntity
from .miot.const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for action in miot_device.action_list.get('notify', []):
new_entities.append(Notify(miot_device=miot_device, spec=action))
if new_entities:
async_add_entities(new_entities)
class Notify(MIoTActionEntity, NotifyEntity):
"""Notify entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecAction) -> None:
"""Initialize the Notify."""
super().__init__(miot_device=miot_device, spec=spec)
self._attr_extra_state_attributes = {}
action_in: str = ', '.join([
f'{prop.description_trans}({prop.format_})'
for prop in self.spec.in_])
self._attr_extra_state_attributes['action params'] = f'[{action_in}]'
async def async_send_message(
self, message: str, title: Optional[str] = None
) -> None:
"""Send a message."""
del title
if not message:
_LOGGER.error(
'action exec failed, %s(%s), empty action params',
self.name, self.entity_id)
return
try:
in_list: list = json.loads(message)
except json.JSONDecodeError:
_LOGGER.error(
'action exec failed, %s(%s), invalid action params format, %s',
self.name, self.entity_id, message)
return
if not isinstance(in_list, list) or len(in_list) != len(self.spec.in_):
_LOGGER.error(
'action exec failed, %s(%s), invalid action params, %s',
self.name, self.entity_id, message)
return
in_value: list[dict] = []
for index, prop in enumerate(self.spec.in_):
if type(in_list[index]).__name__ != prop.format_:
logging.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,
prop.format_, message)
return
in_value.append({'piid': prop.iid, 'value': in_list[index]})
return await self.action_async(in_list=in_value)

View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Number entities for Xiaomi Home.
"""
from __future__ import annotations
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.number import NumberEntity
from .miot.const import DOMAIN
from .miot.miot_spec import MIoTSpecProperty
from .miot.miot_device import MIoTDevice, MIoTPropertyEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for prop in miot_device.prop_list.get('number', []):
new_entities.append(Number(miot_device=miot_device, spec=prop))
if new_entities:
async_add_entities(new_entities)
class Number(MIoTPropertyEntity, NumberEntity):
"""Number entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
"""Initialize the Notify."""
super().__init__(miot_device=miot_device, spec=spec)
# Set device_class
self._attr_device_class = spec.device_class
# Set unit
if self.spec.external_unit:
self._attr_native_unit_of_measurement = self.spec.external_unit
# Set icon
if self.spec.icon:
self._attr_icon = self.spec.icon
# Set value range
if self._value_range:
self._attr_native_min_value = self._value_range['min']
self._attr_native_max_value = self._value_range['max']
self._attr_native_step = self._value_range['step']
@property
def native_value(self) -> Optional[float]:
"""Return the current value of the number."""
return self._value
async def async_set_native_value(self, value: float) -> None:
"""Update the current value."""
await self.set_property_async(value=value)

View File

@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Select entities for Xiaomi Home.
"""
from __future__ import annotations
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.select import SelectEntity
from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTPropertyEntity
from .miot.miot_spec import MIoTSpecProperty
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for prop in miot_device.prop_list.get('select', []):
new_entities.append(Select(miot_device=miot_device, spec=prop))
if new_entities:
async_add_entities(new_entities)
class Select(MIoTPropertyEntity, SelectEntity):
"""Select entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
"""Initialize the Select."""
super().__init__(miot_device=miot_device, spec=spec)
self._attr_options = list(self._value_list.values())
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self.set_property_async(
value=self.get_vlist_value(description=option))
@property
def current_option(self) -> Optional[str]:
"""Return the current selected option."""
return self.get_vlist_description(value=self._value)

View File

@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Sensor entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.sensor import SensorEntity, SensorDeviceClass
from homeassistant.components.sensor import DEVICE_CLASS_UNITS
from .miot.miot_device import MIoTDevice, MIoTPropertyEntity
from .miot.miot_spec import MIoTSpecProperty
from .miot.const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for prop in miot_device.prop_list.get('sensor', []):
new_entities.append(Sensor(miot_device=miot_device, spec=prop))
if new_entities:
async_add_entities(new_entities)
class Sensor(MIoTPropertyEntity, SensorEntity):
"""Sensor entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
"""Initialize the Sensor."""
super().__init__(miot_device=miot_device, spec=spec)
# Set device_class
if self._value_list:
self._attr_device_class = SensorDeviceClass.ENUM
self._attr_icon = 'mdi:message-text'
self._attr_native_unit_of_measurement = None
self._attr_options = list(self._value_list.values())
else:
self._attr_device_class = spec.device_class
if spec.external_unit:
self._attr_native_unit_of_measurement = spec.external_unit
else:
# device_class is not empty but unit is empty.
# Set the default unit according to device_class.
unit_sets = DEVICE_CLASS_UNITS.get(
self._attr_device_class, None)
self._attr_native_unit_of_measurement = list(
unit_sets)[0] if unit_sets else None
# Set icon
if spec.icon:
self._attr_icon = spec.icon
@property
def native_value(self) -> any:
"""Return the current value of the sensor."""
if self._value_range and isinstance(self._value, (int, float)):
if (
self._value < self._value_range['min']
or self._value > self._value_range['max']
):
_LOGGER.info(
'%s, data exception, out of range, %s, %s',
self.entity_id, self._value, self._value_range)
if self._value_list:
return self._value_list.get(self._value, None)
if isinstance(self._value, str):
return self._value[:255]
return self._value

View File

@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Switch entities for Xiaomi Home.
"""
from __future__ import annotations
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.switch import SwitchEntity
from .miot.miot_device import MIoTDevice
from .miot.miot_spec import MIoTSpecProperty
from .miot.miot_device import MIoTPropertyEntity
from .miot.const import DOMAIN
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for prop in miot_device.prop_list.get('switch', []):
new_entities.append(Switch(miot_device=miot_device, spec=prop))
if new_entities:
async_add_entities(new_entities)
class Switch(MIoTPropertyEntity, SwitchEntity):
"""Switch entities for Xiaomi Home."""
# pylint: disable=unused-argument
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
"""Initialize the Switch."""
super().__init__(miot_device=miot_device, spec=spec)
# Set device_class
self._attr_device_class = spec.device_class
@property
def is_on(self) -> bool:
"""On/Off state."""
return self._value is True
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.set_property_async(value=True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.set_property_async(value=False)
async def async_toggle(self, **kwargs: Any) -> None:
"""Toggle the switch."""
await self.set_property_async(value=not self.is_on)

View File

@ -0,0 +1,41 @@
"""Check if a file is a valid JSON file.
Usage:
python json_check.py [JSON file path]
Example:
python json_check.py multi_lang.json
"""
import argparse
import json
import sys
import os
def check_json_file(file_path):
try:
with open(file_path, "r", encoding="utf-8") as file:
json.load(file)
return True
except FileNotFoundError:
print(file_path, "is not found.")
return False
except json.JSONDecodeError:
print(file_path, "is not a valid JSON file.")
return False
def main():
parser = argparse.ArgumentParser(
description="Check if a file is a valid JSON file.")
parser.add_argument("file_path", help="JSON file path")
args = parser.parse_args()
script_name = os.path.basename(__file__)
file_name = os.path.basename(args.file_path)
if not check_json_file(args.file_path):
print(args.file_path, script_name, "FAIL")
sys.exit(1)
print(script_name, file_name, "PASS")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,149 @@
"""Check if conversion rules are valid.
The files to be checked are in the directory of ../miot/specs/
To run this script, PYTHONPATH must be set first.
See test_all.sh for the usage.
You can run all tests by running:
```
./test_all.sh
```
"""
import sys
import os
import json
def load_json(file_path: str) -> dict:
"""Load json file."""
with open(file_path, "r", encoding="utf-8") as file:
data = json.load(file)
return data
def dict_str_str(d: dict) -> bool:
"""restricted format: dict[str, str]"""
if not isinstance(d, dict):
return False
for k, v in d.items():
if not isinstance(k, str) or not isinstance(v, str):
return False
return True
def dict_str_dict(d: dict) -> bool:
"""restricted format: dict[str, dict]"""
if not isinstance(d, dict):
return False
for k, v in d.items():
if not isinstance(k, str) or not isinstance(v, dict):
return False
return True
def nested_2_dict_str_str(d: dict) -> bool:
"""restricted format: dict[str, dict[str, str]]"""
if not dict_str_dict(d):
return False
for v in d.values():
if not dict_str_str(v):
return False
return True
def nested_3_dict_str_str(d: dict) -> bool:
"""restricted format: dict[str, dict[str, dict[str, str]]]"""
if not dict_str_dict(d):
return False
for v in d.values():
if not nested_2_dict_str_str(v):
return False
return True
def spec_filter(d: dict) -> bool:
"""restricted format: dict[str, dict[str, list<str>]]"""
if not dict_str_dict(d):
return False
for value in d.values():
for k, v in value.items():
if not isinstance(k, str) or not isinstance(v, list):
return False
if not all(isinstance(i, str) for i in v):
return False
return True
def bool_trans(d: dict) -> bool:
"""dict[str, dict[str, str] | dict[str, dict[str, str]] ]"""
if not isinstance(d, dict):
return False
if "data" not in d or "translate" not in d:
return False
if not dict_str_str(d["data"]):
return False
if not nested_3_dict_str_str(d["translate"]):
return False
return True
def main():
script_name = os.path.basename(__file__)
source_dir = "../miot/specs"
if not bool_trans(load_json(f"{source_dir}/bool_trans.json")):
print(script_name, "bool_trans FAIL")
sys.exit(1)
if not nested_3_dict_str_str(load_json(f"{source_dir}/multi_lang.json")):
print(script_name, "multi_lang FAIL")
sys.exit(1)
if not spec_filter(load_json(f"{source_dir}/spec_filter.json")):
print(script_name, "spec_filter FAIL")
sys.exit(1)
if not nested_2_dict_str_str(load_json(
f"{source_dir}/std_ex_actions.json")):
print(script_name, "std_ex_actions.json FAIL")
sys.exit(1)
if not nested_2_dict_str_str(load_json(
f"{source_dir}/std_ex_devices.json")):
print(script_name, "std_ex_devices.json FAIL")
sys.exit(1)
if not nested_2_dict_str_str(load_json(f"{source_dir}/std_ex_events.json")):
print(script_name, "std_ex_events.json FAIL")
sys.exit(1)
if not nested_2_dict_str_str(load_json(
f"{source_dir}/std_ex_properties.json")):
print(script_name, "std_ex_properties.json FAIL")
sys.exit(1)
if not nested_2_dict_str_str(load_json(
f"{source_dir}/std_ex_services.json")):
print(script_name, "std_ex_services.json FAIL")
sys.exit(1)
if not nested_2_dict_str_str(load_json(f"{source_dir}/std_ex_values.json")):
print(script_name, "std_ex_values.json FAIL")
sys.exit(1)
source_dir = "../miot/i18n"
if not nested_3_dict_str_str(load_json(f"{source_dir}/de.json")):
print(script_name, "i18n de.json FAIL")
sys.exit(1)
if not nested_3_dict_str_str(load_json(f"{source_dir}/en.json")):
print(script_name, "i18n en.json FAIL")
sys.exit(1)
if not nested_3_dict_str_str(load_json(f"{source_dir}/es.json")):
print(script_name, "i18n es.json FAIL")
sys.exit(1)
if not nested_3_dict_str_str(load_json(f"{source_dir}/fr.json")):
print(script_name, "i18n fr.json FAIL")
sys.exit(1)
if not nested_3_dict_str_str(load_json(f"{source_dir}/ja.json")):
print(script_name, "i18n ja.json FAIL")
sys.exit(1)
if not nested_3_dict_str_str(load_json(f"{source_dir}/ru.json")):
print(script_name, "i18n ru.json FAIL")
sys.exit(1)
if not nested_3_dict_str_str(load_json(f"{source_dir}/zh-Hans.json")):
print(script_name, "i18n zh-Hans.json FAIL")
sys.exit(1)
if not nested_3_dict_str_str(load_json(f"{source_dir}/zh-Hant.json")):
print(script_name, "i18n zh-Hant.json FAIL")
sys.exit(1)
print(script_name, "PASS")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,35 @@
#!/bin/bash
set -e
# Get the script path.
script_path=$(dirname "$0")
# Change to the script path.
cd "$script_path"
# Set PYTHONPATH.
cd ..
export PYTHONPATH=`pwd`
echo "PYTHONPATH=$PYTHONPATH"
cd -
# Run the tests.
export source_dir="../miot/specs"
python3 json_format.py $source_dir/bool_trans.json
python3 json_format.py $source_dir/multi_lang.json
python3 json_format.py $source_dir/spec_filter.json
python3 json_format.py $source_dir/std_ex_actions.json
python3 json_format.py $source_dir/std_ex_devices.json
python3 json_format.py $source_dir/std_ex_events.json
python3 json_format.py $source_dir/std_ex_properties.json
python3 json_format.py $source_dir/std_ex_services.json
python3 json_format.py $source_dir/std_ex_values.json
export source_dir="../miot/i18n"
python3 json_format.py $source_dir/de.json
python3 json_format.py $source_dir/en.json
python3 json_format.py $source_dir/es.json
python3 json_format.py $source_dir/fr.json
python3 json_format.py $source_dir/ja.json
python3 json_format.py $source_dir/ru.json
python3 json_format.py $source_dir/zh-Hans.json
python3 json_format.py $source_dir/zh-Hant.json
python3 rule_format.py

View File

@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Text entities for Xiaomi Home.
"""
from __future__ import annotations
import json
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.text import TextEntity
from .miot.const import DOMAIN
from .miot.miot_spec import MIoTSpecAction, MIoTSpecProperty
from .miot.miot_device import MIoTActionEntity, MIoTDevice, MIoTPropertyEntity
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for prop in miot_device.prop_list.get('text', []):
new_entities.append(Text(miot_device=miot_device, spec=prop))
for action in miot_device.action_list.get('action_text', []):
new_entities.append(ActionText(
miot_device=miot_device, spec=action))
if new_entities:
async_add_entities(new_entities)
class Text(MIoTPropertyEntity, TextEntity):
"""Text entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None:
"""Initialize the Text."""
super().__init__(miot_device=miot_device, spec=spec)
@property
def native_value(self) -> Optional[str]:
"""Return the current text value."""
if isinstance(self._value, str):
return self._value[:255]
return self._value
async def async_set_value(self, value: str) -> None:
"""Set the text value."""
await self.set_property_async(value=value)
class ActionText(MIoTActionEntity, TextEntity):
"""Text entities for Xiaomi Home."""
def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecAction) -> None:
super().__init__(miot_device=miot_device, spec=spec)
self._attr_extra_state_attributes = {}
self._attr_native_value = ''
action_in: str = ', '.join([
f'{prop.description_trans}({prop.format_})'
for prop in self.spec.in_])
self._attr_extra_state_attributes['action params'] = f'[{action_in}]'
# For action debug
self.action_platform = 'action_text'
async def async_set_value(self, value: str) -> None:
if not value:
return
in_list: list = None
try:
in_list = json.loads(value)
except json.JSONDecodeError as e:
_LOGGER.error(
'action exec failed, %s(%s), invalid action params format, %s',
self.name, self.entity_id, value)
raise ValueError(
f'action exec failed, {self.name}({self.entity_id}), '
f'invalid action params format, {value}') from e
if not isinstance(in_list, list) or len(in_list) != len(self.spec.in_):
_LOGGER.error(
'action exec failed, %s(%s), invalid action params, %s',
self.name, self.entity_id, value)
raise ValueError(
f'action exec failed, {self.name}({self.entity_id}), '
f'invalid action params, {value}')
in_value: list[dict] = []
for index, prop in enumerate(self.spec.in_):
if type(in_list[index]).__name__ != prop.format_:
logging.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, prop.format_, value)
raise ValueError(
f'action exec failed, {self.name}({self.entity_id}), '
f'invalid params item, which item({prop.description_trans})'
f' in the list must be {prop.format_}, {value}')
in_value.append({'piid': prop.iid, 'value': in_list[index]})
self._attr_native_value = value
if await self.action_async(in_list=in_value):
self.async_write_ha_state()

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "Xiaomi Home Integration",
"step": {
"eula": {
"title": "Risikohinweis",
"description": "1. Ihre **Xiaomi-Benutzerinformationen und Geräteinformationen** werden in Ihrem Home Assistant-System gespeichert. **Xiaomi kann die Sicherheit des Home Assistant-Speichermechanismus nicht garantieren**. Sie sind dafür verantwortlich, Ihre Informationen vor Diebstahl zu schützen.\r\n2. Diese Integration wird von der Open-Source-Community unterstützt und gewartet. Es können jedoch Stabilitätsprobleme oder andere Probleme auftreten. Wenn Sie auf ein Problem stoßen, das mit dieser Integration zusammenhängt, sollten Sie **die Open-Source-Community um Hilfe bitten, anstatt sich an den Xiaomi Home Kundendienst zu wenden**.\r\n3. Sie benötigen bestimmte technische Fähigkeiten, um Ihre lokale Laufzeitumgebung zu warten. Diese Integration ist für Anfänger nicht geeignet. \r\n4. Bevor Sie diese Integration verwenden, lesen Sie bitte die **README-Datei sorgfältig durch**.\r\n5. Um eine stabile Nutzung der Integration zu gewährleisten und Missbrauch der Schnittstelle zu verhindern, **darf diese Integration nur in Home Assistant verwendet werden. Weitere Informationen finden Sie in der LICENSE**.\r\n",
"data": {
"eula": "Ich habe das oben genannte Risiko zur Kenntnis genommen und übernehme freiwillig die damit verbundenen Risiken durch die Verwendung der Integration."
}
},
"auth_config": {
"title": "Grundkonfiguration",
"description": "### Anmeldegebiet\r\nWählen Sie das Gebiet, in dem sich Ihr Xiaomi Home-Konto befindet. Sie können es in der Xiaomi Home App unter `Mein (unten im Menü) > Weitere Einstellungen > Über Xiaomi Home` überprüfen.\r\n### Sprache\r\nWählen Sie die Sprache, in der Geräte und Entitätsnamen angezeigt werden. Teile von Sätzen, die nicht übersetzt sind, werden in Englisch angezeigt.\r\n### OAuth2-Authentifizierungs-Umleitungs-URL\r\nDie Umleitungs-URL für die OAuth2-Authentifizierung lautet **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. Home Assistant muss im selben lokalen Netzwerk wie das aktuelle Betriebsterminal (z. B. ein persönlicher Computer) und das Betriebsterminal muss über diese Adresse auf die Home Assistant-Homepage zugreifen können. Andernfalls kann die Anmeldeauthentifizierung fehlschlagen.\r\n### Hinweis\r\n- Für Benutzer mit Hunderten oder mehr Mi Home-Geräten wird das erste Hinzufügen der Integration einige Zeit in Anspruch nehmen. Bitte haben Sie Geduld.\r\n- Wenn Home Assistant in einer Docker-Umgebung läuft, stellen Sie bitte sicher, dass der Docker-Netzwerkmodus auf host eingestellt ist, da sonst die lokale Steuerungsfunktion möglicherweise nicht richtig funktioniert.\r\n- Die lokale Steuerungsfunktion der Integration hat einige Abhängigkeiten. Bitte lesen Sie das README sorgfältig.\r\n",
"data": {
"cloud_server": "Anmeldegebiet",
"integration_language": "Sprache",
"oauth_redirect_url": "OAuth2-Authentifizierungs-Umleitungs-URL"
}
},
"oauth_error": {
"title": "Fehler bei der Anmeldung",
"description": "Klicken Sie auf \"Weiter\", um es erneut zu versuchen."
},
"devices_filter": {
"title": "Familie und Geräte auswählen",
"description": "## Gebrauchsanweisung\r\n### Steuerungsmodus\r\n- Automatisch: Wenn im lokalen Netzwerk ein verfügbarer Xiaomi-Zentralgateway vorhanden ist, wird Home Assistant bevorzugt Steuerbefehle über den Zentralgateway senden, um eine lokale Steuerung zu ermöglichen. Wenn im lokalen Netzwerk kein Zentralgateway vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden, um eine lokale Steuerung zu ermöglichen. Nur wenn die oben genannten Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Steuerbefehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden nur über die Cloud gesendet.\r\n### Familienimport für importierte Geräte\r\nDie Integration fügt Geräte aus den ausgewählten Familien hinzu.\r\n### Raumnamensynchronisationsmodus\r\nWenn Geräte von der Xiaomi Home App zu Home Assistant synchronisiert werden, wird die Bezeichnung des Bereichs, in dem sich die Geräte in Home Assistant befinden, nach folgenden Regeln benannt. Beachten Sie, dass das Synchronisieren von Geräten den von Xiaomi Home App festgelegten Familien- und Raum-Einstellungen nicht ändert.\r\n- Nicht synchronisieren: Das Gerät wird keinem Bereich hinzugefügt.\r\n- Andere Optionen: Der Bereich, in den das Gerät aufgenommen wird, wird nach dem Namen der Familie oder des Raums in der Xiaomi Home App benannt.\r\n### Action-Debug-Modus\r\nFür von MIoT-Spec-V2 definierte Gerätemethoden wird neben der Benachrichtigungs-Entität auch eine Texteingabe-Entität generiert. Damit können Sie bei der Fehlerbehebung Steuerbefehle an das Gerät senden.\r\n### Verstecke Nicht-Standard-Entitäten\r\nVerstecke Entitäten, die von nicht standardmäßigen MIoT-Spec-V2-Instanzen mit einem Namen beginnen, der mit einem \"*\" beginnt.\r\n\r\n&emsp;\r\n### Hallo {nick_name}! Bitte wählen Sie den Steuerungsmodus der Integration sowie die Familie aus, in der sich die hinzuzufügenden Geräte befinden.\r\n",
"data": {
"ctrl_mode": "Steuerungsmodus",
"home_infos": "Familienimport für importierte Geräte",
"area_name_rule": "Raumnamensynchronisationsmodus",
"action_debug": "Action-Debug-Modus",
"hide_non_standard_entities": "Verstecke Nicht-Standard-Entitäten"
}
}
},
"progress": {
"oauth": "### {link_left}Klicken Sie hier, um sich anzumelden{link_right}\r\n(Sie werden nach erfolgreicher Anmeldung automatisch zur nächsten Seite weitergeleitet)"
},
"error": {
"eula_not_agree": "Bitte lesen Sie den Risikohinweis.",
"get_token_error": "Fehler beim Abrufen von Anmeldeinformationen (OAuth-Token).",
"get_homeinfo_error": "Fehler beim Abrufen von Familieninformationen.",
"mdns_discovery_error": "Lokaler Geräteerkennungsdienst ist nicht verfügbar.",
"get_cert_error": "Fehler beim Abrufen des Gateway-Zertifikats.",
"no_family_selected": "Keine Familie ausgewählt.",
"no_devices": "In der ausgewählten Familie sind keine Geräte enthalten. Bitte wählen Sie eine Familie mit Geräten aus und fahren Sie fort.",
"no_central_device": "Im Modus \"Xiaomi Central Hub Gateway\" muss ein verfügbares Xiaomi Central Hub Gateway im lokalen Netzwerk von Home Assistant vorhanden sein. Stellen Sie sicher, dass die ausgewählte Familie diese Anforderungen erfüllt."
},
"abort": {
"network_connect_error": "Konfiguration fehlgeschlagen. Netzwerkverbindung fehlgeschlagen. Überprüfen Sie die Netzwerkkonfiguration des Geräts.",
"already_configured": "Dieser Benutzer hat die Konfiguration bereits abgeschlossen. Gehen Sie zur Integrationsseite und klicken Sie auf die Schaltfläche \"Konfiguration\", um die Konfiguration zu ändern.",
"invalid_auth_info": "Authentifizierungsinformationen sind abgelaufen. Gehen Sie zur Integrationsseite und klicken Sie auf die Schaltfläche \"Konfiguration\", um die Authentifizierung erneut durchzuführen.",
"config_flow_error": "Integrationskonfigurationsfehler: {error}"
}
},
"options": {
"step": {
"auth_config": {
"title": "Authentifizierungskonfiguration",
"description": "Lokale Authentifizierungsinformationen sind abgelaufen. Starten Sie die Authentifizierung erneut.\r\n### Aktuelles Anmeldegebiet: {cloud_server}\r\n### OAuth2-Authentifizierungs-Umleitungs-URL\r\nDie Umleitungs-URL für die OAuth2-Authentifizierung lautet **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. Home Assistant muss im selben lokalen Netzwerk wie das aktuelle Betriebsterminal (z. B. ein persönlicher Computer) und das Betriebsterminal muss über diese Adresse auf die Home Assistant-Homepage zugreifen können. Andernfalls kann die Anmeldeauthentifizierung fehlschlagen.\r\n",
"data": {
"oauth_redirect_url": "OAuth2-Authentifizierungs-Umleitungs-URL"
}
},
"oauth_error": {
"title": "Fehler bei der Anmeldung",
"description": "Klicken Sie auf \"Weiter\", um es erneut zu versuchen."
},
"config_options": {
"title": "Konfigurationsoptionen",
"description": "### Hallo {nick_name}!\r\n\r\nXiaomi Home-Konto-ID: {uid}\r\nAktuelles Anmeldegebiet: {cloud_server}\r\n\r\nWählen Sie die Optionen aus, die Sie erneut konfigurieren möchten, und klicken Sie dann auf \"Weiter\".\r\n",
"data": {
"integration_language": "Integrationsprache",
"update_user_info": "Benutzerinformationen aktualisieren",
"update_devices": "Geräteliste aktualisieren",
"action_debug": "Action-Debug-Modus",
"hide_non_standard_entities": "Verstecke Nicht-Standard-Entitäten",
"update_trans_rules": "Entitätskonvertierungsregeln aktualisieren (globale konfiguration)",
"update_lan_ctrl_config": "LAN-Steuerungskonfiguration aktualisieren (globale Konfiguration)"
}
},
"update_user_info": {
"title": "Benutzernamen aktualisieren",
"description": "{nick_name}! Bitte geben Sie unten Ihren Benutzernamen ein.\r\n",
"data": {
"nick_name": "Benutzername"
}
},
"devices_filter": {
"title": "Familie und Geräte neu auswählen",
"description": "## Gebrauchsanweisung\r\n### Steuerungsmodus\r\n- Automatisch: Wenn im lokalen Netzwerk ein verfügbarer Xiaomi-Zentralgateway vorhanden ist, wird Home Assistant bevorzugt Steuerbefehle über den Zentralgateway senden, um eine lokale Steuerung zu ermöglichen. Wenn im lokalen Netzwerk kein Zentralgateway vorhanden ist, wird versucht, Steuerbefehle über das Xiaomi-OT-Protokoll zu senden, um eine lokale Steuerung zu ermöglichen. Nur wenn die oben genannten Bedingungen für die lokale Steuerung nicht erfüllt sind, werden die Steuerbefehle über die Cloud gesendet.\r\n- Cloud: Steuerbefehle werden nur über die Cloud gesendet.\r\n### Familienimport für importierte Geräte\r\nDie Integration fügt Geräte aus den ausgewählten Familien hinzu.\r\n&emsp;\r\n### Hallo {nick_name}! Bitte wählen Sie den Steuerungsmodus der Integration sowie die Familie aus, in der sich die hinzuzufügenden Geräte befinden.\r\n",
"data": {
"ctrl_mode": "Steuerungsmodus",
"home_infos": "Familienimport für importierte Geräte"
}
},
"update_trans_rules": {
"title": "Entitätskonvertierungsregeln aktualisieren",
"description": "## Gebrauchsanweisung\r\n- Aktualisieren Sie die Entitätsinformationen der Geräte im aktuellen Integrationsinstanz, einschließlich der mehrsprachigen SPEC-Konfiguration, der SPEC-Booleschen Übersetzung und der SPEC-Modellfilterung.\r\n- **Warnung: Diese Konfiguration ist eine globale Konfiguration** und aktualisiert direkt den lokalen Cache. Wenn in anderen Integrationsinstanzen Geräte desselben Modells vorhanden sind, werden diese nach dem Neuladen der entsprechenden Instanzen ebenfalls aktualisiert.\r\n- Dieser Vorgang kann einige Zeit in Anspruch nehmen, bitte haben Sie Geduld. Wählen Sie \"Bestätigen Sie das Update\" und klicken Sie auf \"Weiter\", um **{urn_count}** Regeln zu aktualisieren, andernfalls überspringen Sie das Update.\r\n",
"data": {
"confirm": "Bestätigen"
}
},
"update_lan_ctrl_config": {
"title": "LAN-Steuerungskonfiguration aktualisieren",
"description": "## Gebrauchsanweisung\r\nAktualisieren Sie die Konfigurationsinformationen für **LAN-Steuerung von Xiaomi Home-Geräten**. Wenn die Cloud und das zentrale Gateway die Geräte nicht steuern können, versucht die Integration, die Geräte über das LAN zu steuern; wenn keine Netzwerkkarte ausgewählt ist, wird die LAN-Steuerung nicht aktiviert.\r\n- Derzeit werden nur **SPEC v2** WiFi-Geräte im LAN unterstützt. Einige ältere Geräte unterstützen möglicherweise keine Steuerung oder Eigenschaftssynchronisierung.\r\n- Bitte wählen Sie die Netzwerkkarte(n) im selben Netzwerk wie die Geräte aus (mehrere Auswahlen werden unterstützt). Wenn die ausgewählte Netzwerkkarte zwei oder mehr Verbindungen im selben Netzwerk hat, wird empfohlen, die mit der besten Netzwerkverbindung auszuwählen, da sonst die **normale Verwendung der Geräte beeinträchtigt werden kann**.\r\n- **Wenn es im LAN Endgeräte (Gateways, Mobiltelefone usw.) gibt, die lokale Steuerung unterstützen, kann das Aktivieren des LAN-Abonnements lokale Automatisierung oder Geräteanomalien verursachen. Bitte verwenden Sie es mit Vorsicht**.\r\n- **Warnung: Diese Konfiguration ist global und Änderungen wirken sich auf andere Integrationsinstanzen aus. Bitte ändern Sie sie mit Vorsicht**.\r\n{notice_net_dup}\r\n",
"data": {
"net_interfaces": "Bitte wählen Sie die zu verwendende Netzwerkkarte aus",
"enable_subscribe": "LAN-Abonnement aktivieren"
}
},
"config_confirm": {
"title": "Bestätigen Sie die Konfiguration",
"description": "**{nick_name}**, bitte bestätigen Sie die neuesten Konfigurationsinformationen und klicken Sie dann auf \"Senden\". Die Integration wird mit den aktualisierten Konfigurationen erneut geladen.\r\n\r\nIntegrationsprache:\t{lang_new}\r\nBenutzername:\t{nick_name_new}\r\nAction-Debug-Modus:\t{action_debug}\r\nVerstecke Nicht-Standard-Entitäten:\t{hide_non_standard_entities}\r\nGeräteänderungen:\t{devices_add} neue Geräte hinzufügen, {devices_remove} Geräte entfernen\r\nKonvertierungsregeländerungen:\tInsgesamt {trans_rules_count} Regeln, aktualisiert {trans_rules_count_success} Regeln\r\n",
"data": {
"confirm": "Änderungen bestätigen"
}
}
},
"progress": {
"oauth": "### {link_left}Klicken Sie hier, um sich erneut anzumelden{link_right}"
},
"error": {
"not_auth": "Nicht authentifiziert. Klicken Sie auf den Authentifizierungslink, um die Benutzeridentität zu authentifizieren.",
"get_token_error": "Fehler beim Abrufen von Anmeldeinformationen (OAuth-Token).",
"get_homeinfo_error": "Fehler beim Abrufen von Home-Informationen.",
"get_cert_error": "Fehler beim Abrufen des Zentralzertifikats.",
"no_family_selected": "Keine Familie ausgewählt.",
"no_devices": "In der ausgewählten Familie sind keine Geräte vorhanden. Bitte wählen Sie eine Familie mit Geräten und fahren Sie dann fort.",
"no_central_device": "Der Modus \"Zentral Gateway\" erfordert ein verfügbares Xiaomi-Zentral-Gateway im lokalen Netzwerk, in dem Home Assistant installiert ist. Überprüfen Sie, ob die ausgewählte Familie diese Anforderung erfüllt.",
"mdns_discovery_error": "Lokaler Geräteerkennungsdienstfehler.",
"update_config_error": "Fehler beim Aktualisieren der Konfigurationsinformationen.",
"not_confirm": "Änderungen wurden nicht bestätigt. Bitte bestätigen Sie die Auswahl, bevor Sie sie einreichen."
},
"abort": {
"network_connect_error": "Konfiguration fehlgeschlagen. Netzwerkverbindungsfehler. Überprüfen Sie die Netzwerkkonfiguration des Geräts.",
"options_flow_error": "Integrationsneukonfigurationsfehler: {error}",
"re_add": "Fügen Sie die Integration erneut hinzu. Fehlermeldung: {error}",
"storage_error": "Integrations-Speichermodulfehler. Bitte versuchen Sie es erneut oder fügen Sie die Integration erneut hinzu: {error}",
"inconsistent_account": "Kontoinformationen sind inkonsistent. Bitte melden Sie sich mit den richtigen Kontoinformationen an."
}
}
}

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "Xiaomi Home Integration",
"step": {
"eula": {
"title": "Risk Notice",
"description": "1. Your Xiaomi user information and device information will be stored in the Home Assistant system. **Xiaomi cannot guarantee the security of the Home Assistant storage mechanism**. You are responsible for preventing your information from being stolen.\r\n2. This integration is maintained by the open-source community. There may be stability issues or other problems. When encountering issues or bugs of this integration, **you should seek help from the open-source community rather than contacting Xiaomi customer service**.\r\n3. You need some technical ability to maintain your local operating environment. The integration is not user-friendly for beginners.\r\n4. Please read the README file before starting.\n\n5. To ensure stable use of the integration and prevent interface abuse, **this integration is only allowed to be used in Home Assistant. For details, please refer to the LICENSE**.\r\n",
"data": {
"eula": "I am aware of the above risks and willing to voluntarily assume any risks associated with the use of the integration."
}
},
"auth_config": {
"title": "Basic configuration",
"description": "### Login Region\r\nSelect the region of your Xiaomi account. You can find it in the Xiaomi Home APP > Profile (located in the menu at the bottom) > Additional settings > About Xiaomi Home.\r\n### Language\r\nSelect the language of the device and entity names. Some sentences without translation will be displayed in English.\r\n### OAuth2 Redirect URL\r\nThe OAuth2 authentication redirect address is **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. The Home Assistant needs to be in the same local area network as the current operating terminal (e.g., the personal computer) and the operating terminal can access the Home Assistant home page through this address. Otherwise, the login authentication may fail.\r\n### Note\r\n- For users with hundreds or more Mi Home devices, the initial addition of the integration will take some time. Please be patient.\r\n- If Home Assistant is running in a Docker environment, please ensure that the Docker network mode is set to host, otherwise local control functionality may not work properly.\r\n- The local control functionality of the integration has some dependencies. Please read the README carefully.\r\n",
"data": {
"cloud_server": "Login Region",
"integration_language": "Language",
"oauth_redirect_url": "OAuth2 Redirect URL"
}
},
"oauth_error": {
"title": "Login Error",
"description": "Click NEXT to try again."
},
"devices_filter": {
"title": "Select Home and Devices",
"description": "## Usage Instructions\r\n### Control mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi LAN control function. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Import devices from home\r\nThe integration will add devices from the selected homes.\n### Room name synchronizing mode\nWhen importing devices from Xiaomi Home APP to Home Assistant, the naming convention of the area where the device is added to is as follows. Note that the device synchronizing process does not change the home or room settings in Xiaomi Home APP.\r\n- Do not synchronize: The device will not be added to any area.\r\n- Other options: The device will be added to an area named as the home and/or room name that already exists in Xiaomi Home APP.\r\n### Debug mode for action\r\nFor the action defined in MIoT-Spec-V2 of the device, a Text entity along with a Notify entity will be created, in which you can send control commands to the device for debugging.\r\n### Hide non-standard created entities\r\nHide the entities generated from non-standard MIoT-Spec-V2 instances, whose names begin with \"*\".\r\n\r\n&emsp;\r\n### Hello {nick_name}, please select the integration control mode and the home where the device you want to import.\r\n",
"data": {
"ctrl_mode": "Control mode",
"home_infos": "Import devices from home",
"area_name_rule": "Room name synchronizing mode",
"action_debug": "Debug mode for action",
"hide_non_standard_entities": "Hide non-standard created entities"
}
}
},
"progress": {
"oauth": "### {link_left}Click here to login{link_right}\r\n(You will be automatically redirected to the next page after a successful login)"
},
"error": {
"eula_not_agree": "Please read the risk notice.",
"get_token_error": "Failed to retrieve login authorization information (OAuth token).",
"get_homeinfo_error": "Failed to retrieve home information.",
"mdns_discovery_error": "Local device discovery service exception.",
"get_cert_error": "Failed to retrieve the central hub gateway certificate.",
"no_family_selected": "No home selected.",
"no_devices": "The selected home does not have any devices. Please choose a home containing devices and continue.",
"no_central_device": "[Central Hub Gateway Mode] requires a Xiaomi central hub gateway available in the local network where Home Assistant exists. Please check if the selected home meets the requirement."
},
"abort": {
"network_connect_error": "Configuration failed. The network connection is abnormal. Please check the equipment network configuration.",
"already_configured": "Configuration for this user is already completed. Please go to the integration page and click the CONFIGURE button for modifications.",
"invalid_auth_info": "Authentication information has expired. Please go to the integration page and click the CONFIGURE button to re-authenticate.",
"config_flow_error": "Integration configuration error: {error}."
}
},
"options": {
"step": {
"auth_config": {
"title": "Authentication Configuration",
"description": "Local authentication information has expired. Please restart the authentication process.\r\n### Current Login Region: {cloud_server}\r\n### OAuth2 Redirect URL\r\nThe OAuth2 authentication redirect address is **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. The Home Assistant needs to be in the same local area network as the current operating terminal (e.g., the personal computer) and the operating terminal can access the Home Assistant home page through this address. Otherwise, the login authentication may fail.\r\n",
"data": {
"oauth_redirect_url": "OAuth2 Redirect URL"
}
},
"oauth_error": {
"title": "An error occurred during login.",
"description": "Click NEXT to retry."
},
"config_options": {
"title": "Configuration Options",
"description": "### Hello, {nick_name}\r\n\r\nXiaomi ID: {uid}\r\nCurrent Login Region: {cloud_server}\r\n\r\nPlease select the options you need to configure, then click NEXT.\r\n",
"data": {
"integration_language": "Integration Language",
"update_user_info": "Update user information",
"update_devices": "Update device list",
"action_debug": "Debug mode for action",
"hide_non_standard_entities": "Hide non-standard created entities",
"update_trans_rules": "Update entity conversion rules",
"update_lan_ctrl_config": "Update LAN control configuration"
}
},
"update_user_info": {
"title": "Update User Nickname",
"description": "Hello {nick_name}, you can modify your custom nickname below.\r\n",
"data": {
"nick_name": "Nick Name"
}
},
"devices_filter": {
"title": "Re-select Home and Devices",
"description": "## Usage Instructions\r\n### Control mode\r\n- Auto: When there is an available Xiaomi central hub gateway in the local area network, Home Assistant will prioritize sending device control commands through the central hub gateway to achieve local control. If there is no central hub gateway in the local area network, it will attempt to send control commands through Xiaomi LAN control function. Only when the above local control conditions are not met, the device control commands will be sent through the cloud.\r\n- Cloud: All control commands are sent through the cloud.\r\n### Import devices from home\r\nThe integration will add devices from the selected homes.\r\n&emsp;\r\n### Hello {nick_name}, please select the integration control mode and the home where the device you want to import.\r\n",
"data": {
"ctrl_mode": "Control mode",
"home_infos": "Import devices from home"
}
},
"update_trans_rules": {
"title": "Update Entities Transformation Rules",
"description": "## Usage Instructions\r\n- Update the entity information of devices in the current integration instance, including MIoT-Spec-V2 multilingual configuration, boolean translation, and model filtering.\r\n- **Warning**: This is a global configuration and will update the local cache. It will affect all integration instances.\r\n- This operation will take some time, please be patient. Check \"Confirm Update\" and click \"Next\" to start updating **{urn_count}** rules, otherwise skip the update.\r\n",
"data": {
"confirm": "Confirm the update"
}
},
"update_lan_ctrl_config": {
"title": "Update lan control configuration",
"description": "## Usage Instructions\r\nUpdate the configurations for Xiaomi LAN control function. When the cloud and the central hub gateway cannot control the devices, the integration will attempt to control the devices through the LAN. If no network card is selected, the LAN control function will not take effect.\r\n- Only MIoT-Spec-V2 compatible IP devices in the LAN are supported. Some devices produced before 2020 may not support LAN control or LAN subscription.\r\n- Please select the network card(s) on the same network as the devices to be controlled. Multiple network cards can be selected. If Home Assistant have two or more connections to the local area network because of the multiple selection of the network cards, it is recommended to select the one with the best network connection, otherwise it may have bad effect on the devices.\r\n- If there are terminal devices (Xiaomi speaker with screen, mobile phone, etc.) in the LAN that support local control, enabling LAN subscription may cause local automation and device anomalies.\r\n- **Warning**: This is a global configuration. It will affect all integration instances. Please use it with caution.\r\n{notice_net_dup}\r\n",
"data": {
"net_interfaces": "Please select the network card to use",
"enable_subscribe": "Enable LAN subscription"
}
},
"config_confirm": {
"title": "Confirm Configuration",
"description": "Hello **{nick_name}**, please confirm the latest configuration information and then Click SUBMIT.\r\nThe integration will reload using the updated configuration.\r\n\r\nIntegration Language: \t{lang_new}\r\nNickname: \t{nick_name_new}\r\nDebug mode for action: \t{action_debug}\r\nHide non-standard created entities: \t{hide_non_standard_entities}\r\nDevice Changes: \tAdd **{devices_add}** devices, Remove **{devices_remove}** devices\r\nTransformation rules change: \tThere are a total of **{trans_rules_count}** rules, and updated **{trans_rules_count_success}** rules\r\n",
"data": {
"confirm": "Confirm the change"
}
}
},
"progress": {
"oauth": "### {link_left}Please click here to re-login{link_right}"
},
"error": {
"not_auth": "Not authenticated. Please click the authentication link to authenticate user identity.",
"get_token_error": "Failed to retrieve login authorization information (OAuth token).",
"get_homeinfo_error": "Failed to retrieve home information.",
"get_cert_error": "Failed to retrieve the central hub gateway certificate.",
"no_devices": "The selected home does not have any devices. Please choose a home containing devices and continue.",
"no_family_selected": "No home selected.",
"no_central_device": "[Central Hub Gateway Mode] requires a Xiaomi central hub gateway available in the local network where Home Assistant exists. Please check if the selected home meets the requirement.",
"mdns_discovery_error": "Local device discovery service exception.",
"update_config_error": "Failed to update configuration information.",
"not_confirm": "Changes are not confirmed. Please confirm the change before submitting."
},
"abort": {
"network_connect_error": "Configuration failed. The network connection is abnormal. Please check the equipment network configuration.",
"options_flow_error": "Integration re-configuration error: {error}",
"re_add": "Please re-add the integration. Error message: {error}",
"storage_error": "Integration storage module exception. Please try again or re-add the integration: {error}",
"inconsistent_account": "Account information is inconsistent. Please login with the correct account."
}
}
}

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "Integración de Xiaomi Home",
"step": {
"eula": {
"title": "Aviso de riesgo",
"description": "1. Su **información de usuario de Xiaomi e información del dispositivo** se almacenará en su sistema Home Assistant. **Xiaomi no puede garantizar la seguridad del mecanismo de almacenamiento de Home Assistant**. Usted es responsable de evitar que su información sea robada.\r\n2. Esta integración es mantenida por la comunidad de código abierto y puede haber problemas de estabilidad u otros problemas. Cuando tenga problemas relacionados con el uso de esta integración, **busque ayuda en la comunidad de código abierto en lugar de contactar al servicio al cliente de Xiaomi**.\r\n3. Es necesario tener ciertas habilidades técnicas para mantener su entorno de ejecución local, esta integración no es amigable para los usuarios novatos.\r\n4. Antes de utilizar esta integración, por favor **lea detenidamente el archivo README**. \r\n5. Para garantizar el uso estable de la integración y prevenir el abuso de la interfaz, **esta integración solo está permitida en Home Assistant. Para más detalles, consulte la LICENSE**.\r\n",
"data": {
"eula": "He leído y entiendo los riesgos anteriores, y estoy dispuesto a asumir cualquier riesgo relacionado con el uso de esta integración."
}
},
"auth_config": {
"title": "Configuración básica",
"description": "### Región de inicio de sesión\r\nSeleccione la región donde se encuentra su cuenta de Xiaomi. Puede consultar esta información en `Xiaomi Home APP > Yo (ubicado en el menú inferior) > Más ajustes > Acerca de Xiaomi Home`.\r\n### Idioma\r\nSeleccione el idioma utilizado para los nombres de los dispositivos y entidades. Las partes de las frases que no están traducidas se mostrarán en inglés.\r\n### Dirección de redireccionamiento de autenticación de OAuth2\r\nLa dirección de redireccionamiento de autenticación de OAuth2 es **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. Home Assistant debe estar en la misma red local que el terminal de operación actual (por ejemplo, una computadora personal) y el terminal de operación debe poder acceder a la página de inicio de Home Assistant a través de esta dirección, de lo contrario, la autenticación de inicio de sesión podría fallar.\r\n### Nota\r\n- Para los usuarios con cientos o más dispositivos Mi Home, la adición inicial de la integración tomará algún tiempo. Por favor, sea paciente.\r\n- Si Home Assistant se está ejecutando en un entorno Docker, asegúrese de que el modo de red de Docker esté configurado en host, de lo contrario, la funcionalidad de control local puede no funcionar correctamente.\r\n- La funcionalidad de control local de la integración tiene algunas dependencias. Por favor, lea el README cuidadosamente.\r\n",
"data": {
"cloud_server": "Región de inicio de sesión",
"integration_language": "Idioma",
"oauth_redirect_url": "Dirección de redireccionamiento de autenticación de OAuth2"
}
},
"oauth_error": {
"title": "Error de inicio de sesión",
"description": "Haga clic en \"Siguiente\" para volver a intentarlo"
},
"devices_filter": {
"title": "Seleccionar hogares y dispositivos",
"description": "## Instrucciones de uso\r\n### Modo de control\r\n- Automático: Cuando hay un gateway central de Xiaomi disponible en la red local, Home Assistant priorizará el envío de comandos de control de dispositivos a través del gateway central para lograr un control localizado. Si no hay un gateway central en la red local, intentará enviar comandos de control a través del protocolo Xiaomi OT para lograr un control localizado. Solo cuando no se cumplan las condiciones anteriores de control localizado, los comandos de control del dispositivo se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Hogares de dispositivos importados\r\nLa integración agregará los dispositivos en los hogares seleccionados.\r\n### Modo de sincronización del nombre de la habitación\r\nCuando se sincronizan los dispositivos desde la aplicación Xiaomi Home a Home Assistant, los nombres de las áreas donde se encuentran los dispositivos en Home Assistant seguirán las reglas de nomenclatura a continuación. Tenga en cuenta que el proceso de sincronización de dispositivos no cambiará la configuración de hogares y habitaciones en la aplicación Xiaomi Home.\r\n- Sin sincronización: el dispositivo no se agregará a ninguna área.\r\n- Otras opciones: la zona donde se agrega el dispositivo tendrá el mismo nombre que el hogar o la habitación en la aplicación Xiaomi Home.\r\n### Modo de depuración de Action\r\nPara los métodos definidos por MIoT-Spec-V2, además de generar una entidad de notificación, también se generará una entidad de cuadro de entrada de texto que se puede utilizar para enviar comandos de control al dispositivo durante la depuración.\r\n### Ocultar entidades generadas no estándar\r\nOcultar las entidades generadas por la instancia no estándar MIoT-Spec-V2 que comienzan con \"*\".\r\n\r\n&emsp;\r\n### ¡Hola, {nick_name}! Seleccione el modo de control de integración y el hogar donde se encuentran los dispositivos que desea agregar.\r\n",
"data": {
"ctrl_mode": "Modo de control",
"home_infos": "Hogares de dispositivos importados",
"area_name_rule": "Modo de sincronización del nombre de la habitación",
"action_debug": "Modo de depuración de Action",
"hide_non_standard_entities": "Ocultar entidades generadas no estándar"
}
}
},
"progress": {
"oauth": "### {link_left}Haga clic aquí para iniciar sesión de nuevo{link_right}\r\n(Será redirigido automáticamente a la siguiente página después de un inicio de sesión exitoso)"
},
"error": {
"eula_not_agree": "Lea el texto de aviso de riesgo.",
"get_token_error": "Error al obtener la información de autorización de inicio de sesión (token OAuth).",
"get_homeinfo_error": "Error al obtener la información del hogar.",
"mdns_discovery_error": "Error en el servicio de descubrimiento de dispositivos locales.",
"get_cert_error": "Error al obtener el certificado de la puerta de enlace.",
"no_family_selected": "No se ha seleccionado ningún hogar.",
"no_devices": "No hay dispositivos en el hogar seleccionado. Seleccione un hogar con dispositivos y continúe.",
"no_central_device": "【Modo de puerta de enlace central】Se requiere una puerta de enlace Xiaomi disponible en la red local donde se encuentra Home Assistant. Verifique si el hogar seleccionado cumple con este requisito."
},
"abort": {
"network_connect_error": "La configuración ha fallado. Existe un problema con la conexión de red, verifique la configuración de red del dispositivo.",
"already_configured": "Esta cuenta ya ha finalizado la configuración. Ingrese a la página de integración y haga clic en el botón \"Configurar\" para modificar la configuración.",
"invalid_auth_info": "La información de autorización ha caducado. Ingrese a la página de integración y haga clic en el botón \"Configurar\" para volver a autenticarse.",
"config_flow_error": "Error de configuración de integración: {error}"
}
},
"options": {
"step": {
"auth_config": {
"title": "Configuración de autorización",
"description": "Se detectó que la información de autenticación local ha caducado, vuelva a autenticarse\r\n### Región de inicio de sesión actual: {cloud_server}\r\n### Dirección de redireccionamiento de autenticación de OAuth2\r\nLa dirección de redireccionamiento de autenticación de OAuth2 es **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. Home Assistant debe estar en la misma red local que el terminal de operación actual (por ejemplo, una computadora personal) y el terminal de operación debe poder acceder a la página de inicio de Home Assistant a través de esta dirección, de lo contrario, la autenticación de inicio de sesión podría fallar.\r\n",
"data": {
"oauth_redirect_url": "Dirección de redireccionamiento de autenticación de OAuth2"
}
},
"oauth_error": {
"title": "Error de inicio de sesión",
"description": "Haga clic en \"Siguiente\" para volver a intentarlo\r\n"
},
"config_options": {
"title": "Opciones de configuración",
"description": "### ¡Hola, {nick_name}!\r\n\r\nID de cuenta de Xiaomi: {uid}\r\nRegión de inicio de sesión actual: {cloud_server}\r\n\r\nSeleccione las opciones que desea reconfigurar y haga clic en \"Siguiente\".\r\n",
"data": {
"integration_language": "Idioma de la integración",
"update_user_info": "Actualizar información de usuario",
"update_devices": "Actualizar lista de dispositivos",
"action_debug": "Modo de depuración de Action",
"hide_non_standard_entities": "Ocultar entidades generadas no estándar",
"update_trans_rules": "Actualizar reglas de conversión de entidad (configuración global)",
"update_lan_ctrl_config": "Actualizar configuración de control LAN (configuración global)"
}
},
"update_user_info": {
"title": "Actualizar apodo de usuario",
"description": "¡Hola, {nick_name}! Modifique su apodo de usuario a continuación.\r\n",
"data": {
"nick_name": "Apodo de usuario"
}
},
"devices_filter": {
"title": "Recomendar hogares y dispositivos",
"description": "## Instrucciones de uso\r\n### Modo de control\r\n- Automático: Cuando hay un gateway central de Xiaomi disponible en la red local, Home Assistant priorizará el envío de comandos de control de dispositivos a través del gateway central para lograr un control localizado. Si no hay un gateway central en la red local, intentará enviar comandos de control a través del protocolo Xiaomi OT para lograr un control localizado. Solo cuando no se cumplan las condiciones anteriores de control localizado, los comandos de control del dispositivo se enviarán a través de la nube.\r\n- Nube: Los comandos de control solo se envían a través de la nube.\r\n### Hogares de dispositivos importados\r\nLa integración agregará los dispositivos en los hogares seleccionados.\r\n&emsp;\r\n### ¡Hola, {nick_name}! Seleccione el modo de control de integración y el hogar donde se encuentran los dispositivos que desea agregar.\r\n",
"data": {
"ctrl_mode": "Modo de control",
"home_infos": "Hogares de dispositivos importados"
}
},
"update_trans_rules": {
"title": "Actualizar reglas de conversión de entidad",
"description": "## Instrucciones de uso\r\n- Actualice la información de la entidad de los dispositivos en la instancia de integración actual, incluida la configuración multilingüe de SPEC, la traducción booleana de SPEC y el filtrado de modelos de SPEC.\r\n- **Advertencia: Esta configuración es una configuración global** y actualizará directamente la caché local. Si hay dispositivos del mismo modelo en otras instancias de integración, las instancias relevantes también se actualizarán después de recargarlas.\r\n- Esta operación tomará algún tiempo, por favor sea paciente. Marque \"Confirmar actualización\" y haga clic en \"Siguiente\" para comenzar a actualizar **{urn_count}** reglas, de lo contrario, omita la actualización.\r\n",
"data": {
"confirm": "Confirmar actualización"
}
},
"update_lan_ctrl_config": {
"title": "Actualizar configuración de control LAN",
"description": "## Instrucciones de uso\r\nActualice la información de configuración para **control LAN de dispositivos Xiaomi Home**. Cuando la nube y la puerta de enlace central no puedan controlar los dispositivos, la integración intentará controlar los dispositivos a través de la LAN; si no se selecciona ninguna tarjeta de red, el control LAN no se habilitará.\r\n- Actualmente, solo se admiten dispositivos WiFi **SPEC v2** en la LAN. Algunos dispositivos más antiguos pueden no admitir el control o la sincronización de propiedades.\r\n- Seleccione la(s) tarjeta(s) de red en la misma red que los dispositivos (se admiten múltiples selecciones). Si la tarjeta de red seleccionada tiene dos o más conexiones en la misma red, se recomienda seleccionar la que tenga la mejor conexión de red, de lo contrario, puede **afectar el uso normal de los dispositivos**.\r\n- **Si hay dispositivos terminales (puertas de enlace, teléfonos móviles, etc.) en la LAN que admiten el control local, habilitar la suscripción LAN puede causar automatización local o anomalías en los dispositivos. Úselo con precaución**.\r\n- **Advertencia: Esta configuración es global y los cambios afectarán a otras instancias de integración. Modifique con precaución**.\r\n{notice_net_dup}\r\n",
"data": {
"net_interfaces": "Por favor, seleccione la tarjeta de red a utilizar",
"enable_subscribe": "Habilitar suscripción LAN"
}
},
"config_confirm": {
"title": "Confirmar configuración",
"description": "¡Hola, **{nick_name}**! Por favor, confirme la última información de configuración y haga clic en \"Enviar\" para finalizar la configuración.\r\nLa integración se volverá a cargar con la nueva configuración.\r\n\r\nIdioma de la integración:\t{lang_new}\r\nApodo de usuario:\t{nick_name_new}\r\nModo de depuración de Action:\t{action_debug}\r\nOcultar entidades generadas no estándar:\t{hide_non_standard_entities}\r\nCambios de dispositivos:\t{devices_add} dispositivos agregados, {devices_remove} dispositivos eliminados\r\nCambios en las reglas de conversión:\t{trans_rules_count} reglas en total, {trans_rules_count_success} reglas actualizadas\r\n",
"data": {
"confirm": "Confirmar modificación"
}
}
},
"progress": {
"oauth": "### {link_left}Haga clic aquí para iniciar sesión de nuevo{link_right}"
},
"error": {
"not_auth": "Usuario no autenticado. Haga clic en el enlace de autenticación para autenticarse.",
"get_token_error": "Error al obtener la información de autorización de inicio de sesión (token OAuth).",
"get_homeinfo_error": "Error al obtener la información del hogar.",
"get_cert_error": "Error al obtener el certificado de la puerta de enlace.",
"no_family_selected": "No se ha seleccionado ningún hogar.",
"no_devices": "No hay dispositivos en el hogar seleccionado. Seleccione un hogar con dispositivos y continúe.",
"no_central_device": "【Modo de puerta de enlace central】Se requiere una puerta de enlace Xiaomi disponible en la red local donde se encuentra Home Assistant. Verifique si el hogar seleccionado cumple con este requisito.",
"mdns_discovery_error": "Error en el servicio de descubrimiento de dispositivos locales.",
"update_config_error": "Error al actualizar la información de configuración.",
"not_confirm": "No se ha confirmado la opción de modificación. Seleccione y confirme la opción antes de enviar."
},
"abort": {
"network_connect_error": "La configuración ha fallado. Existe un problema con la conexión de red, verifique la configuración de red del dispositivo.",
"options_flow_error": "Error al reconfigurar la integración: {error}",
"re_add": "Agregue la integración de nuevo, mensaje de error: {error}",
"storage_error": "Error en el módulo de almacenamiento de integración. Intente de nuevo o agregue la integración de nuevo: {error}",
"inconsistent_account": "La información de la cuenta no coincide. Inicie sesión con la cuenta correcta."
}
}
}

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "Intégration Xiaomi Home",
"step": {
"eula": {
"title": "Notification de risques",
"description": "1. Vos **informations utilisateur Xiaomi et informations sur l'appareil** seront stockées dans votre système Home Assistant. **Xiaomi ne peut garantir la sécurité du mécanisme de stockage de Home Assistant**. Vous êtes responsable de la protection de vos informations contre le vol.\r\n2. Cette intégration est maintenue par la communauté open source et peut rencontrer des problèmes de stabilité ou autres problèmes. Si vous rencontrez des problèmes liés à l'utilisation de cette intégration, vous devez **demander de l'aide à la communauté open source plutôt que de contacter le service client Xiaomi**.\r\n3. Vous avez besoin d'un certain niveau de compétences techniques pour maintenir votre environnement d'exécution local. Cette intégration n'est pas conviviale pour les utilisateurs novices.\r\n4. Avant d'utiliser cette intégration, veuillez **lire attentivement le README**.\r\n5. Pour garantir une utilisation stable de l'intégration et prévenir les abus d'interface, **cette intégration n'est autorisée qu'à être utilisée dans Home Assistant. Pour plus de détails, veuillez consulter la LICENSE**.\r\n",
"data": {
"eula": "Je suis informé des risques ci-dessus et j'accepte volontairement les risques associés à l'utilisation de cette intégration."
}
},
"auth_config": {
"title": "Configuration de base",
"description": "### Région de connexion\r\nSélectionnez la région où se trouve votre compte Xiaomi. Vous pouvez le trouver dans `Xiaomi Home APP > Mon (situé dans le menu inférieur) > Plus de paramètres > À propos de Xiaomi Home`.\r\n### Langue\r\nChoisissez la langue utilisée pour les noms de périphériques et d'entités. Les parties de phrases sans traduction seront affichées en anglais.\r\n### Adresse de redirection de l'authentification OAuth2\r\nL'adresse de redirection de l'authentification OAuth2 est **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. Home Assistant doit être dans le même réseau local que le terminal de l'opération actuelle (par exemple, un ordinateur personnel) et le terminal de l'opération doit pouvoir accéder à la page d'accueil de Home Assistant via cette adresse. Sinon, l'authentification de connexion peut échouer.\r\n### Remarque\r\n- Pour les utilisateurs ayant des centaines ou plus d'appareils Mi Home, l'ajout initial de l'intégration prendra un certain temps. Veuillez être patient.\r\n- Si Home Assistant fonctionne dans un environnement Docker, veuillez vous assurer que le mode réseau Docker est réglé sur host, sinon la fonctionnalité de contrôle local peut ne pas fonctionner correctement.\r\n- La fonctionnalité de contrôle local de l'intégration a quelques dépendances. Veuillez lire attentivement le README.\r\n",
"data": {
"cloud_server": "Région de connexion",
"integration_language": "Langue",
"oauth_redirect_url": "Adresse de redirection de l'authentification"
}
},
"oauth_error": {
"title": "Erreur de connexion",
"description": "Cliquez sur \"Suivant\" pour réessayer"
},
"devices_filter": {
"title": "Sélectionner une maison et des appareils",
"description": "## Instructions d'utilisation\r\n### Mode de contrôle\r\n- Automatique: Lorsqu'il y a une passerelle centrale Xiaomi disponible dans le réseau local, Home Assistant priorisera l'envoi des commandes de contrôle des appareils via la passerelle centrale pour réaliser un contrôle localisé. S'il n'y a pas de passerelle centrale dans le réseau local, il tentera d'envoyer des commandes de contrôle via le protocole Xiaomi OT pour réaliser un contrôle localisé. Ce n'est que lorsque les conditions de contrôle localisé ci-dessus ne sont pas remplies que les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud: Les commandes de contrôle ne sont envoyées que via le cloud.\r\n### Importer une maison pour les appareils\r\nL'intégration ajoutera les appareils de la maison sélectionnée.\r\n### Mode de synchronisation des noms de pièces\r\nLors de la synchronisation des appareils de Xiaomi Home à Home Assistant, le nom de la pièce où se trouve l'appareil sera nommé selon les règles suivantes. Notez que le processus de synchronisation des appareils n'affecte pas les paramètres de la maison et de la pièce dans Xiaomi Home APP.\r\n- Ne pas synchroniser: L'appareil ne sera ajouté à aucune zone.\r\n- Autre option: La zone dans laquelle l'appareil est ajouté est nommée en fonction du nom de la maison ou de la pièce de Xiaomi Home APP.\r\n### Mode de débogage d'action\r\nPour les méthodes définies par MIoT-Spec-V2, en plus de générer une entité de notification, une entité de zone de texte sera également générée pour que vous puissiez envoyer des commandes de contrôle à l'appareil lors du débogage.\r\n### Masquer les entités générées non standard\r\nMasquer les entités générées non standard de MIoT-Spec-V2 commençant par \"*\".\r\n\r\n&emsp;\r\n### {nick_name} Bonjour ! Veuillez sélectionner le mode de contrôle de l'intégration et la maison où se trouvent les appareils à ajouter.\r\n",
"data": {
"ctrl_mode": "Mode de contrôle",
"home_infos": "Importer une maison pour les appareils",
"area_name_rule": "Mode de synchronisation des noms de pièces",
"action_debug": "Mode de débogage d'action",
"hide_non_standard_entities": "Masquer les entités générées non standard"
}
}
},
"progress": {
"oauth": "### {link_left}Veuillez cliquer ici pour vous reconnecter{link_right}\r\n(Vous serez automatiquement redirigé vers la page suivante après une connexion réussie)"
},
"error": {
"eula_not_agree": "Veuillez lire le texte de notification de risques.",
"get_token_error": "Échec de la récupération des informations d'autorisation de connexion (jeton OAuth).",
"get_homeinfo_error": "Échec de la récupération des informations de la maison.",
"mdns_discovery_error": "Le service de découverte de périphériques locaux est anormal.",
"get_cert_error": "Échec de l'obtention du certificat de la passerelle.",
"no_family_selected": "Aucune maison sélectionnée.",
"no_devices": "Il n'y a pas d'appareil dans la maison sélectionnée. Veuillez sélectionner une maison avec des appareils avant de continuer.",
"no_central_device": "Le mode gateway central a besoin d'un Xiaomi Gateway disponible dans le réseau local où se trouve Home Assistant. Veuillez vérifier si la maison sélectionnée répond à cette exigence."
},
"abort": {
"network_connect_error": "La configuration a échoué. Erreur de connexion réseau. Veuillez vérifier la configuration du réseau de l'appareil.",
"already_configured": "Cet utilisateur a déjà terminé la configuration. Veuillez accéder à la page d'intégration et cliquer sur le bouton \"Configurer\" pour modifier la configuration.",
"invalid_auth_info": "Les informations d'authentification ont expiré. Veuillez accéder à la page d'intégration et cliquer sur le bouton \"Configurer\" pour vous authentifier à nouveau.",
"config_flow_error": "Erreur de configuration de l'intégration : {error}"
}
},
"options": {
"step": {
"auth_config": {
"title": "Configuration d'authentification",
"description": "Les informations d'authentification locales ont expiré. Veuillez recommencer l'authentification\r\n### Région de connexion actuelle : {cloud_server}\r\n### Adresse de redirection de l'authentification OAuth2\r\nL'adresse de redirection de l'authentification OAuth2 est **[http://homeassistant.local:8123](http://homeassistant.local:8123)**. Home Assistant doit être dans le même réseau local que le terminal de l'opération actuelle (par exemple, un ordinateur personnel) et le terminal de l'opération doit pouvoir accéder à la page d'accueil de Home Assistant via cette adresse. Sinon, l'authentification de connexion peut échouer.\r\n",
"data": {
"oauth_redirect_url": "Adresse de redirection de l'authentification"
}
},
"oauth_error": {
"title": "Erreur de connexion",
"description": "Cliquez sur \"Suivant\" pour réessayer\r\n"
},
"config_options": {
"title": "Options de configuration",
"description": "### {nick_name} Bonjour !\r\n\r\nID de compte Xiaomi : {uid}\r\nRégion de connexion actuelle : {cloud_server}\r\n\r\nVeuillez sélectionner les options que vous devez reconfigurer et cliquer sur \"Suivant\".\r\n",
"data": {
"integration_language": "Langue d'intégration",
"update_user_info": "Mettre à jour les informations utilisateur",
"update_devices": "Mettre à jour la liste des appareils",
"action_debug": "Mode de débogage d'action",
"hide_non_standard_entities": "Masquer les entités générées non standard",
"update_trans_rules": "Mettre à jour les règles de conversion d'entités (configuration globale)",
"update_lan_ctrl_config": "Mettre à jour la configuration de contrôle LAN (configuration globale)"
}
},
"update_user_info": {
"title": "Mettre à jour le pseudo de l'utilisateur",
"description": "{nick_name} Bonjour ! Veuillez modifier votre pseudo utilisateur ci-dessous.\r\n",
"data": {
"nick_name": "Pseudo utilisateur"
}
},
"devices_filter": {
"title": "Re-sélectionner une maison et des appareils",
"description": "## Instructions d'utilisation\r\n### Mode de contrôle\r\n- Automatique: Lorsqu'il y a une passerelle centrale Xiaomi disponible dans le réseau local, Home Assistant priorisera l'envoi des commandes de contrôle des appareils via la passerelle centrale pour réaliser un contrôle localisé. S'il n'y a pas de passerelle centrale dans le réseau local, il tentera d'envoyer des commandes de contrôle via le protocole Xiaomi OT pour réaliser un contrôle localisé. Ce n'est que lorsque les conditions de contrôle localisé ci-dessus ne sont pas remplies que les commandes de contrôle des appareils seront envoyées via le cloud.\r\n- Cloud: Les commandes de contrôle ne sont envoyées que via le cloud.\r\n### Importer une maison pour les appareils\r\nL'intégration ajoutera les appareils de la maison sélectionnée.\r\n&emsp;\r\n### {nick_name} Bonjour ! Veuillez sélectionner le mode de contrôle de l'intégration et la maison où se trouvent les appareils à ajouter.\r\n",
"data": {
"ctrl_mode": "Mode de contrôle",
"home_infos": "Importer une maison pour les appareils"
}
},
"update_trans_rules": {
"title": "Mettre à jour les règles de conversion d'entités",
"description": "## Instructions d'utilisation\r\n- Mettez à jour les informations d'entité des appareils dans l'instance d'intégration actuelle, y compris la configuration multilingue SPEC, la traduction booléenne SPEC et le filtrage de modèle SPEC.\r\n- **Avertissement: Cette configuration est une configuration globale** et mettra directement à jour le cache local. S'il y a des appareils du même modèle dans d'autres instances d'intégration, les instances pertinentes seront également mises à jour après rechargement.\r\n- Cette opération prendra du temps, veuillez être patient. Cochez \"Confirmer la mise à jour\" et cliquez sur \"Suivant\" pour commencer à mettre à jour **{urn_count}** règles, sinon passez la mise à jour.\r\n",
"data": {
"confirm": "Confirmer la mise à jour"
}
},
"update_lan_ctrl_config": {
"title": "Mettre à jour la configuration réseau de contrôle LAN",
"description": "## Instructions d'utilisation\r\nMettez à jour les informations de configuration pour **le contrôle LAN des appareils Xiaomi Home**. Lorsque le cloud et la passerelle centrale ne peuvent pas contrôler les appareils, l'intégration tentera de contrôler les appareils via le LAN ; si aucune carte réseau n'est sélectionnée, le contrôle LAN ne sera pas activé.\r\n- Actuellement, seuls les appareils WiFi **SPEC v2** dans le LAN sont pris en charge. Certains anciens appareils peuvent ne pas prendre en charge le contrôle ou la synchronisation des propriétés.\r\n- Veuillez sélectionner la ou les cartes réseau sur le même réseau que les appareils (plusieurs sélections sont prises en charge). Si la carte réseau sélectionnée a deux ou plusieurs connexions sur le même réseau, il est recommandé de sélectionner celle avec la meilleure connexion réseau, sinon cela peut **affecter l'utilisation normale des appareils**.\r\n- **S'il y a des appareils terminaux (passerelles, téléphones mobiles, etc.) dans le LAN qui prennent en charge le contrôle local, l'activation de l'abonnement LAN peut provoquer des automatisations locales ou des anomalies des appareils. Veuillez l'utiliser avec prudence**.\r\n- **Avertissement : Cette configuration est globale et les modifications affecteront d'autres instances d'intégration. Veuillez modifier avec prudence**.\r\n{notice_net_dup}\r\n",
"data": {
"net_interfaces": "Veuillez sélectionner la carte réseau à utiliser",
"enable_subscribe": "Activer la souscription"
}
},
"config_confirm": {
"title": "Confirmer la configuration",
"description": "**{nick_name}** Bonjour ! Veuillez confirmer les dernières informations de configuration et cliquer sur \"Soumettre\".\r\nL'intégration rechargera avec la nouvelle configuration.\r\n\r\nLangue d'intégration : {lang_new}\r\nPseudo utilisateur : {nick_name_new}\r\nMode de débogage d'action : {action_debug}\r\nMasquer les entités générées non standard : {hide_non_standard_entities}\r\nModifications des appareils : Ajouter **{devices_add}** appareils, supprimer **{devices_remove}** appareils\r\nModifications des règles de conversion : **{trans_rules_count}** règles au total, mise à jour de **{trans_rules_count_success}** règles\r\n",
"data": {
"confirm": "Confirmer la modification"
}
}
},
"progress": {
"oauth": "### {link_left}Veuillez cliquer ici pour vous reconnecter{link_right}"
},
"error": {
"not_auth": "L'utilisateur n'est pas authentifié. Veuillez cliquer sur le lien d'authentification pour vous identifier.",
"get_token_error": "Impossible d'obtenir les informations d'authentification (jeton OAuth).",
"get_homeinfo_error": "Impossible d'obtenir les informations de la maison.",
"get_cert_error": "Impossible d'obtenir le certificat central.",
"no_family_selected": "Aucune maison sélectionnée.",
"no_devices": "Aucun périphérique dans la maison sélectionnée. Veuillez sélectionner une maison avec des périphériques et continuer.",
"no_central_device": "Le mode passerelle centrale nécessite une passerelle Xiaomi disponible dans le réseau local où est installé Home Assistant. Veuillez vérifier que la maison sélectionnée répond à cette exigence.",
"mdns_discovery_error": "Service de découverte de périphérique local en panne.",
"update_config_error": "Échec de la mise à jour des informations de configuration.",
"not_confirm": "La modification n'a pas été confirmée. Veuillez cocher la case de confirmation avant de soumettre."
},
"abort": {
"network_connect_error": "Échec de la configuration. Problème de connexion réseau, veuillez vérifier la configuration du périphérique.",
"options_flow_error": "Erreur de réinitialisation de la configuration de l'intégration : {error}",
"re_add": "Veuillez réajouter l'intégration, message d'erreur : {error}",
"storage_error": "Erreur de stockage pour l'intégration. Veuillez réessayer ou réajouter l'intégration : {error}",
"inconsistent_account": "Les informations de compte sont incohérentes. Veuillez vous connecter avec les informations de compte correctes."
}
}
}

View File

@ -0,0 +1,146 @@
{
"config": {
"flow_title": "Xiaomi Home インテグレーション",
"step": {
"eula": {
"title": "リスク告知",
"description": "1. あなたの**Xiaomiユーザー情報とデバイス情報**は、Home Assistantシステムに保存されます。**XiaomiはHome Assistantの保存メカニズムの安全性を保証できません**。情報が盗まれないようにする責任はあなたにあります。\r\n2. このインテグレーションはオープンソースコミュニティによって管理されています。安定性の問題やその他の問題が発生する可能性があります。問題が発生した場合は、 **Xiaomi カスタマーサポートに連絡するのではなく、オープンソースコミュニティに助けを求める必要があります**。\r\n3. あなたはある程度の技術力を持っている必要があります。このインテグレーションは初心者には友好的ではありません。\r\n4. このインテグレーションを使用する前に、 **README をよく読んでください**。\r\n5. 統合の安定した使用を確保し、インターフェースの乱用を防ぐために、**この統合はHome Assistantでのみ使用することが許可されています。詳細についてはLICENSEを参照してください**。\r\n",
"data": {
"eula": "私は上記のリスクを理解し、インテグレーションを使用することによる関連するリスクを自己責任で引き受けます。"
}
},
"auth_config": {
"title": "基本設定",
"description": "### ログインエリア\r\nXiaomi アカウントが属する地域を選択します。 `Xiaomi Home アプリ> マイ(ボトムメニューにあります)> その他の設定> Xiaomi Home について` で確認できます。\r\n### 言語\r\nデバイスおよびエンティティ名に使用される言語を選択します。一部の翻訳が欠落している場合、英語が表示されます。\r\n### OAuth2 認証リダイレクトアドレス\r\nOAuth2 認証リダイレクトアドレスは **[http://homeassistant.local:8123](http://homeassistant.local:8123)** です。Home Assistant は、現在の操作端末(たとえば、パーソナルコンピュータ)と同じ LAN 内にあり、操作端末がこのアドレスで Home Assistant ホームページにアクセスできる場合にのみログイン認証が成功する場合があります。\r\n### 注意事項\r\n- 数百台以上のMi Homeデバイスをお持ちのユーザーの場合、統合の初回追加には時間がかかります。しばらくお待ちください。\r\n- Home AssistantがDocker環境で実行されている場合は、Dockerのネットワークモードがhostに設定されていることを確認してください。そうしないと、ローカル制御機能が正しく動作しない可能性があります。\r\n- 統合のローカル制御機能にはいくつかの依存関係があります。READMEを注意深く読んでください。\r\n",
"data": {
"cloud_server": "ログインエリア",
"integration_language": "言語",
"oauth_redirect_url": "認証リダイレクトアドレス"
}
},
"oauth_error": {
"title": "ログインエラー",
"description": "「次へ」をクリックして再試行してください"
},
"devices_filter": {
"title": "ホームとデバイスを選択",
"description": "## 使用方法\r\n### 制御モード\r\n- 自動: ローカルエリアネットワーク内に利用可能なXiaomi中央ゲートウェイが存在する場合、Home Assistantは中央ゲートウェイを介してデバイス制御コマンドを優先的に送信し、ローカル制御機能を実現します。ローカルエリアネットワーク内に中央ゲートウェイが存在しない場合、Xiaomi OTプロトコルを介して制御コマンドを送信し、ローカル制御機能を実現しようとします。上記のローカル制御条件が満たされない場合にのみ、デバイス制御コマンドはクラウドを介して送信されます。\r\n- クラウド: 制御コマンドはクラウドを介してのみ送信されます。\r\n### 導入されたデバイスのホーム\r\n統合は、選択された家庭にあるデバイスを追加します。\r\n### 部屋名同期モード\r\nXiaomi Home アプリから Home Assistant に同期されるデバイスの場合、デバイスが Home Assistant 内でどのような領域にあるかを示す名前の命名方式は、以下のルールに従います。ただし、デバイスの同期プロセスは、Xiaomi Home アプリで家庭および部屋の設定を変更しないことに注意してください。\r\n- 同期しない:デバイスはどの領域にも追加されません。\r\n- その他のオプションデバイスが追加される領域は、Xiaomi Home アプリの家庭または部屋の名前に従って命名されます。\r\n### Action デバッグモード\r\nデバイスが MIoT-Spec-V2 で定義された方法を実行する場合、通知エンティティの生成に加えて、テキスト入力ボックスエンティティも生成されます。これを使用して、デバイスに制御命令を送信することができます。\r\n### 非標準生成エンティティを非表示にする\r\n「*」で始まる名前の非標準 MIoT-Spec-V2 インスタンスによって生成されたエンティティを非表示にします。\r\n\r\n&emsp;\r\n### {nick_name} さん、こんにちは! 統合制御モードと追加するデバイスがあるホームを選択してください。\r\n",
"data": {
"ctrl_mode": "制御モード",
"home_infos": "導入されたデバイスのホーム",
"area_name_rule": "部屋名同期モード",
"action_debug": "Action デバッグモード",
"hide_non_standard_entities": "非標準生成エンティティを非表示にする"
}
}
},
"progress": {
"oauth": "### {link_left}ここをクリックして再度ログインしてください{link_right}\r\n(ログインに成功すると、自動的に次のページにリダイレクトされます)"
},
"error": {
"eula_not_agree": "リスク告知文書を読んでください。",
"get_token_error": "ログイン認証情報OAuth トークン)を取得できませんでした。",
"get_homeinfo_error": "ホーム情報を取得できませんでした。",
"mdns_discovery_error": "ローカルデバイス検出サービスに異常があります。",
"get_cert_error": "ゲートウェイ証明書を取得できませんでした。",
"no_family_selected": "家庭が選択されていません。",
"no_devices": "選択された家庭にデバイスがありません。デバイスがある家庭を選択して続行してください。",
"no_central_device": "【中央ゲートウェイモード】Home Assistant が存在する LAN 内に使用可能な Xiaomi 中央ゲートウェイがある必要があります。選択された家庭がこの要件を満たしているかどうかを確認してください。",
"update_config_error": "設定情報の更新に失敗しました。",
"not_confirm": "変更項目が確認されていません。確認を選択してから送信してください。"
},
"abort": {
"network_connect_error": "設定に失敗しました。ネットワーク接続に異常があります。デバイスのネットワーク設定を確認してください。",
"already_configured": "このユーザーはすでに設定が完了しています。統合ページにアクセスして、「設定」ボタンをクリックして設定を変更してください。",
"invalid_auth_info": "認証情報が期限切れになりました。統合ページにアクセスして、「設定」ボタンをクリックして再度認証してください。",
"config_flow_error": "統合設定エラー:{error}"
}
},
"options": {
"step": {
"auth_config": {
"title": "認証構成",
"description": "ローカル認証情報が期限切れになっています。認証を再開してください。\r\n### 現在のログインエリア:{cloud_server}\r\n### OAuth2 認証リダイレクトアドレス\r\nOAuth2 認証リダイレクトアドレスは **[http://homeassistant.local:8123](http://homeassistant.local:8123)** です。Home Assistant は、現在の操作端末(たとえば、パーソナルコンピュータ)と同じ LAN 内にあり、操作端末がこのアドレスで Home Assistant ホームページにアクセスできる場合にのみログイン認証が成功する場合があります。\r\n",
"data": {
"oauth_redirect_url": "認証リダイレクトアドレス"
}
},
"oauth_error": {
"title": "ログインエラー",
"description": "「次へ」をクリックして再試行してください"
},
"config_options": {
"title": "設定オプション",
"description": "### {nick_name} さん、こんにちは!\r\n\r\nXiaomi アカウントID{uid}\r\n現在のログインエリア{cloud_server}\r\n\r\n必要な構成オプションを選択して、[次へ] をクリックしてください。\r\n",
"data": {
"integration_language": "統合言語",
"update_user_info": "ユーザー情報を更新する",
"update_devices": "デバイスリストを更新する",
"action_debug": "Action デバッグモード",
"hide_non_standard_entities": "非標準生成エンティティを非表示にする",
"update_trans_rules": "エンティティ変換ルールを更新する (グローバル設定)",
"update_lan_ctrl_config": "LAN制御構成を更新するグローバル設定"
}
},
"update_user_info": {
"title": "ユーザー名を更新する",
"description": "{nick_name} さん、こんにちは! 下のボックスからユーザー名を変更してください。\r\n",
"data": {
"nick_name": "ユーザー名"
}
},
"devices_filter": {
"title": "ホームとデバイスを再度選択",
"description": "## 使用方法\r\n### 制御モード\r\n- 自動: ローカルエリアネットワーク内に利用可能なXiaomi中央ゲートウェイが存在する場合、Home Assistantは中央ゲートウェイを介してデバイス制御コマンドを優先的に送信し、ローカル制御機能を実現します。ローカルエリアネットワーク内に中央ゲートウェイが存在しない場合、Xiaomi OTプロトコルを介して制御コマンドを送信し、ローカル制御機能を実現しようとします。上記のローカル制御条件が満たされない場合にのみ、デバイス制御コマンドはクラウドを介して送信されます。\r\n- クラウド: 制御コマンドはクラウドを介してのみ送信されます。\r\n### 導入されたデバイスのホーム\r\n統合は、選択された家庭にあるデバイスを追加します。\r\n&emsp;\r\n### {nick_name} さん、こんにちは! 統合制御モードと追加するデバイスがあるホームを選択してください。\r\n",
"data": {
"ctrl_mode": "制御モード",
"home_infos": "導入されたデバイスのホーム"
}
},
"update_trans_rules": {
"title": "エンティティ変換ルールを更新する",
"description": "## 使用方法\r\n- 現在の統合インスタンス内のデバイスのエンティティ情報を更新します。これには、SPEC多言語設定、SPECブール値翻訳、SPECモデルフィルタリングが含まれます。\r\n- **警告: この設定はグローバル設定**であり、ローカルキャッシュを直接更新します。他の統合インスタンスに同じモデルのデバイスがある場合、関連するインスタンスを再読み込みした後に更新されます。\r\n- この操作には時間がかかるため、しばらくお待ちください。「更新を確認」を選択し、「次へ」をクリックして **{urn_count}** ルールの更新を開始します。そうでない場合は、更新をスキップします。\r\n",
"data": {
"confirm": "確認する"
}
},
"update_lan_ctrl_config": {
"title": "LAN制御構成を更新する",
"description": "## 使用方法\r\n**Xiaomi HomeデバイスのLAN制御**の設定情報を更新します。クラウドと中央ゲートウェイがデバイスを制御できない場合、統合はLANを介してデバイスを制御しようとします。ネットワークカードが選択されていない場合、LAN制御は有効になりません。\r\n- 現在、LAN内の**SPEC v2** WiFiデバイスのみがサポートされています。一部の古いデバイスは、制御やプロパティの同期をサポートしていない場合があります。\r\n- デバイスと同じネットワーク上のネットワークカードを選択してください複数選択がサポートされています。選択したネットワークカードが同じネットワークに2つ以上の接続を持っている場合は、最適なネットワーク接続を持つものを選択することをお勧めします。そうしないと、デバイスの正常な使用に**影響を与える可能性があります**。\r\n- **LAN内にローカル制御をサポートする端末デバイスゲートウェイ、携帯電話などが存在する場合、LANサブスクリプションを有効にすると、ローカルオートメーションやデバイスの異常が発生する可能性があります。慎重に使用してください**。\r\n- **警告:この設定はグローバル設定であり、変更は他の統合インスタンスに影響を与えます。慎重に変更してください**。\r\n{notice_net_dup}\r\n",
"data": {
"net_interfaces": "使用するネットワークカードを選択してください",
"enable_subscribe": "LANサブスクリプションを有効にする"
}
},
"config_confirm": {
"title": "構成を確認する",
"description": "**{nick_name}** さん、こんにちは! 最新の構成情報を確認してください。[送信] をクリックして、更新された構成を使用して再度読み込みます。\r\n\r\n統合言語\t{lang_new}\r\nユーザー名\t{nick_name_new}\r\nAction デバッグモード:\t{action_debug}\r\n非標準生成エンティティを非表示にする\t{hide_non_standard_entities}\r\nデバイス変更\t追加 **{devices_add}** 個のデバイス、削除 **{devices_remove}** 個のデバイス\r\n変換ルール変更\t合計 **{trans_rules_count}** 個の規則、更新 **{trans_rules_count_success}** 個の規則\r\n",
"data": {
"confirm": "変更を確認する"
}
}
},
"progress": {
"oauth": "### {link_left}ここをクリックして再度ログインしてください{link_right}"
},
"error": {
"not_auth": "ユーザーが認証されていません。認証リンクをクリックしてユーザーの身元を確認してください。",
"get_token_error": "ログイン認証情報OAuthトークンの取得に失敗しました。",
"get_homeinfo_error": "家庭情報の取得に失敗しました。",
"get_cert_error": "中枢証明書の取得に失敗しました。",
"no_family_selected": "家族が選択されていません。",
"no_devices": "選択された家庭にはデバイスがありません。デバイスがある家庭を選択してから続行してください。",
"no_central_device": "【中枢ゲートウェイモード】には、Home Assistantが存在するローカルネットワークに使用可能なXiaomi Central Hub Gatewayが存在する必要があります。選択された家庭がこの要件を満たしているかどうかを確認してください。",
"mdns_discovery_error": "ローカルデバイス発見サービスが異常です。",
"update_config_error": "構成情報の更新に失敗しました。",
"not_confirm": "変更を確認していません。確認をチェックしてから送信してください。"
},
"abort": {
"network_connect_error": "構成に失敗しました。ネットワーク接続に異常があります。デバイスのネットワーク構成を確認してください。",
"options_flow_error": "統合の再設定エラー:{error}",
"re_add": "統合を再度追加してください。エラーメッセージ:{error}",
"storage_error": "統合ストレージモジュールに異常があります。再試行するか、統合を再度追加してください:{error}",
"inconsistent_account": "アカウント情報が一致しません。正しいアカウントでログインしてください。"
}
}
}

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "Ми Дома Интеграция",
"step": {
"eula": {
"title": "Риск-информирование",
"description": "1. Ваша **информация о пользователе Xiaomi и информация об устройстве** будет храниться в вашей системе Home Assistant. **Xiaomi не может гарантировать безопасность механизма хранения Home Assistant**. Вы несете ответственность за предотвращение кражи вашей информации.\r\n2. Эта интеграция поддерживается сообществом Open Source и может иметь проблемы с устойчивостью или другими проблемами, если вы столкнулись с проблемами при использовании этой интеграции, **вы должны обратиться за помощью к сообществу Open Source, а не к службе поддержки Xiaomi**.\r\n3. Вам нужно иметь определенные технические навыки для поддержания вашей локальной рабочей среды, эта интеграция не является дружественной для новичков.\r\n4. Перед использованием этой интеграции, **внимательно прочитайте README**.\r\n5. Чтобы обеспечить стабильное использование интеграции и предотвратить злоупотребление интерфейсом, **эта интеграция разрешена только для использования в Home Assistant. Для получения подробной информации, пожалуйста, обратитесь к LICENSE**.\r\n",
"data": {
"eula": "Я ознакомлен с вышеуказанными рисками и добровольно несу связанные с использованием интеграции риски."
}
},
"auth_config": {
"title": "Основные настройки",
"description": "### Регион входа в систему\r\nВыберите регион, в котором находится ваша учетная запись Xiaomi. Вы можете узнать об этом в `Xiaomi Home> Мой (в нижнем меню)> Дополнительные настройки> О Xiaomi Home`.\r\n### Язык\r\nВыберите язык, используемый для имен устройств и сущностей. Части предложений, которые не имеют перевода, будут отображаться на английском языке.\r\n### Адрес перенаправления для аутентификации OAuth2\r\nАдрес перенаправления для аутентификации OAuth2 - ** [http: //homeassistant.local: 8123] (http: //homeassistant.local: 8123) **, Home Assistant должен находиться в одной локальной сети с текущим терминалом (например, персональный компьютер), и терминал должен иметь доступ к домашней странице Home Assistant по этому адресу, в противном случае аутентификация входа может завершиться неудачно.\r\n### Примечание\r\n- Для пользователей с сотнями или более устройств Mi Home первоначальное добавление интеграции займет некоторое время. Пожалуйста, будьте терпеливы.\r\n- Если Home Assistant работает в среде Docker, убедитесь, что сетевой режим Docker установлен на host, иначе функция локального управления может работать неправильно.\r\n- Функция локального управления интеграции имеет некоторые зависимости. Пожалуйста, внимательно прочитайте README.\r\n",
"data": {
"cloud_server": "Регион входа в систему",
"integration_language": "Язык",
"oauth_redirect_url": "Адрес перенаправления для аутентификации OAuth2"
}
},
"oauth_error": {
"title": "Ошибка входа в систему",
"description": "Нажмите кнопку «Далее», чтобы повторить попытку"
},
"devices_filter": {
"title": "Выберите дом и устройства",
"description": "## Инструкция по использованию\r\n### Режим управления\r\n- Авто: Когда в локальной сети доступен центральный шлюз Xiaomi, Home Assistant будет в первую очередь отправлять команды управления устройствами через центральный шлюз для достижения локализованного управления. Если в локальной сети нет центрального шлюза, он попытается отправить команды управления через протокол Xiaomi OT для достижения локализованного управления. Только если вышеуказанные условия локализованного управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: Команды управления отправляются только через облако.\r\n### Импорт домашнего устройства\r\nИнтеграция добавит устройства из выбранных домов.\r\n### Режим синхронизации имен комнат\r\nПри синхронизации устройств из приложения Xiaomi Home в Home Assistant имена комнат устройств в Home Assistant будут именоваться в соответствии с именами дома или комнаты в приложении Xiaomi Home.\r\n- Не синхронизировать: устройство не будет добавлено в любую область.\r\n- Другие параметры: область, в которую добавляется устройство, называется именем дома или комнаты в приложении Xiaomi Home.\r\n### Режим отладки Action\r\nДля методов, определенных в MIoT-Spec-V2, помимо создания уведомительной сущности будет создана сущность текстового поля ввода, которую можно использовать для отправки команд управления устройством во время отладки.\r\n### Скрыть нестандартные сущности\r\nСкрыть сущности, созданные нестандартными примерами MIoT-Spec-V2, имена которых начинаются с « * ».\r\n\r\n&emsp;\r\n### {nick_name} Здравствуйте! Выберите режим управления интеграцией и дом, в котором находятся устройства, которые вы хотите добавить.\r\n",
"data": {
"ctrl_mode": "Режим управления",
"home_infos": "Импорт домашнего устройства",
"area_name_rule": "Режим синхронизации имен комнат",
"action_debug": "Режим отладки Action",
"hide_non_standard_entities": "Скрыть нестандартные сущности"
}
}
},
"progress": {
"oauth": "### {link_left}Пожалуйста, нажмите здесь, чтобы войти в систему{link_right}\r\n(После успешного входа вы будете автоматически перенаправлены на следующую страницу)"
},
"error": {
"eula_not_agree": "Пожалуйста, ознакомьтесь с текстом рискового информирования.",
"get_token_error": "Не удалось получить информацию об авторизации входа в систему (OAuth token).",
"get_homeinfo_error": "Не удалось получить информацию о домашнем устройстве.",
"mdns_discovery_error": "Сервис обнаружения локальных устройств недоступен.",
"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}"
}
}
},
"options": {
"step": {
"auth_config": {
"title": "Настройка аутентификации",
"description": "Обнаружено, что информация об аутентификации локальной сети устарела, повторите аутентификацию.\r\n### Текущий регион входа в систему: {cloud_server}\r\n### Адрес перенаправления для аутентификации OAuth2\r\nАдрес перенаправления для аутентификации OAuth2 - ** [http: //homeassistant.local: 8123] (http: //homeassistant.local: 8123) **, Home Assistant должен находиться в одной локальной сети с текущим терминалом (например, персональный компьютер), и терминал должен иметь доступ к домашней странице Home Assistant по этому адресу, в противном случае аутентификация входа может завершиться неудачно.\r\n",
"data": {
"oauth_redirect_url": "Адрес перенаправления для аутентификации OAuth2"
}
},
"oauth_error": {
"title": "Ошибка входа в систему",
"description": "Нажмите кнопку «Далее», чтобы повторить попытку"
},
"config_options": {
"title": "Параметры настройки",
"description": "### {nick_name} Здравствуйте!\r\n\r\nID учетной записи Xiaomi: {uid}\r\nТекущий регион входа в систему: {cloud_server}\r\n\r\nВыберите параметры, которые нужно настроить заново, а затем нажмите «Далее».\r\n",
"data": {
"integration_language": "Язык интеграции",
"update_user_info": "Обновить информацию о пользователе",
"update_devices": "Обновить список устройств",
"action_debug": "Режим отладки Action",
"hide_non_standard_entities": "Скрыть нестандартные сущности",
"update_trans_rules": "Обновить правила преобразования сущностей (глобальная настройка)",
"update_lan_ctrl_config": "Обновить конфигурацию управления LAN (глобальная настройка)"
}
},
"update_user_info": {
"title": "Обновить имя пользователя",
"description": "{nick_name} Здравствуйте! Пожалуйста, введите свое имя пользователя ниже.\r\n",
"data": {
"nick_name": "Имя пользователя"
}
},
"devices_filter": {
"title": "Выберите дом и устройства",
"description": "## Инструкция по использованию\r\n### Режим управления\r\n- Авто: Когда в локальной сети доступен центральный шлюз Xiaomi, Home Assistant будет в первую очередь отправлять команды управления устройствами через центральный шлюз для достижения локализованного управления. Если в локальной сети нет центрального шлюза, он попытается отправить команды управления через протокол Xiaomi OT для достижения локализованного управления. Только если вышеуказанные условия локализованного управления не выполняются, команды управления устройствами будут отправляться через облако.\r\n- Облако: Команды управления отправляются только через облако.\r\n### Импорт домашнего устройства\r\nИнтеграция добавит устройства из выбранных домов.\r\n&emsp;\r\n### {nick_name} Здравствуйте! Выберите режим управления интеграцией и дом, в котором находятся устройства, которые вы хотите добавить.\r\n",
"data": {
"ctrl_mode": "Режим управления",
"home_infos": "Импорт домашнего устройства"
}
},
"update_trans_rules": {
"title": "Обновить правила преобразования сущностей",
"description": "## Инструкция по использованию\r\n- Обновите информацию об объектах устройств в текущем экземпляре интеграции, включая многоязычную конфигурацию SPEC, булевый перевод SPEC и фильтрацию моделей SPEC.\r\n- **Предупреждение: Эта конфигурация является глобальной конфигурацией** и напрямую обновит локальный кэш. Если в других экземплярах интеграции есть устройства той же модели, соответствующие экземпляры также будут обновлены после перезагрузки.\r\n- Эта операция займет некоторое время, пожалуйста, будьте терпеливы. Установите флажок \"Подтвердить обновление\" и нажмите \"Далее\", чтобы начать обновление **{urn_count}** правил, иначе пропустите обновление.\r\n",
"data": {
"confirm": "Подтвердить обновление"
}
},
"update_lan_ctrl_config": {
"title": "Обновить конфигурацию управления LAN",
"description": "## Инструкция по использованию\r\nОбновите информацию о конфигурации для **LAN-управления устройствами Xiaomi Home**. Когда облако и центральный шлюз не могут управлять устройствами, интеграция попытается управлять устройствами через LAN; если сетевая карта не выбрана, управление через LAN не будет включено.\r\n- В настоящее время поддерживаются только устройства WiFi **SPEC v2** в локальной сети. Некоторые старые устройства могут не поддерживать управление или синхронизацию свойств.\r\n- Пожалуйста, выберите сетевую карту(и) в той же сети, что и устройства (поддерживается множественный выбор). Если выбранная сетевая карта имеет два или более соединений в одной сети, рекомендуется выбрать ту, которая имеет наилучшее сетевое соединение, иначе это может **повлиять на нормальное использование устройств**.\r\n- **Если в локальной сети есть терминальные устройства (шлюзы, мобильные телефоны и т. д.), поддерживающие локальное управление, включение подписки на LAN может вызвать локальную автоматизацию или аномалии устройств. Пожалуйста, используйте с осторожностью**.\r\n- **Предупреждение: Эта конфигурация является глобальной, и изменения повлияют на другие экземпляры интеграции. Пожалуйста, изменяйте с осторожностью**.\r\n{notice_net_dup}\r\n",
"data": {
"net_interfaces": "Пожалуйста, выберите сетевую карту для использования",
"enable_subscribe": "Включить подписку LAN"
}
},
"config_confirm": {
"title": "Подтверждение настройки",
"description": "**{nick_name}** Здравствуйте! Подтвердите последнюю информацию о настройке и нажмите «Отправить». Интеграция будет перезагружена с использованием обновленных настроек.\r\n\r\nЯзык интеграции:\t{lang_new}\r\nИмя пользователя:\t{nick_name_new}\r\nРежим отладки Action:\t{action_debug}\r\nСкрыть непроизводственные сущности:\t{hide_non_standard_entities}\r\nИзменение устройства:\tДобавлено **{devices_add}** устройство, удалено **{devices_remove}** устройства\r\nИзменение правил преобразования:\tВсего **{trans_rules_count}** правил, обновлено **{trans_rules_count_success}** правил\r\n",
"data": {
"confirm": "Подтвердить изменения"
}
}
},
"progress": {
"oauth": "### {link_left}Пожалуйста, нажмите здесь, чтобы повторно войти в систему{link_right}"
},
"error": {
"not_auth": "Пользователь не аутентифицирован. Нажмите ссылку для аутентификации пользователя.",
"get_token_error": "Не удалось получить информацию об авторизации входа (OAuth token).",
"get_homeinfo_error": "Не удалось получить информацию о домашней сети.",
"get_cert_error": "Не удалось получить центральный сертификат.",
"no_family_selected": "Не выбрана семья.",
"no_devices": "В выбранной семье нет устройств. Пожалуйста, выберите семью с устройствами и продолжайте.",
"no_central_device": "Для режима центрального шлюза необходим существующий в локальной сети Home Assistant с доступным Xiaomi-шлюзом. Пожалуйста, проверьте, соответствует ли выбранная семья этому требованию.",
"mdns_discovery_error": "Ошибка сервиса поиска локальных устройств.",
"update_config_error": "Не удалось обновить информацию о конфигурации.",
"not_confirm": "Изменение не подтверждено. Пожалуйста, отметьте для подтверждения и отправки."
},
"abort": {
"network_connect_error": "Ошибка конфигурации. Сбой сетевого подключения. Проверьте настройки сети устройства.",
"options_flow_error": "Ошибка повторной настройки интеграции: {error}",
"re_add": "Пожалуйста, добавьте интеграцию снова. Информация об ошибке: {error}",
"storage_error": "Ошибка хранения интеграции. Пожалуйста, повторите попытку или добавьте интеграцию снова: {error}",
"inconsistent_account": "Информация об аккаунте не соответствует. Пожалуйста, используйте правильный аккаунт для входа."
}
}
}

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "米家集成",
"step": {
"eula": {
"title": "风险告知",
"description": "1. 您的小米用户信息和设备信息将会存储在您的 Home Assistant 系统中,**小米无法保证 Home Assistant 存储机制的安全性**。您需要负责防止您的信息被窃取。\r\n2. 此集成由开源社区维护,可能会出现稳定性问题或其它问题,使用此集成遇到相关问题时,您应当**向开源社区寻求帮助,而不是联系小米客服**。\r\n3. 您需要有一定的技术能力来维护您的本地运行环境,此集成对新手用户来说并不友好。\r\n4. 在使用此集成前请仔细阅读README。\r\n5. 为了用户能够稳定地使用集成,避免接口被滥用,**此集成仅允许在 Home Assistant 中使用详情请参考LICENSE**。\r\n",
"data": {
"eula": "我已悉知以上风险并自愿承担因使用集成所带来的相关风险。"
}
},
"auth_config": {
"title": "基础配置",
"description": "### 登录地区\r\n选择小米账号所在的地区。您可以在 `米家APP > 我的(位于底部菜单) > 更多设置 > 关于米家` 中查看。\r\n### 语言\r\n选择设备及实体名称所用的语言。缺少翻译的部分语句将使用英文显示。\r\n### OAuth2 认证跳转地址\r\nOAuth2 认证跳转地址为 **[http://homeassistant.local:8123](http://homeassistant.local:8123)**Home Assistant 需要与当前操作终端(例如,个人电脑)在同一局域网内,且操作终端能通过该地址访问 Home Assistant 首页,否则登录认证可能会失败。\r\n### 注意事项\r\n- 对于数百个及以上米家设备的用户,首次添加集成会耗费一些时间,请耐心等待。\r\n- 如果 Home Assistant 运行在docker环境下请确保docker网络模式为host否则会导致本地控制功能异常。\r\n- 集成本地控制功能存在一些依赖项请仔细阅读README。\r\n",
"data": {
"cloud_server": "登录地区",
"integration_language": "语言",
"oauth_redirect_url": "认证跳转地址"
}
},
"oauth_error": {
"title": "登录出现错误",
"description": "点击“下一步”重试"
},
"devices_filter": {
"title": "选择家庭与设备",
"description": "## 使用介绍\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令以实现本地化控制功能。本地局域网不存在中枢时会尝试通过小米OT协议发送控制指令以实现本地化控制功能。只有当上述本地化控制条件不满足时设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### 导入设备的家庭\r\n集成将添加已选中家庭中的设备。\r\n### 房间名同步模式\r\n将设备从米家APP同步到 Home Assistant 时,设备在 Home Assistant 中所处区域的名称的命名方式将遵循以下规则。注意设备同步过程不会改变米家APP中家庭和房间的设置。\r\n- 不同步:设备不会被添加至任何区域。\r\n- 其它选项设备所添加到的区域以米家APP中的家庭或房间名称命名。\r\n### Action 调试模式\r\n对于设备 MIoT-Spec-V2 定义的方法,在生成通知实体之外,还会生成一个文本输入框实体,您可以在调试时用它向设备发送控制指令。\r\n### 隐藏非标准生成实体\r\n隐藏名称以“*”开头的非标准 MIoT-Spec-V2 实例生成的实体。\r\n\r\n&emsp;\r\n### {nick_name} 您好!请选择集成控制模式以及您想要添加的设备所处的家庭。\r\n",
"data": {
"ctrl_mode": "控制模式",
"home_infos": "导入设备的家庭",
"area_name_rule": "房间名同步模式",
"action_debug": "Action 调试模式",
"hide_non_standard_entities": "隐藏非标准生成实体"
}
}
},
"progress": {
"oauth": "### {link_left}请点击此处进行登录{link_right}\r\n登录成功后将会自动跳转至下一页面"
},
"error": {
"eula_not_agree": "请阅读风险告知文本。",
"get_token_error": "获取登录授权信息OAuth token失败。",
"get_homeinfo_error": "获取家庭信息失败。",
"mdns_discovery_error": "本地设备发现服务异常。",
"get_cert_error": "获取中枢证书失败。",
"no_family_selected": "未选择家庭。",
"no_devices": "选择的家庭中没有设备。请选择有设备的家庭,而后继续。",
"no_central_device": "【中枢网关模式】需要 Home Assistant 所在的局域网中存在可用的小米中枢网关。请检查选择的家庭是否符合该要求。"
},
"abort": {
"network_connect_error": "配置失败。网络连接异常,请检查设备网络配置。",
"already_configured": "该用户已配置完成。请进入集成页面,点击“配置”按钮修改配置。",
"invalid_auth_info": "认证信息已过期。请进入集成页面,点击“配置”按钮重新认证。",
"config_flow_error": "集成配置错误:{error}"
}
},
"options": {
"step": {
"auth_config": {
"title": "认证配置",
"description": "检测到本地认证信息过期,请重新开始认证\r\n### 当前登录地区: {cloud_server}\r\n### OAuth2 认证跳转地址\r\nOAuth2 认证跳转地址为 **[http://homeassistant.local:8123](http://homeassistant.local:8123)**Home Assistant 需要与当前操作终端(例如,个人电脑)在同一局域网内,且操作终端能通过该地址访问 Home Assistant 首页,否则登录认证可能会失败。\r\n",
"data": {
"oauth_redirect_url": "认证跳转地址"
}
},
"oauth_error": {
"title": "登录出现错误",
"description": "点击“下一步”重试"
},
"config_options": {
"title": "配置选项",
"description": "### {nick_name} 您好!\r\n\r\n小米账号ID{uid}\r\n当前登录区域{cloud_server}\r\n\r\n请勾选需要重新配置的选项然后点击“下一步”。\r\n",
"data": {
"integration_language": "集成语言",
"update_user_info": "更新用户信息",
"update_devices": "更新设备列表",
"action_debug": "Action 调试模式",
"hide_non_standard_entities": "隐藏非标准生成实体",
"update_trans_rules": "更新实体转换规则",
"update_lan_ctrl_config": "更新局域网控制配置"
}
},
"update_user_info": {
"title": "更新用户昵称",
"description": "{nick_name} 您好!请在下方修改您的用户昵称。\r\n",
"data": {
"nick_name": "用户昵称"
}
},
"devices_filter": {
"title": "重新选择家庭与设备",
"description": "## 使用介绍\r\n### 控制模式\r\n- 自动:本地局域网内存在可用的小米中枢网关时, Home Assistant 会优先通过中枢网关发送设备控制指令以实现本地化控制功能。本地局域网不存在中枢时会尝试通过小米OT协议发送控制指令以实现本地化控制功能。只有当上述本地化控制条件不满足时设备控制指令才会通过云端发送。\r\n- 云端:控制指令仅通过云端发送。\r\n### 导入设备的家庭\r\n集成将添加已选中家庭中的设备。\r\n&emsp;\r\n### {nick_name} 您好!请选择集成控制模式以及您想要添加的设备所处的家庭。\r\n",
"data": {
"ctrl_mode": "控制模式",
"home_infos": "导入设备的家庭"
}
},
"update_trans_rules": {
"title": "更新实体转换规则",
"description": "## 使用介绍\r\n- 更新当前集成实例中设备的实体信息,包含 MIoT-Spec-V2 多语言配置、布尔值翻译、过滤规则。\r\n- **警告**:该配置为全局配置,将会更新本地缓存,会影响所有集成实例。\r\n- 该操作会耗费一定时间,请耐心等待,勾选“确认更新”,点击“下一步”开始更新 **{urn_count}** 条规则,否则跳过更新。\r\n",
"data": {
"confirm": "确认更新"
}
},
"update_lan_ctrl_config": {
"title": "更新局域网控制配置",
"description": "## 使用介绍\r\n更新小米局域网控制功能的配置信息。当云端和中枢网关均无法控制设备时集成会尝试通过局域网控制设备。如果未选择网卡局域网控制将不会生效。\r\n- 目前只支持控制局域网内的兼容 MIoT-Spec-V2 的 IP 设备部分2020年之前生产的旧设备可能不支持局域网控制或者不支持局域网订阅。\r\n- 请选择和被控设备同一局域网的网卡(支持多选)。如果选择多个网卡导致 Home Assistant 到同一局域网存在多个连接,建议只保留最优的网络连接,否则可能会影响设备的正常使用。\r\n- 如果局域网内存在支持本地控制的终端设备(带屏音箱、手机等),启用局域网订阅可能会导致本地自动化或者设备异常。\r\n- **警告**:该配置为全局配置,会影响所有集成实例,请谨慎修改。\r\n{notice_net_dup}\r\n",
"data": {
"net_interfaces": "请选择使用的网卡",
"enable_subscribe": "启用局域网订阅"
}
},
"config_confirm": {
"title": "确认配置",
"description": "**{nick_name}** 您好!请确认最新的配置信息,然后点击“提交”。\r\n集成将会使用更新后的配置重新载入。\r\n\r\n集成语言\t{lang_new}\r\n用户昵称\t{nick_name_new}\r\nAction 调试模式:\t{action_debug}\r\n隐藏非标准生成实体\t{hide_non_standard_entities}\r\n设备变化\t新增 **{devices_add}** 个设备,移除 **{devices_remove}** 个设备\r\n转换规则变化\t共条 **{trans_rules_count}** 规则,更新 **{trans_rules_count_success}** 条规则\r\n",
"data": {
"confirm": "确认修改"
}
}
},
"progress": {
"oauth": "### {link_left}请点击此处重新登录{link_right}"
},
"error": {
"not_auth": "用户未认证。请点击认证链接以认证用户身份。",
"get_token_error": "获取登录授权信息OAuth token失败。",
"get_homeinfo_error": "获取家庭信息失败。",
"get_cert_error": "获取中枢证书失败。",
"no_family_selected": "未选择家庭。",
"no_devices": "选择的家庭中没有设备,请选择有设备的家庭,而后继续。",
"no_central_device": "【中枢网关模式】需要 Home Assistant 所在的局域网中存在可用的小米中枢网关。请检查选择的家庭是否符合该要求。",
"mdns_discovery_error": "本地设备发现服务异常。",
"update_config_error": "配置信息更新失败。",
"not_confirm": "未确认修改项。请勾选确认后再提交。"
},
"abort": {
"network_connect_error": "配置失败。网络连接异常,请检查设备网络配置。",
"options_flow_error": "集成重新配置错误:{error}",
"re_add": "请重新添加集成,错误信息:{error}",
"storage_error": "集成存储模块异常。请重试或者重新添加集成:{error}",
"inconsistent_account": "账号信息不一致。请使用正确的账号登录。"
}
}
}

View File

@ -0,0 +1,144 @@
{
"config": {
"flow_title": "米家集成",
"step": {
"eula": {
"title": "風險告知",
"description": "1. 您的**小米用戶信息和設備信息**將會存儲在您的 Home Assistant 系統中,**小米無法保證 Home Assistant 存儲機制的安全性**。您需要負責防止您的信息被竊取。\r\n2. 此集成由開源社區維護,可能會出現穩定性問題或其他問題,使用此集成遇到相關問題時,您應當**向開源社區尋求幫助,而不是聯繫小米客服**。\r\n3. 您需要有一定的技術能力來維護您的本地運行環境,此集成對新手用戶來說並不友好。\r\n4. 在使用此集成前,請**仔細閱讀README**。\r\n5. 為了用戶能夠穩定地使用集成,避免接口被濫用,**此集成僅允許在 Home Assistant 中使用詳情請參考LICENSE**。\r\n",
"data": {
"eula": "我已悉知以上風險並自願承擔因使用集成所帶來的相關風險。"
}
},
"auth_config": {
"title": "基礎配置",
"description": "### 登錄地區\r\n選擇小米帳號所在的地區。您可以在 `米家APP > 我的(位於底部菜單) > 更多設置 > 關於米家` 中查看。\r\n### 語言\r\n選擇設備及實體名稱所用的語言。缺少翻譯的部分語句將使用英文顯示。\r\n### OAuth2 認證跳轉地址\r\nOAuth2 認證跳轉地址為 **[http://homeassistant.local:8123](http://homeassistant.local:8123)**Home Assistant 需要與當前操作終端(例如,個人電腦)在同一局域網內,且操作終端能通過該地址訪問 Home Assistant 首頁,否則登錄認證可能會失敗。\r\n### 注意事項\r\n- 對於數百個及以上米家設備的用戶,首次添加集成會耗費一些時間,請耐心等待。\r\n- 如果 Home Assistant 運行在docker環境下請確保docker網絡模式為host否則會導致本地控制功能異常。\r\n- 集成本地控制功能存在一些依賴項請仔細閱讀README。\r\n",
"data": {
"cloud_server": "登錄地區",
"integration_language": "語言",
"oauth_redirect_url": "認證跳轉地址"
}
},
"oauth_error": {
"title": "登錄出現錯誤",
"description": "點擊“下一步”重試"
},
"devices_filter": {
"title": "選擇家庭與設備",
"description": "## 使用介紹\r\n### 控制模式\r\n- 自動:本地區域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令以實現本地化控制功能。本地區域網不存在中樞時會嘗試通過小米OT協議發送控制指令以實現本地化控制功能。只有當上述本地化控制條件不滿足時設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令僅通過雲端發送。\r\n### 導入設備的家庭\r\n集成將添加已選中家庭中的設備。\r\n### 房間名同步模式\r\n將設備從米家APP同步到 Home Assistant 時,設備在 Home Assistant 中所處區域的名稱的命名方式將遵循以下規則。注意設備同步過程不會改變米家APP中家庭和房間的設置。\r\n- 不同步:設備不會被添加至任何區域。\r\n- 其它選項設備所添加到的區域以米家APP中的家庭或房間名稱命名。\r\n### Action 調試模式\r\n對於設備 MIoT-Spec-V2 定義的方法,在生成通知實體之外,還會生成一個文本輸入框實體,您可以在調試時用它向設備發送控制指令。\r\n### 隱藏非標準生成實體\r\n隱藏名稱以“*”開頭的非標準 MIoT-Spec-V2 實例生成的實體。\r\n\r\n&emsp;\r\n### {nick_name} 您好!請選擇集成控制模式以及您想要添加的設備所處的家庭。\r\n",
"data": {
"ctrl_mode": "控制模式",
"home_infos": "導入設備的家庭",
"area_name_rule": "房間名同步模式",
"action_debug": "Action 調試模式",
"hide_non_standard_entities": "隱藏非標準生成實體"
}
}
},
"progress": {
"oauth": "### {link_left}請點擊此處進行登錄{link_right}\r\n(登錄成功後,將會自動跳轉至下一頁面)"
},
"error": {
"eula_not_agree": "請閱讀風險告知文本。",
"get_token_error": "獲取登錄授權信息OAuth token失敗。",
"get_homeinfo_error": "獲取家庭信息失敗。",
"mdns_discovery_error": "本地設備發現服務異常。",
"get_cert_error": "獲取中樞證書失敗。",
"no_family_selected": "未選擇家庭。",
"no_devices": "選擇的家庭中沒有設備。請選擇有設備的家庭,而後繼續。",
"no_central_device": "【中樞網關模式】需要 Home Assistant 所在的局域網中存在可用的小米中樞網關。請檢查選擇的家庭是否符合該要求。"
},
"abort": {
"network_connect_error": "配置失敗。網絡連接異常,請檢查設備網絡配置。",
"already_configured": "該用戶已配置完成。請進入集成頁面,點擊“配置”按鈕修改配置。",
"invalid_auth_info": "認證信息已過期。請進入集成頁面,點擊“配置”按鈕重新認證。",
"config_flow_error": "集成配置錯誤:{error}"
}
},
"options": {
"step": {
"auth_config": {
"title": "認證配置",
"description": "檢測到本地認證信息過期,請重新開始認證\r\n### 當前登錄地區: {cloud_server}\r\n### OAuth2 認證跳轉地址\r\nOAuth2 認證跳轉地址為 **[http://homeassistant.local:8123](http://homeassistant.local:8123)**Home Assistant 需要與當前操作終端(例如,個人電腦)在同一局域網內,且操作終端能通過該地址訪問 Home Assistant 首頁,否則登錄認證可能會失敗。\r\n",
"data": {
"oauth_redirect_url": "認證跳轉地址"
}
},
"oauth_error": {
"title": "登錄出現錯誤",
"description": "點擊“下一步”重試"
},
"config_options": {
"title": "配置選項",
"description": "### {nick_name} 您好!\r\n\r\n小米帳號ID{uid}\r\n當前登錄區域{cloud_server}\r\n\r\n請勾選需要重新配置的選項然後點擊“下一步”。\r\n",
"data": {
"integration_language": "集成語言",
"update_user_info": "更新用戶信息",
"update_devices": "更新設備列表",
"action_debug": "Action 調試模式",
"hide_non_standard_entities": "隱藏非標準生成實體",
"update_trans_rules": "更新實體轉換規則",
"update_lan_ctrl_config": "更新局域網控制配置"
}
},
"update_user_info": {
"title": "更新用戶暱稱",
"description": "{nick_name} 您好!請在下方修改您的用戶暱稱。\r\n",
"data": {
"nick_name": "用戶暱稱"
}
},
"devices_filter": {
"title": "重新選擇家庭與設備",
"description": "\r\n## 使用介紹\r\n### 控制模式\r\n- 自動:本地局域網內存在可用的小米中樞網關時, Home Assistant 會優先通過中樞網關發送設備控制指令,以實現本地化控制功能。只有當本地化控制條件不滿足時,設備控制指令才會通過雲端發送。\r\n- 雲端:控制指令強制通過雲端發送。\r\n### 導入設備的家庭\r\n集成將添加已選中家庭中的設備。\r\n&emsp;\r\n### {nick_name} 您好!請選擇集成控制模式以及您想要添加的設備所處的家庭。\r\n",
"data": {
"ctrl_mode": "控制模式",
"home_infos": "導入設備的家庭"
}
},
"update_trans_rules": {
"title": "更新實體轉換規則",
"description": "## 使用介紹\r\n- 更新當前集成實例中設備的實體信息包含SPEC多語言配置、SPEC布爾值翻譯、SPEC模型過濾。\r\n- **警告:該配置為全局配置**,會直接更新本地緩存,如果其它集成實例中有相同型號設備,相關實例重載後也會更新。\r\n- 該操作會耗費一定時間,請耐心等待,勾選“確認更新”,點擊“下一步”開始更新 **{urn_count}** 條規則,否則跳過更新。\r\n",
"data": {
"confirm": "確認更新"
}
},
"update_lan_ctrl_config": {
"title": "更新局域網控制配置",
"description": "## 使用介紹\r\n更新**局域網控制米家設備**時的配置信息,當雲端和中樞網關無法控制設備時,集成會嘗試通過局域網控制設備;如果未選擇網卡,局域網控制將不會啟用。\r\n- 目前只支持控制局域網內 **SPEC v2** WiFi 設備,部分舊設備可能不支持控制或者不支持屬性同步。\r\n- 請選擇和設備同一網絡的網卡(支持多選),如果選擇的網卡存在兩個及以上連接在同一網絡中,建議選擇網絡連接最優的,否則可能會**影響設備正常使用**。\r\n- **如果局域網中存在支持本地控制的終端設備(網關、手機等),啟用局域網訂閱可能會導致本地自動化或者設備異常,請謹慎使用**。\r\n- **警告:該配置為全局配置,修改會影響其他集成實例,請謹慎修改**。\r\n{notice_net_dup}\r\n",
"data": {
"net_interfaces": "請選擇使用的網卡",
"enable_subscribe": "啟用局域網訂閱"
}
},
"config_confirm": {
"title": "確認配置",
"description": "**{nick_name}** 您好!請確認最新的配置信息,然後點擊“提交”。\r\n集成將會使用更新後的配置重新載入。\r\n\r\n集成語言\t{lang_new}\r\n用戶暱稱\t{nick_name_new}\r\nAction 調試模式:\t{action_debug}\r\n隱藏非標準生成實體\t{hide_non_standard_entities}\r\n設備變化\t新增 **{devices_add}** 個設備,移除 **{devices_remove}** 個設備\r\n轉換規則變化\t共條 **{trans_rules_count}** 規則,更新 **{trans_rules_count_success}** 條規則\r\n",
"data": {
"confirm": "確認修改"
}
}
},
"progress": {
"oauth": "### {link_left}請點擊此處重新登錄{link_right}"
},
"error": {
"not_auth": "用戶未認證。請點擊認證鏈接以認證用戶身份。",
"get_token_error": "獲取登錄授權信息OAuth token失敗。",
"get_homeinfo_error": "獲取家庭信息失敗。",
"get_cert_error": "獲取中樞證書失敗。",
"no_family_selected": "未選擇家庭。",
"no_devices": "選擇的家庭中沒有設備,請選擇有設備的家庭,而後繼續。",
"no_central_device": "【中樞網關模式】需要 Home Assistant 所在的局域網中存在可用的小米中樞網關。請檢查選擇的家庭是否符合該要求。",
"mdns_discovery_error": "本地設備發現服務異常。",
"update_config_error": "配置信息更新失敗。",
"not_confirm": "未確認修改項。請勾選確認後再提交。"
},
"abort": {
"network_connect_error": "配置失敗。網絡連接異常,請檢查設備網絡配置。",
"options_flow_error": "集成重新配置錯誤:{error}",
"re_add": "請重新添加集成,錯誤信息:{error}",
"storage_error": "集成存儲模塊異常。請重試或者重新添加集成:{error}",
"inconsistent_account": "帳號信息不一致。請使用正確的帳號登錄。"
}
}
}

View File

@ -0,0 +1,219 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Vacuum entities for Xiaomi Home.
"""
from __future__ import annotations
from typing import Any, Optional
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.vacuum import (
StateVacuumEntity,
VacuumEntityFeature
)
from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
from .miot.miot_spec import (
MIoTSpecAction,
MIoTSpecProperty)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for data in miot_device.entity_list.get('vacuum', []):
new_entities.append(
Vacuum(miot_device=miot_device, entity_data=data))
if new_entities:
async_add_entities(new_entities)
class Vacuum(MIoTServiceEntity, StateVacuumEntity):
"""Vacuum entities for Xiaomi Home."""
# pylint: disable=unused-argument
_prop_status: Optional[MIoTSpecProperty]
_prop_fan_level: Optional[MIoTSpecProperty]
_prop_battery_level: Optional[MIoTSpecProperty]
_action_start_sweep: Optional[MIoTSpecAction]
_action_stop_sweeping: Optional[MIoTSpecAction]
_action_pause_sweeping: Optional[MIoTSpecAction]
_action_continue_sweep: Optional[MIoTSpecAction]
_action_stop_and_gocharge: Optional[MIoTSpecAction]
_action_identify: Optional[MIoTSpecAction]
_status_map: Optional[dict[int, str]]
_fan_level_map: Optional[dict[int, str]]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_supported_features = VacuumEntityFeature(0)
self._prop_status = None
self._prop_fan_level = None
self._prop_battery_level = None
self._action_start_sweep = None
self._action_stop_sweeping = None
self._action_pause_sweeping = None
self._action_continue_sweep = None
self._action_stop_and_gocharge = None
self._action_identify = None
self._status_map = None
self._fan_level_map = None
# properties
for prop in entity_data.props:
if prop.name == 'status':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'invalid status value_list, %s', self.entity_id)
continue
self._status_map = {
item['value']: item['description']
for item in prop.value_list}
self._prop_status = prop
elif prop.name == 'fan-level':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'invalid fan-level value_list, %s', self.entity_id)
continue
self._fan_level_map = {
item['value']: item['description']
for item in prop.value_list}
self._attr_fan_speed_list = list(self._fan_level_map.values())
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
self._prop_fan_level = prop
elif prop.name == 'battery-level':
self._attr_supported_features |= VacuumEntityFeature.BATTERY
self._prop_battery_level = prop
# action
for action in entity_data.actions:
if action.name == 'start-sweep':
self._attr_supported_features |= VacuumEntityFeature.START
self._action_start_sweep = action
elif action.name == 'stop-sweeping':
self._attr_supported_features |= VacuumEntityFeature.STOP
self._action_stop_sweeping = action
elif action.name == 'pause-sweeping':
self._attr_supported_features |= VacuumEntityFeature.PAUSE
self._action_pause_sweeping = action
elif action.name == 'continue-sweep':
self._action_continue_sweep = action
elif action.name == 'stop-and-gocharge':
self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME
self._action_stop_and_gocharge = action
elif action.name == 'identify':
self._attr_supported_features |= VacuumEntityFeature.LOCATE
self._action_identify = action
async def async_start(self) -> None:
"""Start or resume the cleaning task."""
if self.state.lower() in ['paused', '暂停中']:
await self.action_async(action=self._action_continue_sweep)
return
await self.action_async(action=self._action_start_sweep)
async def async_stop(self, **kwargs: Any) -> None:
"""Stop the vacuum cleaner, do not return to base."""
await self.action_async(action=self._action_stop_sweeping)
async def async_pause(self) -> None:
"""Pause the cleaning task."""
await self.action_async(action=self._action_pause_sweeping)
async def async_return_to_base(self, **kwargs: Any) -> None:
"""Set the vacuum cleaner to return to the dock."""
await self.action_async(action=self._action_stop_and_gocharge)
async def async_clean_spot(self, **kwargs: Any) -> None:
"""Perform a spot clean-up."""
async def async_locate(self, **kwargs: Any) -> None:
"""Locate the vacuum cleaner."""
await self.action_async(action=self._action_identify)
async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
"""Set fan speed."""
@property
def state(self) -> Optional[str]:
"""Return the current state of the vacuum cleaner."""
return self.get_map_description(
map_=self._status_map,
key=self.get_prop_value(prop=self._prop_status))
@property
def battery_level(self) -> Optional[int]:
"""Return the current battery level of the vacuum cleaner."""
return self.get_prop_value(prop=self._prop_battery_level)
@property
def fan_speed(self) -> Optional[str]:
"""Return the current fan speed of the vacuum cleaner."""
return self.get_map_description(
map_=self._fan_level_map,
key=self.get_prop_value(prop=self._prop_fan_level))

View File

@ -0,0 +1,207 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 2024 Xiaomi Corporation.
The ownership and intellectual property rights of Xiaomi Home Assistant
Integration and related Xiaomi cloud service API interface provided under this
license, including source code and object code (collectively, "Licensed Work"),
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
hereby grants you a personal, limited, non-exclusive, non-transferable,
non-sublicensable, and royalty-free license to reproduce, use, modify, and
distribute the Licensed Work only for your use of Home Assistant for
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
you to use the Licensed Work for any other purpose, including but not limited
to use Licensed Work to develop applications (APP), Web services, and other
forms of software.
You may reproduce and distribute copies of the Licensed Work, with or without
modifications, whether in source or object form, provided that you must give
any other recipients of the Licensed Work a copy of this License and retain all
copyright and disclaimers.
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied, including, without
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
for any direct, indirect, special, incidental, or consequential damages or
losses arising from the use or inability to use the Licensed Work.
Xiaomi reserves all rights not expressly granted to you in this License.
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
does not authorize you in any form to use the trademarks, copyrights, or other
forms of intellectual property rights of Xiaomi and its affiliates, including,
without limitation, without obtaining other written permission from Xiaomi, you
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
may make the public associate with Xiaomi in any form to publicize or promote
the software or hardware devices that use the Licensed Work.
Xiaomi has the right to immediately terminate all your authorization under this
License in the event:
1. You assert patent invalidation, litigation, or other claims against patents
or other intellectual property rights of Xiaomi or its affiliates; or,
2. You make, have made, manufacture, sell, or offer to sell products that knock
off Xiaomi or its affiliates' products.
Water heater entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from typing import Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.water_heater import (
ATTR_TEMPERATURE,
WaterHeaterEntity,
WaterHeaterEntityFeature
)
from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
from .miot.miot_spec import MIoTSpecProperty
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a config entry."""
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
config_entry.entry_id]
new_entities = []
for miot_device in device_list:
for data in miot_device.entity_list.get('water_heater', []):
new_entities.append(WaterHeater(
miot_device=miot_device, entity_data=data))
if new_entities:
async_add_entities(new_entities)
class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
"""Water heater entities for Xiaomi Home."""
_prop_on: Optional[MIoTSpecProperty]
_prop_temp: Optional[MIoTSpecProperty]
_prop_target_temp: Optional[MIoTSpecProperty]
_prop_mode: Optional[MIoTSpecProperty]
_mode_list: Optional[dict[any, any]]
def __init__(
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None:
"""Initialize the Water heater."""
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_temperature_unit = None
self._attr_supported_features = WaterHeaterEntityFeature(0)
self._prop_on = None
self._prop_temp = None
self._prop_target_temp = None
self._prop_mode = None
self._mode_list = None
# properties
for prop in entity_data.props:
# on
if prop.name == 'on':
self._prop_on = prop
# temperature
if prop.name == 'temperature':
if isinstance(prop.value_range, dict):
self._attr_min_temp = prop.value_range['min']
self._attr_max_temp = prop.value_range['max']
if (
self._attr_temperature_unit is None
and prop.external_unit
):
self._attr_temperature_unit = prop.external_unit
self._prop_temp = prop
else:
_LOGGER.error(
'invalid temperature value_range format, %s',
self.entity_id)
# target-temperature
if prop.name == 'target-temperature':
if self._attr_temperature_unit is None and prop.external_unit:
self._attr_temperature_unit = prop.external_unit
self._attr_supported_features |= (
WaterHeaterEntityFeature.TARGET_TEMPERATURE)
self._prop_target_temp = prop
# mode
if prop.name == 'mode':
if (
not isinstance(prop.value_list, list)
or not prop.value_list
):
_LOGGER.error(
'mode value_list is None, %s', self.entity_id)
continue
self._mode_list = {
item['value']: item['description']
for item in prop.value_list}
self._attr_operation_list = list(self._mode_list.values())
self._attr_supported_features |= (
WaterHeaterEntityFeature.OPERATION_MODE)
self._prop_mode = prop
async def async_turn_on(self) -> None:
"""Turn the water heater on."""
await self.set_property_async(prop=self._prop_on, value=True)
async def async_turn_off(self) -> None:
"""Turn the water heater off."""
await self.set_property_async(prop=self._prop_on, value=False)
async def async_set_temperature(self, **kwargs: any) -> None:
"""Set the temperature the water heater should heat water to."""
await self.set_property_async(
prop=self._prop_target_temp, value=kwargs[ATTR_TEMPERATURE])
async def async_set_operation_mode(self, operation_mode: str) -> None:
"""Set the operation mode of the water heater.
Must be in the operation_list.
"""
await self.set_property_async(
prop=self._prop_mode,
value=self.__get_mode_value(description=operation_mode))
async def async_turn_away_mode_on(self) -> None:
"""Set the water heater to away mode."""
await self.hass.async_add_executor_job(self.turn_away_mode_on)
@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return self.get_prop_value(prop=self._prop_temp)
@property
def target_temperature(self) -> Optional[float]:
"""Return the target temperature."""
return self.get_prop_value(prop=self._prop_target_temp)
@property
def current_operation(self) -> Optional[str]:
"""Return the current mode."""
return self.__get_mode_description(
key=self.get_prop_value(prop=self._prop_mode))
def __get_mode_description(self, key: int) -> Optional[str]:
"""Convert mode value to description."""
if self._mode_list is None:
return None
return self._mode_list.get(key, None)
def __get_mode_value(self, description: str) -> Optional[int]:
"""Convert mode description to value."""
if self._mode_list is None:
return None
for key, value in self._mode_list.items():
if value == description:
return key
return None

7
doc/CHANGELOG.md Normal file
View File

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

105
doc/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,105 @@
# Contribution Guidelines
[English](./CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
Thank you for considering contributing to our project! We appreciate your efforts to make our project better.
Before you start contributing, please take a moment to review the following guidelines.
## How Can I Contribute?
### Reporting Bugs
If you encounter a bug in the project, please [open an issue](https://github.com/XiaoMi/ha_xiaomi_home/issues/new/) on GitHub and provide the detailed information about the bug, including the steps to reproduce the bug, the logs of debug level and the time when it occurs.
The [method](https://www.home-assistant.io/integrations/logger/#log-filters) to set the integration's log level:
```
# Set the log level in configuration.yaml
logger:
default: critical
logs:
custom_components.xiaomi_home: debug
```
### Suggesting Enhancements
If you have ideas for enhancements or new features, you are welcomed to [start a discussion on ideas](https://github.com/XiaoMi/ha_xiaomi_home/discussions/new?category=ideas) on GitHub to discuss your ideas.
### Contributing Code
1. Fork the repository and create your branch from `main`.
2. Ensure that your code adheres to the project coding standard.
3. Make sure that your commit messages are descriptive and meaningful.
4. Pull requests should be accompanied by a clear description of the problem and the solution.
5. Update the documents if necessary.
6. Run tests if they are available and ensure they pass.
## Pull Request Guidelines
Before submitting a pull request, please make sure that the following requirements are met:
- Your pull request addresses a single issue or feature.
- You have tested your changes locally.
- Your code follows the project's [code style](#code-style). Run [`pylint`](https://github.com/google/pyink) over your code using this [pylintrc](../.pylintrc).
- All existing tests pass, and you have added new tests if applicable.
- Any dependent changes are documented.
## Code Style
We follow [Google Style](https://google.github.io/styleguide/pyguide.html) for code style and formatting. Please make sure to adhere to this guideline in your contributions.
## Commit Message Format
```
<type>: <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
type: commit type is one of the following
- feat: A new feature.
- fix: A bug fix.
- docs: Documentation only changes.
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.).
- refactor: A code change that neither fixes a bug nor adds a feature.
- perf: A code change that improves performance.
- test: Adding missing tests or correcting existing tests.
- chore: Changes to the build process or auxiliary tools and libraries.
- revert: Reverting a previous commit.
subject: A short summary in imperative, present tense. Not capitalized. No period at the end.
body: A detailed description of the commit and the motivation for the change. The body is mandatory for all commits except for those of type "docs".
footer: Optional. The footer is the place to reference GitHub issues and PRs that this commit closes or is related to.
## Naming Conventions
### Xiaomi Naming Convention
- When describing Xiaomi, always use "Xiaomi" in full. Variable names can use "xiaomi" or "mi".
- When describing Xiaomi Home, always use "Xiaomi Home". Variable names can use "mihome" or "MiHome".
- When describing Xiaomi IoT, always use "MIoT". Variable names can use "miot" or "MIoT".
### Third-Party Platform Naming Convention
- When describing Home Assistant, always use "Home Assistant". Variables can use "hass" or "hass_xxx".
### Other Naming Conventions
- When using mixed Chinese and English sentences in the document, there must be a space between Chinese and English or the English words must be quoted by Chinese quotation marks. (It is best to write code comments this way too.)
## Licensing
When contributing to this project, you agree that your contributions will be licensed under the project's [LICENSE](../LICENSE.md).
## How to Get Help
If you need help or have questions, feel free to ask in [discussions](https://github.com/XiaoMi/ha_xiaomi_home/discussions/) on GitHub.
You can also contact ha_xiaomi_home@xiaomi.com

105
doc/CONTRIBUTING_zh.md Normal file
View File

@ -0,0 +1,105 @@
# 贡献指南
[English](./CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
感谢您考虑为我们的项目做出贡献!您的努力将使我们的项目变得更好。
在您开始贡献之前,请花一点时间阅读以下准则:
## 我可以如何贡献?
### 报告问题
如果您在项目中遇到错误,请在 GitHub 上[报告问题](https://github.com/XiaoMi/ha_xiaomi_home/issues/new/),并提供关于错误的详细信息,包括复现步骤、 debug 级日志以及错误出现的时间。
集成开启 debug 级日志的[方法](https://www.home-assistant.io/integrations/logger/#log-filters)
```
# configuration.yaml 设置打印日志等级
logger:
default: critical
logs:
custom_components.xiaomi_home: debug
```
### 建议增强功能
如果您有增强或新功能的想法,欢迎您在 GitHub 讨论区[创建想法](https://github.com/XiaoMi/ha_xiaomi_home/discussions/new?category=ideas) 。我们期待您的建议!
### 贡献代码
1. Fork 该仓库并从 `main` 创建您的分支。
2. 确保您的代码符合项目的编码规范。
3. 确保您的提交消息描述清晰。
4. 提交请求应附有明确的问题描述和解决方案。
5. 如果必要,请更新文档。
6. 请运行测试并确保测试通过。
## 拉取请求准则
在提交拉取请求之前,请确保满足以下要求:
- 您的拉取请求解决了单个问题或功能。
- 您已在本地测试过您的更改。
- 您的代码遵循项目的[代码规范](#代码规范),已运行 [`pylint`](https://github.com/google/pyink) 搭配本项目的 [pylintrc](../.pylintrc) 检查代码。
- 所有现有测试都通过,并且如果适用,您已添加了新的测试。
- 任何依赖更改都有文档说明。
## 代码规范
本项目的代码格式遵循 [Google Style](https://google.github.io/styleguide/pyguide.html) 。请确保您的贡献符合该指南。
## Commit Message 格式
```
<type>: <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
type :有以下几种变更类型
- feat新增功能。
- fix修复问题。
- docs仅仅修改了文档。
- style仅仅是对格式进行修改如逗号、缩进、空格等不改变代码逻辑。
- refactor代码重构没有新增功能或修复问题。
- perf优化性能。
- test增加或修改测试用例。
- chore修改编译流程或变更依赖库和工具等。
- revert版本回滚。
subject :简洁的标题,描述本次提交的概要。使用祈使句、现在时态,首字母小写,结尾不加句号。
body :描述本次提交的详细内容,解释为什么需要这些变更。除 docs 之外的变更类型必须包含 body。
footer :(可选)关联的 issue 或 pull request 编号。
## 命名规范
### 米家命名规范
- 描述“小米”时必须使用“Xiaomi”变量名称可以使用“xiaomi”或“mi”。
- 描述“米家”时必须使用“Xiaomi Home”变量名称可以使用“mihome”或“MiHome”。
- 描述“小米IoT”时必须使用“MIoT”变量名称可以使用“miot”或“MIoT”。
### 第三方平台命名规范
- 描述“Home Assistant”时必须使用“Home Assistant”变量可以使用“hass”或“hass_xxx”。
### 其它命名规范
- 文档中的中文语句包含英文时,如果英文没有被中文引号括起来,那么中文与英文之间必须有一个空格。(最好代码注释也这么写)
## 许可
在为本项目做出贡献时,您同意您的贡献遵循本项目的[许可证](../LICENSE.md) 。
## 获取帮助
如果您需要帮助或有疑问,可在 GitHub 的[讨论区](https://github.com/XiaoMi/ha_xiaomi_home/discussions/)询问。
您还可以联系 ha_xiaomi_home@xiaomi.com

396
doc/README_zh.md Normal file
View File

@ -0,0 +1,396 @@
# Home Assistant 米家集成
[English](../README.md) | [简体中文](./README_zh.md)
米家集成是一个由小米官方提供支持的 Home Assistant 的集成组件,它可以让您在 Home Assistant 中使用小米 IoT 智能设备。
## 安装
> Home Assistant 版本要求:
>
> - Core $\geq$ 2024.12.1
> - Operating System $\geq$ 14.0
### 方法 1使用 git clone 命令从 GitHub 下载
```bash
cd config
git clone https://github.com/XiaoMi/ha_xiaomi_home.git
cd ha_xiaomi_home
./install.sh /config
```
推荐使用此方法安装米家集成。当您想要更新至特定版本时,只需要切换至相应的 Tag 。
例如,更新米家集成版本至 v1.0.0
```bash
cd config/ha_xiaomi_home
git checkout v1.0.0
./install.sh /config
```
### 方法 2: [HACS](https://hacs.xyz/)
HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category: Integration > ADD
> 米家集成暂未添加到 HACS 商店,敬请期待。
### 方法 3通过 [Samba](https://github.com/home-assistant/addons/tree/master/samba) 或 [FTPS](https://github.com/hassio-addons/addon-ftp) 手动安装
下载并将 `custom_components/xiaomi_home` 文件夹复制到 Home Assistant 的 `config/custom_components` 文件夹下。
## 配置
### 登录
[设置 > 设备与服务 > 添加集成](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > 搜索“`Xiaomi Home`” > 下一步 > 请点击此处进行登录 > 使用小米账号登录
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
### 添加 MIoT 设备
登录成功后,会弹出会话框“选择家庭与设备”。您可以选择需要添加的米家家庭,该家庭内的所有设备将导入 Home Assistant 。
### 多账号登录
用一个小米账号登录并配置完成后,您可以在 Xiaomi Home Integration 页面中继续添加其他小米账号。
方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 添加中枢 > 下一步 > 请点击此处进行登录 > 使用小米账号登录
[![Open your Home Assistant instance and show an integration.](https://my.home-assistant.io/badges/integration.svg)](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
### 修改配置项
在会话框“配置选项”中,可选择需要变更的配置项。您可以修改用户昵称或更新从米家 APP 导入的设备列表。
方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > 选择需要变更的配置项
### Action 调试模式
开启该模式后,您可手动向设备发送带参数的 Action 控制指令。发送带参数的 Action 控制指令的用户入口显示为一个文本实体。
方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > Action 调试模式
## 安全性
米家集成及其使用的云端接口由小米官方提供。您需要使用小米账号登录以获取设备列表。米家集成使用 OAuth 2.0 的登录方式,不会在 Home Assistant 中保存您的小米账号密码。但由于 Home Assistant 平台的限制,登录成功后,您的小米用户信息(包括设备信息、证书、 token 等)会明文保存在 Home Assistant 的配置文件中。因此,您需要保管好自己 Home Assistant 配置文件。一旦该文件泄露,其他人可能会冒用您的身份登录。
## 常见问题
- 米家集成是否支持所有的小米米家设备?
米家集成目前支持大部分米家设备品类,但仍有一小部分设备品类(蓝牙、红外及虚拟设备)并不支持。
- 米家集成是否可以同时使用多个小米账号?
是的,米家集成支持多个小米账号同时登录。另外,米家集成还支持不同账号的米家设备添加至同一个 Home Assistant 区域。
- 米家集成是否支持本地化控制?
米家集成支持通过[小米中枢网关](https://www.mi.com/shop/buy/detail?product_id=15755&cfrom=search)(固件版本 3.4.0_000 以上)或内置中枢网关(软件版本 0.8.0 以上)的米家设备实现本地化控制。如果没有小米中枢网关或其他带中枢网关功能的设备,那么所有控制指令都会通过小米云发送。支持 Home Assistant 本地化控制的小米中枢网关(含内置中枢网关)的固件尚未发布,固件升级计划请参阅 MIoT 团队的通知。
小米中枢网关仅在中国大陆可用,在其他地区不可用。
米家集成也能通过开启小米局域网控制功能实现部分本地化控制效果。小米局域网控制功能只能控制与 Home Assistant 处于同一局域网内的 IP 设备(使用 WiFi、网线连接路由器的设备无法控制蓝牙 Mesh、ZigBee 等协议接入的设备。该功能可能会引起一些异常,我们建议不要使用该功能。小米局域网控制功能开启方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > 更新局域网控制配置
小米局域网控制功能不受地区限制,在全球范围内均可用。如果 Home Assistant 所在的局域网内存在中枢网关,那么即便米家集成开启了小米局域网控制功能,该功能也不会生效。
- 米家集成在哪些地区可用?
米家集成所用的云服务接口已部署在中国大陆、欧洲、印度、俄罗斯、新加坡、美国共六个地区的机房。由于用户数据在不同地区的小米云上相互隔离,您需要在配置 Home Assistant 时选择用户所在地区,才能导入相应的米家设备。米家集成支持将不同地区的米家设备添加至同一个 Home Assistant 区域。
## 消息收发原理
### 云端控制
<div align=center>
<img src="./images/cloud_control_zh.jpg" width=300>
图 1云端控制架构
</div>
米家集成向小米云 MQTT Broker 订阅关注的设备消息。当设备属性发生改变或产生设备事件时,设备向小米云发送上行消息, MQTT Broker 向米家集成推送订阅的设备消息。由于米家集成不需要向云端轮询以获取设备当前的属性值,因此米家集成能第一时间获知设备属性变化或事件发生。得益于消息订阅机制,米家集成只在配置完成时向云端查询一次所有的设备属性,对云端产生的访问压力很小。
米家集成需要控制设备时,通过小米云 HTTP 接口向设备发送控制消息。设备收到小米云发来的下行消息后做出响应。
### 本地控制
<div align=center>
<img src="./images/local_control_zh.jpg" width=300>
图 2本地控制架构
</div>
小米中枢网关内包含一个标准的 MQTT Broker ,实现了完整的订阅发布机制。米家集成向小米中枢网关订阅关注的设备消息。当设备属性发生改变或产生设备事件时,设备向小米中枢网关发送上行消息, MQTT Broker 向米家集成推送订阅的设备消息。
米家集成需要控制设备时,向 MQTT Broker 发布设备控制消息,再经由小米中枢网关转发给设备。设备收到小米中枢网关发来的下行消息后做出响应。
## MIoT-Spec-V2 与 Home Assistant 实体的映射关系
[MIoT-Spec-V2](https://iot.mi.com/v2/new/doc/introduction/knowledge/spec) 的全称为 MIoT Specification Version 2 ,是小米 IoT 平台制订的物联网协议,用于对 IoT 设备进行规范化的功能性描述,其中包含功能定义(其他 IoT 平台称之为物模型)、交互模型、消息格式以及编码。
在 MIoT-Spec-V2 中,一个产品定义为一个设备,一个设备包含若干服务,一个服务包含若干属性、方法和事件。米家集成根据 MIoT-Spec-V2 生成对应的 Home Assistant 实体。
具体的转换关系如下:
### 一般转换规则
- 属性Property
| access访问方式 | format数据格式 | value-list取值列表 | value-range取值范围 | 转换后的实体 |
| ------------------ | ------------------ | ---------------------- | ----------------------- | ------------ |
| 可写 | string | - | - | Text |
| 可写 | bool | - | - | Switch |
| 可写 | 非 string、非 bool | 有 | - | Select |
| 可写 | 非 string、非 bool | 无 | 有 | Number |
| 不可写 | - | - | - | Sensor |
- 事件Event
转换后的实体为 Event事件参数同时传递给实体的 `_trigger_event`
- 方法Action
| in输入参数列表 | 转换后的实体 |
| ------------------ | ------------ |
| 空 | Button |
| 非空 | Notify |
如果开启了“Action 调试模式”,方法的 in 字段为非空时,还会生成 Text 实体。
Notify 实体详情页中 Attributes 会显示输入参数的格式。输入参数为有序的列表,英文中括号[]包括。字符串元素由英文双引号""包括。
例如, xiaomi.wifispeaker.s12 siid=5 aiid=5 方法( Intelligent Speaker Execute Text Directive )在 Notify 实体详情页显示的输入参数格式为 `[Text Content(str), Silent Execution(bool)]` ,使用的输入参数可以是 `["Hello", true]`
### 特殊转换规则
MIoT-Spec-V2 定义类型使用的 URN 格式为 `urn:<namespace>:<type>:<name>:<value>[:<vendor-product>:<version>]`,其中 `name` 是用于描述实例(设备、服务、属性、事件、方法)的有意义的单词或词组。米家集成先用实例名称( name )判断是否将 MIoT-Spec-V2 实例转换成特定的 Home Assistant 实体。对于不符合特殊转换规则的 MIoT-Spec-V2 实例,再使用一般转换规则进行转换。
`namespace` 用于描述实例所属的命名空间取值为“miot-spec-v2”表示小米定义的规范 取值为“bluetooth-spec”表示蓝牙联盟定义的规范其他则为厂商自定义的规范。当 `namespace` 不是“miot-spec-v2”时转换后的实体名称前会显示一个星号\*。
- 设备Device
转换规则为 `SPEC_DEVICE_TRANS_MAP`
```
{
'<device instance name>':{
'required':{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
}
}
},
'optional':{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
}
}
},
'entity': str
}
}
```
device instance name 下的 required 表示必须包含的服务, optional 表示可选的服务, entity 表示转换后的实体。 service instance name 下的 required 表示必须包含的属性、事件、方法optional 表示可选的属性、事件、方法。 required 、 properties 域下的 property instance name 的值表示属性的访问方式,匹配成功的条件是 property instance name 的值是相应 MIoT-Spec-V2 属性实例的访问方式的子集。
如果 MIoT-Spec-V2 设备实例缺少必选的服务、属性、事件、方法,则不会生成 Home Assistant 实体。
- 服务Service
转换规则为 `SPEC_SERVICE_TRANS_MAP`
```
{
'<service instance name>':{
'required':{
'properties': {
'<property instance name>': set<property access: str>
},
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'optional':{
'properties': set<property instance name: str>,
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'entity': str
}
}
```
service instance name 下的 required 表示必须包含的属性、事件、方法optional 表示可选的属性、事件、方法, entity 表示转换后的实体。 required 、 properties 域下的 property instance name 的值表示属性的访问方式,匹配成功的条件是 property instance name 的值是相应 MIoT-Spec-V2 属性实例的访问方式的子集。
如果 MIoT-Spec-V2 服务实例缺少必选的属性、事件、方法,则不会生成 Home Assistant 实体。
- 属性Property
转换规则为 `SPEC_PROP_TRANS_MAP`
```
{
'entities':{
'<entity name>':{
'format': set<str>,
'access': set<str>
}
},
'properties': {
'<property instance name>':{
'device_class': str,
'entity': str
}
}
}
```
entity name 下的 format 表示属性的数据格式,匹配上一个值即为匹配成功; access 表示属性的访问方式,匹配上所有值才算匹配成功。
property instance name 下的 entity 表示转换后的实体,取值为 entities 下的 entity name device_class 表示转换后实体所用的 `_attr_device_class`
- 事件Event
转换规则为 `SPEC_EVENT_TRANS_MAP`
```
{
'<event instance name>': str
}
```
event instance name 下的值表示转换后实体所用的 `_attr_device_class`
### MIoT-Spec-V2 过滤规则
`spec_filter.json` 用于过滤掉不需要的 MIoT-Spec-V2 实例,过滤掉的实例不会转换成 Home Assistant 实体。
`spec_filter.json`的格式如下:
```
{
"<MIoT-Spec-V2 device instance>":{
"services": list<service_iid: str>,
"properties": list<service_iid.property_iid: str>,
"events": list<service_iid.event_iid: str>,
"actions": list<service_iid.action_iid: str>,
}
}
```
`spec_filter.json` 的键值为 MIoT-Spec-V2 设备实例的 urn 不含版本号“version”字段。一个产品的不同版本的固件可能会关联不同版本的 MIoT-Spec-V2 设备实例。 MIoT 平台要求厂商定义产品的 MIoT-Spec-V2 时,高版本的 MIoT-Spec-V2 实例必须包含全部低版本的 MIoT-Spec-V2 实例。因此, `spec_filter.json` 的键值不需要指定设备实例的版本号。
设备实例下的 services 、 properties 、 events 、 actions 域的值表示需要过滤掉的服务、属性、事件、方法的实例号( iid ,即 instance id )。支持通配符匹配。
示例:
```
{
"urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1":{
"services": ["*"] # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2 device instance.
},
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
"services": ["3"], # Filter out the service whose iid=3.
"properties": ["4.*"] # Filter out all properties in the service whose iid=4.
"events": ["4.1"], # Filter out the iid=1 event in the iid=4 service.
"actions": ["4.1"] # Filter out the iid=1 action in the iid=4 service.
}
}
```
所有设备的设备信息服务( urn:miot-spec-v2:service:device-information:00007801 )均不会生成 Home Assistant 实体。
## 多语言支持
米家集成配置选项中可选择的集成使用的语言有简体中文、繁体中文、英文、西班牙语、俄语、法语、德语、日语这八种语言。目前,米家集成配置页面的简体中文和英文已经过人工校审,其他语言由机器翻译。如果您希望修改配置页面的词句,则需要修改 `custom_components/xiaomi_home/translations/` 目录下相应语言的 json 文件。
在显示 Home Assistant 实体名称时,米家集成会从小米云下载设备厂商为设备配置的多语言文件,该文件包含设备 MIoT-Spec-V2 实例的多语言翻译。 `multi_lang.json` 是本地维护的多语言配置字典,其优先级高于从云端获取的多语言文件,可用于补充或修改设备的多语言翻译。
`multi_lang.json` 的格式如下:
```
{
"<MIoT-Spec-V2 device instance>": {
"<language code>": {
"<instance code>": <translation: str>
}
}
}
```
`multi_lang.json` 的键值为 MIoT-Spec-V2 设备实例的 urn 不含版本号“version”字段
language code 为语言代码,取值为 zh-Hans、zh-Hant、en、es、ru、fr、de、ja (对应上述米家集成可选的八种语言)。
instance code 为 MIoT-Spec-V2 实例代码,格式如下:
```
service:<siid> # 服务
service:<siid>:property:<piid> # 属性
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的值
service:<siid>:event:<eiid> # 事件
service:<siid>:action:<aiid> # 方法
```
siid、piid、eiid、aiid、value 均为十进制三位整数。
示例:
```
{
"urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1": {
"zh-Hant": {
"service:002": "養生壺",
"service:002:property:001": "工作狀態",
"service:002:property:001:valuelist:000": "待機中",
"service:002:action:002": "停止烹飪",
"service:005:event:001": "烹飪完成"
}
}
}
```
## 文档
- [许可证](../LICENSE.md)
- 贡献指南: [English](./CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
- [更新日志](./CHANGELOG.md)
- 开发文档: https://developers.home-assistant.io/docs/creating_component_index
## 目录结构
- miot核心代码。
- miot/miot_client每添加一个用户需要增加一个 miot_client 实例。
- miot/miot_cloud云服务相关功能包括 OAuth 登录、 HTTP 接口功能(获取用户信息、发送设备控制指令等)。
- miot/miot_device设备实体包含设备信息以及属性、事件、方法的处理逻辑。
- miot/miot_mips消息总线用于订阅和发布消息。
- miot/miot_spec解析 MIoT-Spec-V2 。
- miot/miot_lan: 设备局域网控制,包括设备发现、设备控制等。
- miot/miot_mdns: 中枢网关服务局域网发现。
- miot/miot_network获取网络状态和网络信息。
- miot/miot_storage: 用于集成文件存储。
- miot/test测试脚本。
- config_flow配置流程。

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

5
hacs.json Normal file
View File

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

28
install.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
set -e
# Check the number of input parameters.
if [ $# -ne 1 ]; then
echo "usage: $0 [config_path]"
exit 1
fi
# Get the config path.
config_path=$1
# Check if config path exists.
if [ ! -d "$config_path" ]; then
echo "$config_path does not exist"
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"
# Copy the new version.
cp -r custom_components/xiaomi_home/ "$config_path/custom_components/"
# Done.
echo "Xiaomi Home installation is completed. Please restart Home Assistant."
exit 0