Full Code of XiaoMi/ha_xiaomi_home for AI

main f290ff17d89d cached
100 files
1.4 MB
371.2k tokens
1008 symbols
1 requests
Download .txt
Showing preview only (1,515K chars total). Download the full file or copy to clipboard to get everything.
Repository: XiaoMi/ha_xiaomi_home
Branch: main
Commit: f290ff17d89d
Files: 100
Total size: 1.4 MB

Directory structure:
gitextract_vsvd5d4t/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   └── config.yml
│   └── workflows/
│       ├── release.yaml
│       ├── test.yaml
│       └── validate.yaml
├── .gitignore
├── .pylintrc
├── CHANGELOG.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE.md
├── LegalNotice.md
├── README.md
├── custom_components/
│   └── xiaomi_home/
│       ├── __init__.py
│       ├── binary_sensor.py
│       ├── button.py
│       ├── climate.py
│       ├── config_flow.py
│       ├── cover.py
│       ├── device_tracker.py
│       ├── event.py
│       ├── fan.py
│       ├── humidifier.py
│       ├── light.py
│       ├── manifest.json
│       ├── media_player.py
│       ├── miot/
│       │   ├── common.py
│       │   ├── const.py
│       │   ├── i18n/
│       │   │   ├── de.json
│       │   │   ├── en.json
│       │   │   ├── es.json
│       │   │   ├── fr.json
│       │   │   ├── it.json
│       │   │   ├── ja.json
│       │   │   ├── nl.json
│       │   │   ├── pt-BR.json
│       │   │   ├── pt.json
│       │   │   ├── ru.json
│       │   │   ├── tr.json
│       │   │   ├── zh-Hans.json
│       │   │   └── zh-Hant.json
│       │   ├── lan/
│       │   │   └── profile_models.yaml
│       │   ├── miot_client.py
│       │   ├── miot_cloud.py
│       │   ├── miot_device.py
│       │   ├── miot_error.py
│       │   ├── miot_i18n.py
│       │   ├── miot_lan.py
│       │   ├── miot_mdns.py
│       │   ├── miot_mips.py
│       │   ├── miot_network.py
│       │   ├── miot_spec.py
│       │   ├── miot_storage.py
│       │   ├── resource/
│       │   │   └── oauth_redirect_page.html
│       │   ├── specs/
│       │   │   ├── bool_trans.yaml
│       │   │   ├── multi_lang.json
│       │   │   ├── spec_add.json
│       │   │   ├── spec_filter.yaml
│       │   │   ├── spec_modify.yaml
│       │   │   └── specv2entity.py
│       │   └── web_pages.py
│       ├── notify.py
│       ├── number.py
│       ├── select.py
│       ├── sensor.py
│       ├── switch.py
│       ├── text.py
│       ├── translations/
│       │   ├── de.json
│       │   ├── en.json
│       │   ├── es.json
│       │   ├── fr.json
│       │   ├── it.json
│       │   ├── ja.json
│       │   ├── nl.json
│       │   ├── pt-BR.json
│       │   ├── pt.json
│       │   ├── ru.json
│       │   ├── tr.json
│       │   ├── zh-Hans.json
│       │   └── zh-Hant.json
│       ├── vacuum.py
│       └── water_heater.py
├── doc/
│   ├── CONTRIBUTING_zh.md
│   └── README_zh.md
├── hacs.json
├── install.sh
├── test/
│   ├── .gitignore
│   ├── check_rule_format.py
│   ├── conftest.py
│   ├── pytest.ini
│   ├── test_cloud.py
│   ├── test_common.py
│   ├── test_lan.py
│   ├── test_mdns.py
│   ├── test_mips.py
│   ├── test_network.py
│   ├── test_spec.py
│   └── test_storage.py
└── tools/
    ├── common.py
    └── update_lan_rule.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
name: Bug Report / 报告问题
description: Create a report to help us improve. / 报告问题以帮助我们改进
body:
  - type: textarea
    attributes:
      label: Describe the Bug / 描述问题
      description: |
        > A clear and concise description of what the bug is. Please include the device model information (Like xiaomi.gateway.hub1 which can be found in Device info page).
        > 清晰且简明地描述问题。请注明设备 model 信息(例如 xiaomi.gateway.hub1,可在设备详情页查询)。
    validations:
      required: true

  - type: textarea
    attributes:
      label: How to Reproduce / 复现步骤
      description: |
        > If applicable, add screenshots to help explain your problem. You can attach images by clicking this area to highlight it and then dragging files in. Steps to reproduce the behavior:
        > 如有需要,可添加截图以帮助解释问题。点击此区域以高亮显示并拖动截图文件以上传。请详细描述复现步骤:
      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: input
    attributes:
      label: Reproduce Time / 问题复现的时间点
      description: |
        > Year-month-day, 24-hour time.
        > 年-月-日,24小时制。
      placeholder: "2025-01-01 17:00:00"
    validations:
      required: true

  - type: textarea
    attributes:
      label: Home Assistant Logs / 系统日志
      description: |
        > Please [set the log level](https://github.com/XiaoMi/ha_xiaomi_home/blob/main/CONTRIBUTING.md#reporting-bugs) to `debug` and try to reproduce the problem.
        > [Settings > System > Logs > DOWNLOAD FULL LOG](https://my.home-assistant.io/redirect/logs) > Filter `xiaomi_home`
        > If you are concerned about privacy, you can send the log to ha_xiaomi_home@xiaomi.com . The mail body should include the link to this issue.
        > 请将[日志级别设置](https://github.com/XiaoMi/ha_xiaomi_home/blob/main/doc/CONTRIBUTING_zh.md#%E6%88%91%E5%8F%AF%E4%BB%A5%E5%A6%82%E4%BD%95%E8%B4%A1%E7%8C%AE)为 `debug` 并尝试复现问题。
        > [设置 > 系统 > 日志 > 下载完整日志](https://my.home-assistant.io/redirect/logs) > 筛选 `xiaomi_home`
        > 如果您担心隐私问题,可将日志发送至 ha_xiaomi_home@xiaomi.com ,邮件正文附上此问题的链接。

  - type: input
    attributes:
      label: Log Timezone / 日志时区
      description: |
        > The [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) of the timestamp in the log.
        > 日志所用时间戳的[时区](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)。
      placeholder: "Asia/Shanghai"
    validations:
      required: true

  - 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.11.0"
    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: "13.0"
    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.1.0"
    validations:
      required: true

  - type: textarea
    attributes:
      label: Additional Context / 其他说明


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
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. / 请在这里提问和答疑


================================================
FILE: .github/workflows/release.yaml
================================================
name: Release

on:
  release:
    types: [published]

jobs:
  release-zip:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: ZIP Component Dir
        run: |
          cd ${{ github.workspace }}/custom_components/xiaomi_home
          zip -r xiaomi_home.zip ./

      - name: Upload zip to release
        uses: svenstaro/upload-release-action@v2
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: ${{ github.workspace }}/custom_components/xiaomi_home/xiaomi_home.zip
          asset_name: xiaomi_home.zip
          tag: ${{ github.ref }}
          overwrite: true


================================================
FILE: .github/workflows/test.yaml
================================================
name: Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  workflow_dispatch:

jobs:
  check-rule-format:
    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 pytest pytest-asyncio pytest-dependency zeroconf paho.mqtt psutil cryptography slugify

      - name: Check rule format with pytest
        run: |
          pytest -v -s -m github ./test/check_rule_format.py

      - name: Unit test with pytest
        run: |
          pytest -v -s -m github ./test/


================================================
FILE: .github/workflows/validate.yaml
================================================
name: Validate

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  workflow_dispatch:

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

  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: Static 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


================================================
FILE: .gitignore
================================================
__pycache__
.pytest_cache
.vscode
.idea
requirements.txt


================================================
FILE: .pylintrc
================================================
# 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


================================================
FILE: CHANGELOG.md
================================================
# CHANGELOG
## v0.4.7
### Added
- Add turkish language in multi_lang.json. [#1593](https://github.com/XiaoMi/ha_xiaomi_home/pull/1593)
### Changed
- Remove unused info getting from central hub gateway. [#1574](https://github.com/XiaoMi/ha_xiaomi_home/pull/1574)
- Remove xiaomi.router.rd03 from `UNSUPPORTED_MODELS` and add era.airp.cwb03, k0918.toothbrush.t700 into it. [#1567](https://github.com/XiaoMi/ha_xiaomi_home/pull/1567)
### Fixed
- Update the BLE mesh device online state from the central hub gateway. Partially fix the BLE mesh device online state. [#1579](https://github.com/XiaoMi/ha_xiaomi_home/pull/1579)
- Add unit for xiaomi.toothbrush.p001 brush-head-left-level property. [#1588](https://github.com/XiaoMi/ha_xiaomi_home/pull/1588)
- Fix the playing-state property's access field of xiaomi.wifispeaker.lx04, xiaomi.wifispeaker.lx06, xiaomi.wifispeaker.x08c and xiaomi.wifispeaker.l04m. [#1567](https://github.com/XiaoMi/ha_xiaomi_home/pull/1567)
- Fix the MIoT-Spec-V2 of xiaomi.airc.h09h00 humidity-range unit. [#1567](https://github.com/XiaoMi/ha_xiaomi_home/pull/1567)

## v0.4.6
### Added
- Add tv-box device as the media player entity. [#1562](https://github.com/XiaoMi/ha_xiaomi_home/pull/1562)
- Set play-control service's play-loop-mode property as the sound mode. [#1562](https://github.com/XiaoMi/ha_xiaomi_home/pull/1562)
### Changed
- Use constant value to indicate the cloud MQTT broker host domain. [#1530](https://github.com/XiaoMi/ha_xiaomi_home/pull/1530)
- Use constant value to indicate the timer delay of refreshing devices. [#1555](https://github.com/XiaoMi/ha_xiaomi_home/pull/1555)
- Set the playing-state property as the required property in the play-control service of the speaker device. [#1552](https://github.com/XiaoMi/ha_xiaomi_home/pull/1552)
- Set the playing-state property as the required property in the optional play-control service of the television. [#1562](https://github.com/XiaoMi/ha_xiaomi_home/pull/1562)
### Fixed
- Catch paho-mqtt subscribe error properly. [#1551](https://github.com/XiaoMi/ha_xiaomi_home/pull/1551)
- After the network resumes, keep retrying to fetch the device list until it succeeds. [#1555](https://github.com/XiaoMi/ha_xiaomi_home/pull/1555)
- Catch the http post error properly. [#1555](https://github.com/XiaoMi/ha_xiaomi_home/pull/1555)
- Fix the format and the access field of daikin.aircondition.k2 and daikin.airfresh.k33 string value properties. [#1561](https://github.com/XiaoMi/ha_xiaomi_home/pull/1561)

## v0.4.5
### Changed
- Ignore mdns REMOVED package. [#1296](https://github.com/XiaoMi/ha_xiaomi_home/pull/1296)
- Format value type first, then evaluate by expression, and set precision at last. [#1516](https://github.com/XiaoMi/ha_xiaomi_home/pull/1516)
### Fixed
- Fix xiaomi.derh.lite temperature precision. [#1505](https://github.com/XiaoMi/ha_xiaomi_home/pull/1505)
- Fix xiaomi.waterpuri.s1200g filter property unit, lxzn.valve.02 electricity property unit, xiaomi.aircondition.c24 power consumption device class, and cuco.plug.cp7pd power consumption and power value precision. [#1517](https://github.com/XiaoMi/ha_xiaomi_home/pull/1517)

## v0.4.4
### Added
- Add Turkish language support. [#1468](https://github.com/XiaoMi/ha_xiaomi_home/pull/1468)
### Fixed
- Stop MQTT internal loop immediately when main loop is closed. [#1465](https://github.com/XiaoMi/ha_xiaomi_home/pull/1465)
- Fix the float value precision. [#1485](https://github.com/XiaoMi/ha_xiaomi_home/pull/1485)
- Fix the climate entity swing mode setting. [#1486](https://github.com/XiaoMi/ha_xiaomi_home/pull/1486)
- Fix the MIoT-Spec-V2 of cykj.hood.jyj22 urn version 4, lumi.motion.bmgl01 siid=2 piid=2 value-list, ykcn.valve.cbcs power property value unit, ainice.sensor_occupy.3b people-number property and qdhkl.airc.a42 hvac mode. [#1496](https://github.com/XiaoMi/ha_xiaomi_home/pull/1496)

## v0.4.3
### Changed
- Remove `VacuumEntityFeature.BATTERY` from the vacuum entity. [#1433](https://github.com/XiaoMi/ha_xiaomi_home/pull/1433)
- Subscribe the proxy gateway child device up messages even though the device is offline. [#1393](https://github.com/XiaoMi/ha_xiaomi_home/pull/1393)
### Fixed
- Fix the integer value step. [#1388](https://github.com/XiaoMi/ha_xiaomi_home/pull/1388)
- Fix the contact-state property value format. [#1387](https://github.com/XiaoMi/ha_xiaomi_home/pull/1387)
- Fix the MIoT-Spec-V2 of xiaomi.airc.rr0r00 swing mode and hyd.airer.lyjpro current-position. [#1394](https://github.com/XiaoMi/ha_xiaomi_home/pull/1394)
- Fix roidmi.vacuum.v60 siid=2 aiid=3 out field format.
- Ignore unsupported properties of xiaomi.wifispeaker.l15a and 759413.aircondition.iez.
- Add an alongside button entity for xiaomi.wifispeaker.l05b play action.
- Add zhimi.fan.za1 fan mode description in zh_Hans.
- Fix the error reported by pylint-4.0.0. [#1455](https://github.com/XiaoMi/ha_xiaomi_home/pull/1455)

## v0.4.2
### Changed
- Set the battery service's start-charge action as the fallback action to support RETURN_HOME feature of the vacuum entity. [#1344](https://github.com/XiaoMi/ha_xiaomi_home/pull/1344)
### Fixed
- Correct the property value format after expression calculation. [#1366](https://github.com/XiaoMi/ha_xiaomi_home/pull/1366)
- Fix the MIoT-Spec-V2 of xiaomi.fan.p70 and xiaomi.fan.p76 fan level, xiaomi.airc.rr0r00 and xiaomi.airc.h43h00 humidity-range, and zhimi.humidifier.ca4 water level. [#1367](https://github.com/XiaoMi/ha_xiaomi_home/pull/1367)
- Ignore the unsupported model hmpace.motion.v6nfc.
- Delete all unsupported MIoT-Spec-V2 instances of narwa.vacuum.001 and narwa.vacuum.ax11. [#1355](https://github.com/XiaoMi/ha_xiaomi_home/pull/1355)

## v0.4.1
### Changed
- The setting option "Cover closed position" in CONFIGURE is changed to "Cover dead zone width". [#1301](https://github.com/XiaoMi/ha_xiaomi_home/pull/1301)
- Add an alongside switch entity for 090615.aircondition.ktf and juhl.aircondition.hvac. [#1303](https://github.com/XiaoMi/ha_xiaomi_home/pull/1303)
### Fixed
- Fix the vacuum status so that the vacuum activity will not always be idle. [#1299](https://github.com/XiaoMi/ha_xiaomi_home/pull/1299)
- Set the device on when the switch status is False or None. [#1303](https://github.com/XiaoMi/ha_xiaomi_home/pull/1303)
- Hide sensitive info in printing logs. [#1328](https://github.com/XiaoMi/ha_xiaomi_home/pull/1328)
- Fix the MIoT-Spec-V2 of cuco.plug.cp2d electric current, xiaomi.fan.p45 fan level, sanmei.valve.s1 power consumption, current and voltage, xiaomi.aircondition.c17, xiaomi.aircondition.m16 and xiaomi.airc.h40h00 humidity-range unit. [#1329](https://github.com/XiaoMi/ha_xiaomi_home/pull/1329)

## v0.4.0
### Added
- Add the watch as the device tracker entity. [#1189](https://github.com/XiaoMi/ha_xiaomi_home/pull/1189)
- Add the wifi speaker and the television as the media player entity. [#706](https://github.com/XiaoMi/ha_xiaomi_home/pull/706)
- Add an option in CONFIGURE to set the cover closed position. [#1242](https://github.com/XiaoMi/ha_xiaomi_home/pull/1242)
- Add notifications to show the status of the local connection to the central hub gateway. [#1280](https://github.com/XiaoMi/ha_xiaomi_home/pull/1280)
- Import the device from the third party cloud. [#1258](https://github.com/XiaoMi/ha_xiaomi_home/pull/1258)
### Changed
- Add an alongside switch entity for viomi.waterheater.m1. [#1255](https://github.com/XiaoMi/ha_xiaomi_home/pull/1255)
- Do not subscribe BLE device online/offline state message. [#1264](https://github.com/XiaoMi/ha_xiaomi_home/pull/1264)
### Fixed
- Keep the first element of the discovered ip address list as the recently added address when getting mdns result. [#1250](https://github.com/XiaoMi/ha_xiaomi_home/pull/1250)
- Subscribe local topics every time when connected to the central hub gateway. [#1266](https://github.com/XiaoMi/ha_xiaomi_home/pull/1266)
- Record the "closing" and "closed" status that occur frequently in the motor-controller, the window-opener and the curtain service. [#1262](https://github.com/XiaoMi/ha_xiaomi_home/pull/1262)
- Fix xiaomi.aircondition.c24 total power consumption unit, adp.motor.adswb4 motor switch, cgllc.airm.cgd1st environment temperature, and shhf.light.sflt11 fan switch status. [#1256](https://github.com/XiaoMi/ha_xiaomi_home/pull/1256)

## v0.3.4
### Added
- Exclude the unsupported device models. [#1205](https://github.com/XiaoMi/ha_xiaomi_home/pull/1205)
### Changed
- Subscribe the BLE device upstream messages even though the device is offline. [#1207](https://github.com/XiaoMi/ha_xiaomi_home/pull/1207)
- Record "opening", "closing" and "closed" status of the airer service that occur frequently and do not record "stop" status for the cover entity. [#1235](https://github.com/XiaoMi/ha_xiaomi_home/pull/1235)
- Modify README about spec_filter.yaml and the event attributes. [#1237](https://github.com/XiaoMi/ha_xiaomi_home/pull/1237)
### Fixed
- Fix the reconnect delay time to be reset when the client is connected to the broker. [#1200](https://github.com/XiaoMi/ha_xiaomi_home/pull/1200)
- Fix the HA warning in the logs related to vacuum state setting. [#694](https://github.com/XiaoMi/ha_xiaomi_home/pull/694)
- Fix the operation mode when the device does not have a mode property. [#1199](https://github.com/XiaoMi/ha_xiaomi_home/pull/1199)
- Fix 090615.aircondition.ktf environment temperature. [#1210](https://github.com/XiaoMi/ha_xiaomi_home/pull/1210)
- Fix a missing variable in translation it.json. [#1215](https://github.com/XiaoMi/ha_xiaomi_home/pull/1215)
- Fix yutai.plug.fsov8m power consumption and ignore bjkcz.curtain.kczble curtain status. [#1236](https://github.com/XiaoMi/ha_xiaomi_home/pull/1236)

## v0.3.3
### Changed
- Change the log level of error "mips unsub internal error, 4, None". [#1135](https://github.com/XiaoMi/ha_xiaomi_home/pull/1135)
- Add necessary logs for distinguishing the set_properties command source. [#1160](https://github.com/XiaoMi/ha_xiaomi_home/pull/1160)
### Fixed
- Fix tofan.airrtc.wk01 thermostat and air conditioner service. [#1160](https://github.com/XiaoMi/ha_xiaomi_home/pull/1160)
- Fix mrbond.airer.m1t closing status. [#1134](https://github.com/XiaoMi/ha_xiaomi_home/pull/1134)
- Fix the MIoT-Spec-V2 of xiaomi.fan.p69 fan service, ainice.sensor_occupy.3b people number, cykj.hood.jyj22 ventilation switch status, xiaomi.fan.p43 fan level, zhimi.airp.ua1a pm10 density, 090615.switch.x1tpm switch status, dmaker.fan.p33 fan-level. [#1132](https://github.com/XiaoMi/ha_xiaomi_home/pull/1132)
- Fix cubee.airrtc.th123e and cubee.airrtc.th123w MIoT-Spec-V2 instance descriptions in Russian.
- Fix ijai.vacuum.v1 suction-state value-list descriptions in Chinese.
- Fix the misuse of Chinese brackets in multi_lang.json.
- The unit of the humidity-range property of xiaomi.aircondition.mt0, xiaomi.aircondition.c35, xiaomi.aircondition.c24 and xiaomi.aircondition.c20 is "none". [#1187](https://github.com/XiaoMi/ha_xiaomi_home/pull/1187)

## v0.3.2
> Xiaomi Home has been added to the Home Assistant Community Store (HACS) as a default since May 8, 2025.
### Added
- Modify MIoT-Spec-V2 property format by spec_modify.yaml. [#1111](https://github.com/XiaoMi/ha_xiaomi_home/pull/1111)
### Changed
- Update the instructions of Xiaomi Home integration installation from HACS. [#102](https://github.com/XiaoMi/ha_xiaomi_home/pull/102) [#1088](https://github.com/XiaoMi/ha_xiaomi_home/pull/1088)
- Add an alongside switch entity for zimi.waterheater.h03 and xiaomi.waterheater.yms2. [#1115](https://github.com/XiaoMi/ha_xiaomi_home/pull/1115)
### Fixed
- Fix Chinese encoding in LAN Control. [#1114](https://github.com/XiaoMi/ha_xiaomi_home/pull/1114)
- Fix the MIoT-Spec-V2 of lxzn.switch.jcbcsm power consumption, voltage and current, shhf.light.sfla10 fan direction, zhimi.fan.za4 fan-level, zhimi.fan.sa1 fan-level. [#1110](https://github.com/XiaoMi/ha_xiaomi_home/pull/1110)
- Revise the Chinese descriptions of loock.lock.t2pv1 door state value-list. [#1110](https://github.com/XiaoMi/ha_xiaomi_home/pull/1110)

## v0.3.1
### Changed
- Setting the fan speed level when the fan is off will turning the fan on first. [#1031](https://github.com/XiaoMi/ha_xiaomi_home/pull/1031)
### Fixed
- Fix update device list error when there is no shared devices. [#1024](https://github.com/XiaoMi/ha_xiaomi_home/pull/1024)
- Fix the humidifier get_prop_value error when the property is None. [#1035](https://github.com/XiaoMi/ha_xiaomi_home/pull/1035)
- Fix the MIoT-Spec-V2 of zhimi.fan.v3 fan-level, cuco.plug.cp1md voltage and current, zimi.plug.zncz01 electric-power, giot.plug.v8icm power-consumption unit, yunmi.kettle.r3 tds unit, and dmaker.fan.p5 fan-level. [#1037](https://github.com/XiaoMi/ha_xiaomi_home/pull/1037)

## v0.3.0
注意:v0.3.0 变更了部分实体 unique_id 的生成规则,如果勾选 xiaomi_home > 配置 > 更新实体转换规则,会导致部分实体已配置的自动化失效。如果想要避免重新配置大量自动化,可使用这个[补丁](https://github.com/XiaoMi/ha_xiaomi_home/pull/972)。

CAUTION: v0.3.0 changes the unique_id of some entities. If you check the option `xiaomi_home > CONFIGURE > Update entity conversion rules`, it may cause the automation settings for these entities to fail. To avoid having to reconfigure a large number of automation settings, you can use this [patch](https://github.com/XiaoMi/ha_xiaomi_home/pull/972).
### Added
- Import the devices in the shared homes and the separated shared devices. [#1021](https://github.com/XiaoMi/ha_xiaomi_home/pull/1021)
- Support _attr_hvac_action of the climate entity. [#956](https://github.com/XiaoMi/ha_xiaomi_home/pull/956)
- Add custom defined MIoT-Spec-V2 instance via spec_add.json. [#953](https://github.com/XiaoMi/ha_xiaomi_home/pull/953)
### Fixed
- Ignore 'Event loop is closed' when unsub a closed event loop. [#991](https://github.com/XiaoMi/ha_xiaomi_home/pull/991)
- Fix contact-state for linp.magnet.m1 and loock.safe.v1. [#977](https://github.com/XiaoMi/ha_xiaomi_home/pull/977)
- Fix the mode initialization error of aupu.bhf_light.s368m. [#955](https://github.com/XiaoMi/ha_xiaomi_home/pull/955)
- Fix the MIoT-Spec-V2 of lumi.gateway.mcn001, qmi.plug.psv3, lumi.motion.acn001, izq.sensor_occupy.24, linp.sensor_occupy.hb01 and yunmi.waterpuri.s20. [#949](https://github.com/XiaoMi/ha_xiaomi_home/pull/949)

## v0.2.4
### Added
- Convert the submersion-state, the contact-state and the occupancy-status property to the binary_sensor entity. [#905](https://github.com/XiaoMi/ha_xiaomi_home/pull/905)
### Changed
- suittc.airrtc.wk168 mode descriptions are set to strings of numbers from 1 to 16. [#921](https://github.com/XiaoMi/ha_xiaomi_home/pull/921)
- Do not set _attr_suggested_display_precision when the spec.expr is set in spec_modify.yaml [#929](https://github.com/XiaoMi/ha_xiaomi_home/pull/929)
- Set "unknown event msg" log to info level.
### Fixed
- hhcc.plantmonitor.v1 soil moisture and soil ec icon and unit. [#927](https://github.com/XiaoMi/ha_xiaomi_home/pull/27)
- cuco.plug.cp2 voltage and power value ratio.
- cgllc.airmonitor.s1 unit ppb.
- roswan.waterpuri.lte01 tds unit.
- lumi.relay.c2acn01 power consumption unit
- xiaomi.bhf_light.s1 fan level of ventilation.

## v0.2.3
### Changed
- Specify the service name and the property name during the climate entity's on/off feature initialization. [#899](https://github.com/XiaoMi/ha_xiaomi_home/pull/899)
- Remove the useless total-battery property from `SPEC_PROP_TRANS_MAP`.
### Fixed
- Fix the hvac mode setting error when changing the preset mode of the ptc-bath-heater.
- Fix the ambiguous descriptions of yeelink.bhf_light.v10 ptc-bath-heater mode value-list.
- Fix the power consumption value of chuangmi.plug.212a01. [#910](https://github.com/XiaoMi/ha_xiaomi_home/pull/910)

## v0.2.2
This version has modified the conversion rules of the climate entity, which will have effect on the devices with the ptc-bath-heater, the air-conditioner and the air-fresh service. After updating, you need to restart Home Assistant and check `xiaomi_home > CONFIGURE >
Update entity conversion rules > NEXT` to reload the integration.

这个版本修改了浴霸、空调、新风机的实体转换规则,更新之后需要重启 Home Assistant,并且勾选 `xiaomi_home > 配置 > 更新实体转换规则 > 下一步` 重新加载集成。
### Added
- Add conversion rules for the air-conditioner service and the air-fresh service. [#879](https://github.com/XiaoMi/ha_xiaomi_home/pull/879)
### Changed
- Convert the mode of the ptc bath heater to the preset mode of the climate entity. [#874](https://github.com/XiaoMi/ha_xiaomi_home/pull/874)
- Use Home Assistant default icon when device_class is set. [#855](https://github.com/XiaoMi/ha_xiaomi_home/pull/855)
### Fixed
- Fix xiaomi.aircondition.m9 humidity-range unit. [#878](https://github.com/XiaoMi/ha_xiaomi_home/pull/878)
- Fix MIoT-Spec-V2 conflicts of xiaomi.fan.p5 and mike.bhf_light.2. [#866](https://github.com/XiaoMi/ha_xiaomi_home/pull/866)

## v0.2.1
### Added
- Add the preset mode for the thermostat. [#833](https://github.com/XiaoMi/ha_xiaomi_home/pull/833)
### Changed
- Change paho-mqtt version to adapt Home Assistant 2025.03. [#839](https://github.com/XiaoMi/ha_xiaomi_home/pull/839)
- Revert to use multi_lang.json. [#834](https://github.com/XiaoMi/ha_xiaomi_home/pull/834)
### Fixed
- Fix the opening and the closing status of linp.wopener.wd1lb. [#826](https://github.com/XiaoMi/ha_xiaomi_home/pull/826)
- Fix the format type of the wind-reverse property. [#810](https://github.com/XiaoMi/ha_xiaomi_home/pull/810)
- Fix the fan-level property without value-list but with value-range. [#808](https://github.com/XiaoMi/ha_xiaomi_home/pull/808)

## v0.2.0
This version has modified some default units of sensors. After updating, it may cause Home Assistant to pop up some compatibility warnings. You can re-add the integration to resolve it.

这个版本修改了一些传感器默认单位,更新后会导致 Home Assistant 弹出一些兼容性提示,您可以重新添加集成解决。

### Added
- Add prop trans rule for surge-power. [#595](https://github.com/XiaoMi/ha_xiaomi_home/pull/595)
- Support modify spec and value conversion. [#663](https://github.com/XiaoMi/ha_xiaomi_home/pull/663)
- Support the electric blanket. [#781](https://github.com/XiaoMi/ha_xiaomi_home/pull/781)
- Add device with motor-control service as cover entity. [#688](https://github.com/XiaoMi/ha_xiaomi_home/pull/688)
### Changed
- Update README file. [#681](https://github.com/XiaoMi/ha_xiaomi_home/pull/681) [#747](https://github.com/XiaoMi/ha_xiaomi_home/pull/747)
- Update CONTRIBUTING.md. [#681](https://github.com/XiaoMi/ha_xiaomi_home/pull/681)
- Refactor climate.py. [#614](https://github.com/XiaoMi/ha_xiaomi_home/pull/614)
### Fixed
- Fix variable name or comment errors & fix test_lan error. [#678](https://github.com/XiaoMi/ha_xiaomi_home/pull/678) [#690](https://github.com/XiaoMi/ha_xiaomi_home/pull/690)
- Fix water heater error & some type error. [#684](https://github.com/XiaoMi/ha_xiaomi_home/pull/684)
- Fix fan level with value-list & fan reverse [#689](https://github.com/XiaoMi/ha_xiaomi_home/pull/689)
- Fix sensor display precision [#708](https://github.com/XiaoMi/ha_xiaomi_home/pull/708)
- Fix event:motion-detected without arguments [#712](https://github.com/XiaoMi/ha_xiaomi_home/pull/712)

## v0.1.5b2
### Added
- Support binary sensors to be displayed as text sensor entities and binary sensor entities. [#592](https://github.com/XiaoMi/ha_xiaomi_home/pull/592)
- Add miot cloud test case. [#620](https://github.com/XiaoMi/ha_xiaomi_home/pull/620)
- Add test case for user cert. [#638](https://github.com/XiaoMi/ha_xiaomi_home/pull/638)
- Add mips test case & Change mips reconnect logic. [#641](https://github.com/XiaoMi/ha_xiaomi_home/pull/641)
- Support remove device. [#622](https://github.com/XiaoMi/ha_xiaomi_home/pull/622)
- Support italian translation. [#183](https://github.com/XiaoMi/ha_xiaomi_home/pull/183)
### Changed
- Refactor miot spec. [#592](https://github.com/XiaoMi/ha_xiaomi_home/pull/592)
- Refactor miot mips & fix type errors. [#365](https://github.com/XiaoMi/ha_xiaomi_home/pull/365)
- Using logging for test case log print. [#636](https://github.com/XiaoMi/ha_xiaomi_home/pull/636)
- Add power properties trans. [#571](https://github.com/XiaoMi/ha_xiaomi_home/pull/571)
- Move web page to html. [#627](https://github.com/XiaoMi/ha_xiaomi_home/pull/627)
### Fixed
- Fix miot cloud and mdns error. [#637](https://github.com/XiaoMi/ha_xiaomi_home/pull/637)
- Fix type error

## v0.1.5b1
This version will cause some Xiaomi routers that do not support access (#564) to become unavailable. You can update the device list in the configuration or delete it manually.
### Added
- Fan entity support direction ctrl [#556](https://github.com/XiaoMi/ha_xiaomi_home/pull/556)
### Changed
- Filter miwifi.* devices and xiaomi.router.rd03 [#564](https://github.com/XiaoMi/ha_xiaomi_home/pull/564)
### Fixed
- Fix multi ha instance login [#560](https://github.com/XiaoMi/ha_xiaomi_home/pull/560)
- Fix fan speed [#464](https://github.com/XiaoMi/ha_xiaomi_home/pull/464)
- The number of profile models updated from 660 to 823. [#583](https://github.com/XiaoMi/ha_xiaomi_home/pull/583)

## v0.1.5b0
### Added
- Add missing parameter state_class  [#101](https://github.com/XiaoMi/ha_xiaomi_home/pull/101)
### Changed
- Make git update guide more accurate [#561](https://github.com/XiaoMi/ha_xiaomi_home/pull/561)
### Fixed
- Limit *light.mode count (value-range) [#535](https://github.com/XiaoMi/ha_xiaomi_home/pull/535)
- Update miot cloud raise error msg [#551](https://github.com/XiaoMi/ha_xiaomi_home/pull/551)
- Fix table header misplacement [#554](https://github.com/XiaoMi/ha_xiaomi_home/pull/554)

## v0.1.4
### Added
- Refactor miot network, add network detection logic, improve devices filter logic. [458](https://github.com/XiaoMi/ha_xiaomi_home/pull/458) [#191](https://github.com/XiaoMi/ha_xiaomi_home/pull/191)
### Changed
- Remove tev dependency for lan control & fixs. [#333](https://github.com/XiaoMi/ha_xiaomi_home/pull/333)
- Use yaml to parse action params. [#447](https://github.com/XiaoMi/ha_xiaomi_home/pull/447)
- Update issue template. [#445](https://github.com/XiaoMi/ha_xiaomi_home/pull/445)
- Remove duplicate dependency(aiohttp) [#390](https://github.com/XiaoMi/ha_xiaomi_home/pull/390)
### Fixed

## v0.1.4b1
### Added
- Support devices filter, and device changed notify logical refinement. [#332](https://github.com/XiaoMi/ha_xiaomi_home/pull/332)
### Changed
- Readme amend HACS installation. [#404](https://github.com/XiaoMi/ha_xiaomi_home/pull/404)
### Fixed
- Fix unit_convert AttributeError, Change to catch all Exception. [#396](https://github.com/XiaoMi/ha_xiaomi_home/pull/396)
- Ignore undefined piid and keep processing following arguments. [#377](https://github.com/XiaoMi/ha_xiaomi_home/pull/377)
- Fix some type error, wrong use of any and Any. [#338](https://github.com/XiaoMi/ha_xiaomi_home/pull/338)
- Fix lumi.switch.acn040 identify service translation of zh-Hans [#412](https://github.com/XiaoMi/ha_xiaomi_home/pull/412)

## v0.1.4b0
### Added
### Changed
### Fixed
- Fix miot cloud token refresh logic. [#307](https://github.com/XiaoMi/ha_xiaomi_home/pull/307)
- Fix lan ctrl filter logic. [#303](https://github.com/XiaoMi/ha_xiaomi_home/pull/303)

## v0.1.3
### Added
### Changed
- Remove default bug label. [#276](https://github.com/XiaoMi/ha_xiaomi_home/pull/276)
- Improve multi-language translation actions. [#256](https://github.com/XiaoMi/ha_xiaomi_home/pull/256)
- Use aiohttp instead of waiting for blocking calls. [#227](https://github.com/XiaoMi/ha_xiaomi_home/pull/227)
- Language supports dt. [#237](https://github.com/XiaoMi/ha_xiaomi_home/pull/237)
### Fixed
- Fix local control error. [#271](https://github.com/XiaoMi/ha_xiaomi_home/pull/271)
- Fix README_zh and miot_storage. [#270](https://github.com/XiaoMi/ha_xiaomi_home/pull/270)

## v0.1.2
### Added
- Support Xiaomi Heater devices. https://github.com/XiaoMi/ha_xiaomi_home/issues/124 https://github.com/XiaoMi/ha_xiaomi_home/issues/117
- Language supports pt, pt-BR.
### Changed
- Adjust the minimum version of HASS core to 2024.4.4 and above versions.
### Fixed

## v0.1.1
### Added
### Changed
### Fixed
- Fix humidifier trans rule. https://github.com/XiaoMi/ha_xiaomi_home/issues/59
- Fix get homeinfo error.  https://github.com/XiaoMi/ha_xiaomi_home/issues/22
- Fix air-conditioner switch on. https://github.com/XiaoMi/ha_xiaomi_home/issues/37 https://github.com/XiaoMi/ha_xiaomi_home/issues/16
- Fix invalid cover status. https://github.com/XiaoMi/ha_xiaomi_home/issues/11  https://github.com/XiaoMi/ha_xiaomi_home/issues/85
- Water heater entity add STATE_OFF. https://github.com/XiaoMi/ha_xiaomi_home/issues/105 https://github.com/XiaoMi/ha_xiaomi_home/issues/17

## v0.1.0
### Added
- First version
### Changed
### Fixed


================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Xiaomi Home Integration is an official Home Assistant integration for controlling Xiaomi IoT smart devices. It connects to devices via Xiaomi Cloud (MQTT) or locally through Xiaomi Central Hub Gateway. The integration converts MIoT-Spec-V2 device specifications into Home Assistant entities.

## Development Commands

### Installation & Setup
```bash
# Install to Home Assistant config directory
./install.sh /path/to/config

# Install test dependencies
pip install pytest pytest-asyncio pytest-dependency zeroconf paho.mqtt psutil cryptography slugify
```

### Testing
```bash
# Run all tests
pytest -v -s -m github ./test/

# Run specific test files
pytest -v -s ./test/test_spec.py
pytest -v -s ./test/test_cloud.py
pytest -v -s ./test/test_lan.py

# Check rule format
pytest -v -s -m github ./test/check_rule_format.py
```

### Code Quality
```bash
# Run pylint (follows Google Python Style Guide)
pylint $(git ls-files '*.py')

# Lint specific files
pylint custom_components/xiaomi_home/*.py
```

### Validation
```bash
# HACS validation (run by GitHub Actions)
# Uses: hacs/action@main

# Hassfest validation (run by GitHub Actions)
# Uses: home-assistant/actions/hassfest@master
```

## Architecture Overview

### Core Components (miot/)

The integration is built around the `miot/` core package:

**miot_client.py**: Top-level client instance representing a logged-in Xiaomi user. Each user login creates one MIoTClient. Manages authentication, device list, and message routing.

**miot_cloud.py**: OAuth 2.0 authentication and HTTP API calls to Xiaomi Cloud. Handles token refresh, user info, device control commands, and spec downloads.

**miot_mips.py**: Message bus (MQTT) for subscribing to device property changes and events. Implements both cloud (MipsCloudClient) and local (MipsLocalClient) message handling.

**miot_device.py**: Device entity class. Each MIoT device creates multiple MIoTDevice instances (one per Home Assistant entity). Handles property updates, action execution, and event processing.

**miot_spec.py**: MIoT-Spec-V2 parser. Parses device specifications (URN-based type system) from cloud or local cache. Each spec defines services, properties, events, and actions.

**miot_lan.py**: Local LAN control for IP devices in same network. Discovery and control without cloud (optional).

**miot_mdns.py**: mDNS discovery for Xiaomi Central Hub Gateway services.

**miot_storage.py**: File storage for certificates, device specs, translations, and cached data.

**miot_network.py**: Network status monitoring and IP address detection.

**miot_i18n.py**: Multi-language support (13 languages). Manages translations for entity names.

### Entity Conversion (specs/specv2entity.py)

MIoT-Spec-V2 instances are converted to Home Assistant entities using three mapping dictionaries:

- **SPEC_DEVICE_TRANS_MAP**: Whole-device patterns (e.g., vacuum, humidifier, climate)
- **SPEC_SERVICE_TRANS_MAP**: Service-level patterns (e.g., battery, air-purifier)
- **SPEC_PROP_TRANS_MAP**: Property-level patterns (e.g., temperature, humidity)

Conversion priority: Device > Service > Property > General rules

### Spec Customization Files (miot/specs/)

**spec_filter.yaml**: Filters out MIoT-Spec-V2 instances that should NOT be converted to entities. Uses device URN keys and supports wildcard matching for service/property/event/action IIDs.

**spec_modify.yaml**: Modifies spec instances before conversion (e.g., changing value ranges, access modes).

**multi_lang.json**: Local translation overrides with higher priority than cloud translations. Keyed by device URN (without version).

**spec_add.json**: Additional spec definitions for devices not in cloud database.

**bool_trans.yaml**: Boolean value translation mappings.

After editing spec files, you MUST update conversion rules via Integration CONFIGURE page in Home Assistant.

### Platform Files (custom_components/xiaomi_home/)

Standard Home Assistant platform files (sensor.py, switch.py, climate.py, etc.) implement entity registration and state management. Each platform imports from miot_device.py and creates entity subclasses.

**config_flow.py**: Configuration flow for OAuth login and device selection.

**__init__.py**: Integration setup, entry management, and data structure initialization.

## MIoT-Spec-V2 Concepts

**URN Format**: `urn:<namespace>:<type>:<name>:<value>[:<vendor-product>:<version>]`
- namespace: miot-spec-v2 (Xiaomi), bluetooth-spec (SIG), or vendor-specific
- type: device, service, property, event, action
- name: human-readable identifier (used for mapping)

**IIDs (Instance IDs)**: Decimal identifiers
- siid: Service Instance ID
- piid: Property Instance ID
- eiid: Event Instance ID
- aiid: Action Instance ID

**Instance Code Format**:
```
service:<siid>                              # service
service:<siid>:property:<piid>              # property
service:<siid>:property:<piid>:valuelist:<index>  # value list item
service:<siid>:event:<eiid>                 # event
service:<siid>:action:<aiid>                # action
```

## Naming Conventions

From CONTRIBUTING.md:

- **Xiaomi**: Always "Xiaomi" in text. Variables: "xiaomi" or "mi"
- **Xiaomi Home**: Always "Xiaomi Home" in text. Variables: "mihome" or "MiHome"
- **Xiaomi IoT**: Always "MIoT" in text. Variables: "miot" or "MIoT"
- **Home Assistant**: Always "Home Assistant" in text. Variables: "hass" or "hass_xxx"

Mixed Chinese/English: Add space between Chinese and English or use Chinese quotation marks.

## Commit Message Format

```
<type>: <subject>

<body>

<footer>
```

Types: feat, fix, docs, style, refactor, perf, test, chore, revert

Subject: Imperative, present tense. Not capitalized. No period.

Body: Detailed description (mandatory except for docs type).

## Code Style

Follow [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html). Use the provided `.pylintrc` for linting.

## Debugging

Enable debug logging in Home Assistant configuration.yaml:
```yaml
logger:
  default: critical
  logs:
    custom_components.xiaomi_home: debug
```

## Control Modes

- **Cloud Control**: MQTT message subscription + HTTP command API
- **Local Control**: Via Xiaomi Central Hub Gateway (firmware 3.3.0_0023+) or LAN control (IP devices only, may be unstable)

Central gateway local control takes priority over LAN control when both are available.

## Multi-Region Support

Regions: China (cn), Europe (eu), India (in), Russia (ru), Singapore (sg), USA (us)

User data is isolated per region. Integration supports multiple regions in same Home Assistant instance.

## Important Files Location

- Integration source: `custom_components/xiaomi_home/`
- Spec mappings: `custom_components/xiaomi_home/miot/specs/specv2entity.py`
- Spec filters: `custom_components/xiaomi_home/miot/specs/spec_filter.yaml`
- Translations: `custom_components/xiaomi_home/translations/` and `custom_components/xiaomi_home/miot/i18n/`
- Tests: `test/`


================================================
FILE: CONTRIBUTING.md
================================================
# Contribution Guidelines

[English](./CONTRIBUTING.md) | [简体中文](./doc/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).


When you submit your first pull request, GitHub Action will prompt you to sign the Contributor License Agreement (CLA). Only after you sign the CLA, your pull request will be merged.

## 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


================================================
FILE: LICENSE.md
================================================
# 许可证

版权声明 (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.


================================================
FILE: LegalNotice.md
================================================
# 法律声明

版权声明 (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.


================================================
FILE: README.md
================================================
# 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.4.4
> - Operating System $\geq$ 13.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 fetch
git checkout v1.0.0
./install.sh /config
```

### Method 2: [HACS](https://hacs.xyz/)

One-click installation from HACS:

[![Open your Home Assistant instance and open the Xiaomi Home integration inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=XiaoMi&repository=ha_xiaomi_home&category=integration)

Or, HACS > In the search box, type **Xiaomi Home** > Click **Xiaomi Home**, getting into the detail page > DOWNLOAD

### 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 Xiaomi Home integration instance.](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 Xiaomi Home 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.

> If you suspect that your OAuth 2.0 token has been leaked, you can revoke the login authorization of your Xiaomi account by the following steps: Xiaomi Home APP -> Profile -> Click your username and get into Xiaomi Account management page -> Basic info: Apps -> Xiaomi Home (Home Assistant Integration) -> Remove

## FAQ

- Does Xiaomi Home Integration support all Xiaomi smart devices?

  Xiaomi Home Integration currently supports most categories of the smart 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. Furthermore, Xiaomi Home Integration allows that devices belonging to different accounts can be added to a same area.

- Does Xiaomi Home Integration support local mode?

  Local mode is implemented by [Xiaomi Central Hub Gateway](https://www.mi.com/shop/buy/detail?product_id=15755&cfrom=search) (firmware version 3.3.0_0023 and above) or Xiaomi smart devices with [built-in central hub gateway](https://github.com/XiaoMi/ha_xiaomi_home/wiki/Central-hub-gateway-device-models) (software version 0.8.9 and 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 mode feature has been released.

  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 mode 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 using 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

| access       | format                | 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`.

MIoT-Spec-V2 event's arguments field is the list of parameters of the event. The list elements represent the piid of the property in the same service. For example, the [MIoT-Spec-V2](http://poc.miot-spec.srv/miot-spec-v2/instance?type=urn:miot-spec-v2:device:remote-control:0000A021:xiaomi-mcn002:1:0000D057) of the Xiaomi Wireless Double-key Switch contains the siid=2 Switch Sensor service. The eiid=1014 Long Press event of the service is triggered when a button is long pressed. The debug level log will print `Press and hold, attributes: {'Button Type': 1}`. This is an example log that the button type is 1, which means the right button is long pressed.

- 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.yaml` is used to filter out the MIoT-Spec-V2 instance that will not be converted to Home Assistant entity.

The format of `spec_filter.yaml` is as follows.

```yaml
<MIoT-Spec-V2 device instance urn without the version field>:
    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.yaml` 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.yaml` 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:

```yaml
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.
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:
    services:
    - '3'   # Filter out the siid=3 service.
    properties:
    - '4.*' # Filter out all properties in the siid=4 service.
    events:
    - '4.1' # Filter out the eiid=1 event in the siid=4 service.
    actions:
    - '4.1' # Filter out the aiid=1 action in the siid=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 13 languages available for selection in the config flow language option of Xiaomi Home, including Simplified Chinese, Traditional Chinese, English, Spanish, Russian, French, German, Japanese, Italian, Dutch, Portuguese, Brazilian Portuguese, and Turkish. The config flow page in Simplified Chinese and English has been manually reviewed by the developer. Other languages are translated by machine translation or community contributions. If you want to modify the words and sentences in the config flow page, you need to modify the json file of the certain language in `custom_components/xiaomi_home/translations/` and `custom_components/xiaomi_home/miot/i18n/` directory.

When displaying Home Assistant entity name, Xiaomi Home downloads the multiple language file configured by the device vendor from MIoT Cloud, which contains translations for MIoT-Spec-V2 instances of the device. `multi_lang.json` is a locally maintained multiple language dictionary, which has a higher priority than the multiple language file obtained from the cloud and can be used to supplement or modify the multiple language translation of devices.

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, ja, it, nl, pt, pt-BR, or tr, corresponding to the 13 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:<index> # The index of a value in the 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": "烹飪完成"
        }
    }
}
```

> If you edit any files in the `custom_components/xiaomi_home/miot/specs` directory (`spec_filter.yaml`, `spec_modify.yaml`, `multi_lang.json`, etc.) in your Home Assistant, you need to update the entity conversion rule in the integration's CONFIGURE page to take effect. Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Update entity conversion rules

## Documents

- [License](./LICENSE.md)
- Contribution Guidelines: [English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
- [ChangeLog](./CHANGELOG.md)
- Development Documents: https://developers.home-assistant.io/docs/creating_component_index
- [FAQ](https://github.com/XiaoMi/ha_xiaomi_home/wiki)

## 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: File storage for the integration.
- miot/test: Test scripts.
- config_flow: Config flow.


================================================
FILE: custom_components/xiaomi_home/__init__.py
================================================
# -*- 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.common import slugify_did
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:
    # pylint: disable=unused-argument
    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 or '',
                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 = await spec_parser.parse(urn=info['urn'])
            if not isinstance(spec_instance, MIoTSpecInstance):
                _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,
                            description=entity.spec.description)
                        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 not miot_client.action_debug:
                # 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)
            # Binary sensor display
            if not miot_client.display_binary_bool:
                for prop in device.prop_list.get('binary_sensor', []):
                    entity_id = device.gen_prop_entity_id(
                        ha_domain='binary_sensor', 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 not miot_client.display_binary_text:
                for prop in device.prop_list.get('binary_sensor', []):
                    entity_id = device.gen_prop_entity_id(
                        ha_domain='sensor', 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)

        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,
                        slugify_did(
                            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


async def async_remove_config_entry_device(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    device_entry: device_registry.DeviceEntry
) -> bool:
    """Remove the device."""
    miot_client: MIoTClient = await get_miot_instance_async(
        hass=hass, entry_id=config_entry.entry_id)

    if len(device_entry.identifiers) != 1:
        _LOGGER.error(
            'remove device failed, invalid identifiers, %s, %s',
            device_entry.id, device_entry.identifiers)
        return False
    identifiers = list(device_entry.identifiers)[0]
    if identifiers[0] != DOMAIN:
        _LOGGER.error(
            'remove device failed, invalid domain, %s, %s',
            device_entry.id, device_entry.identifiers)
        return False

    # Remove device
    await miot_client.remove_device2_async(did_tag=identifiers[1])
    device_registry.async_get(hass).async_remove_device(device_entry.id)
    _LOGGER.info(
        'remove device, %s, %s', identifiers[1], device_entry.id)
    return True


================================================
FILE: custom_components/xiaomi_home/binary_sensor.py
================================================
# -*- 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:
        if miot_device.miot_client.display_binary_bool:
            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."""
        if self.spec.name == 'contact-state':
            return bool(self._value) is False
        elif self.spec.name == 'occupancy-status':
            return bool(self._value)
        return self._value is True


================================================
FILE: custom_components/xiaomi_home/button.py
================================================
# -*- 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()


================================================
FILE: custom_components/xiaomi_home/climate.py
================================================
# -*- 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 Any, Optional

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.const import UnitOfTemperature
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.climate import (
    FAN_ON, FAN_OFF, SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL,
    ATTR_TEMPERATURE, HVACMode, HVACAction, 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('air-conditioner', []):
            new_entities.append(
                AirConditioner(miot_device=miot_device, entity_data=data))
        for data in miot_device.entity_list.get('heater', []):
            new_entities.append(
                Heater(miot_device=miot_device, entity_data=data))
        for data in miot_device.entity_list.get('bath-heater', []):
            new_entities.append(
                PtcBathHeater(miot_device=miot_device, entity_data=data))
        for data in miot_device.entity_list.get('thermostat', []):
            new_entities.append(
                Thermostat(miot_device=miot_device, entity_data=data))
        for data in miot_device.entity_list.get('electric-blanket', []):
            new_entities.append(
                ElectricBlanket(miot_device=miot_device, entity_data=data))

    if new_entities:
        async_add_entities(new_entities)


class FeatureOnOff(MIoTServiceEntity, ClimateEntity):
    """TURN_ON and TURN_OFF feature of the climate entity."""
    _prop_on: Optional[MIoTSpecProperty]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the feature class."""
        self._prop_on = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)

    def _init_on_off(self, service_name: str, prop_name: str) -> None:
        """Initialize the on_off feature."""
        for prop in self.entity_data.props:
            if prop.name == prop_name and prop.service.name == service_name:
                if prop.format_ != bool:
                    _LOGGER.error('wrong format %s %s, %s', service_name,
                                  prop_name, self.entity_id)
                    continue
                self._attr_supported_features |= ClimateEntityFeature.TURN_ON
                self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
                self._prop_on = prop
                break

    async def async_turn_on(self) -> None:
        """Turn on."""
        await self.set_property_async(prop=self._prop_on, value=True)

    async def async_turn_off(self) -> None:
        """Turn off."""
        await self.set_property_async(prop=self._prop_on, value=False)


class FeatureTargetTemperature(MIoTServiceEntity, ClimateEntity):
    """TARGET_TEMPERATURE feature of the climate entity."""
    _prop_target_temp: Optional[MIoTSpecProperty]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the feature class."""
        self._prop_target_temp = None
        self._attr_temperature_unit = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)
        # properties
        for prop in entity_data.props:
            if prop.name == 'target-temperature':
                if not prop.value_range:
                    _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
                break
        # temperature_unit is required by the climate entity
        if not self._attr_temperature_unit:
            self._attr_temperature_unit = UnitOfTemperature.CELSIUS

    async def async_set_temperature(self, **kwargs):
        """Set the target temperature."""
        if ATTR_TEMPERATURE in kwargs:
            temp = kwargs[ATTR_TEMPERATURE]
            if temp > self._attr_max_temp:
                temp = self._attr_max_temp
            elif temp < self._attr_min_temp:
                temp = self._attr_min_temp

            await self.set_property_async(prop=self._prop_target_temp,
                                          value=temp)

    @property
    def target_temperature(self) -> Optional[float]:
        """The current target temperature."""
        return (self.get_prop_value(
            prop=self._prop_target_temp) if self._prop_target_temp else None)


class FeaturePresetMode(MIoTServiceEntity, ClimateEntity):
    """PRESET_MODE feature of the climate entity."""
    _prop_mode: Optional[MIoTSpecProperty]
    _mode_map: Optional[dict[int, str]]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the feature class."""
        self._prop_mode = None
        self._mode_map = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)

    def _init_preset_modes(self, service_name: str, prop_name: str) -> None:
        """Initialize the preset modes."""
        for prop in self.entity_data.props:
            if prop.name == prop_name and prop.service.name == service_name:
                if not prop.value_list:
                    _LOGGER.error('invalid %s %s value_list, %s', service_name,
                                  prop_name, self.entity_id)
                    continue
                self._mode_map = prop.value_list.to_map()
                self._attr_preset_modes = prop.value_list.descriptions
                self._attr_supported_features |= (
                    ClimateEntityFeature.PRESET_MODE)
                self._prop_mode = prop
                break

    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_map_key(
                                          map_=self._mode_map,
                                          value=preset_mode))

    @property
    def preset_mode(self) -> Optional[str]:
        """The current preset mode."""
        return (self.get_map_value(
            map_=self._mode_map, key=self.get_prop_value(
                prop=self._prop_mode)) if self._prop_mode else None)


class FeatureFanMode(MIoTServiceEntity, ClimateEntity):
    """FAN_MODE feature of the climate entity."""
    _prop_fan_on: Optional[MIoTSpecProperty]
    _prop_fan_level: Optional[MIoTSpecProperty]
    _fan_mode_map: Optional[dict[int, str]]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the feature class."""
        self._prop_fan_on = None
        self._prop_fan_level = None
        self._fan_mode_map = None
        self._attr_fan_modes = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)
        # properties
        for prop in entity_data.props:
            if (prop.name == 'fan-level' and
                (prop.service.name == 'fan-control' or
                 prop.service.name == 'thermostat')):
                if not prop.value_list:
                    _LOGGER.error('invalid fan-level value_list, %s',
                                  self.entity_id)
                    continue
                self._fan_mode_map = prop.value_list.to_map()
                self._attr_fan_modes = prop.value_list.descriptions
                self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
                self._prop_fan_level = prop
            elif prop.name == 'on' and prop.service.name == 'fan-control':
                self._prop_fan_on = prop
                self._attr_supported_features |= ClimateEntityFeature.FAN_MODE

        if self._prop_fan_on:
            if self._attr_fan_modes is None:
                self._attr_fan_modes = [FAN_ON, FAN_OFF]
            else:
                self._attr_fan_modes.append(FAN_OFF)

    async def async_set_fan_mode(self, fan_mode):
        """Set the target fan mode."""
        if fan_mode == FAN_OFF:
            await self.set_property_async(prop=self._prop_fan_on, value=False)
            return
        if fan_mode == FAN_ON:
            await self.set_property_async(prop=self._prop_fan_on, value=True)
            return
        mode_value = self.get_map_key(map_=self._fan_mode_map, value=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 fan_mode(self) -> Optional[str]:
        """The current fan mode."""
        if self._prop_fan_level is None and self._prop_fan_on is None:
            return None
        if self._prop_fan_level is None and self._prop_fan_on:
            return (FAN_ON if self.get_prop_value(
                prop=self._prop_fan_on) else FAN_OFF)
        return self.get_map_value(
            map_=self._fan_mode_map,
            key=self.get_prop_value(prop=self._prop_fan_level))


class FeatureSwingMode(MIoTServiceEntity, ClimateEntity):
    """SWING_MODE feature of the climate entity."""
    _prop_horizontal_swing: Optional[MIoTSpecProperty]
    _prop_vertical_swing: Optional[MIoTSpecProperty]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the feature class."""
        self._prop_horizontal_swing = None
        self._prop_vertical_swing = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)
        # properties
        swing_modes = []
        for prop in entity_data.props:
            if prop.name == 'horizontal-swing':
                swing_modes.append(SWING_HORIZONTAL)
                self._prop_horizontal_swing = prop
            elif prop.name == 'vertical-swing':
                swing_modes.append(SWING_VERTICAL)
                self._prop_vertical_swing = prop
        # swing modes
        if SWING_HORIZONTAL in swing_modes and SWING_VERTICAL in swing_modes:
            swing_modes.append(SWING_BOTH)
        if swing_modes:
            swing_modes.insert(0, SWING_OFF)
            self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
            self._attr_swing_modes = swing_modes

    async def async_set_swing_mode(self, swing_mode):
        """Set the target swing operation."""
        if swing_mode == SWING_BOTH:
            await self.set_property_async(prop=self._prop_horizontal_swing,
                                          value=True)
            await self.set_property_async(prop=self._prop_vertical_swing,
                                          value=True)
        elif swing_mode == SWING_HORIZONTAL:
            if self._prop_vertical_swing:
                await self.set_property_async(prop=self._prop_vertical_swing,
                                              value=False)
            await self.set_property_async(prop=self._prop_horizontal_swing,
                                          value=True)
        elif swing_mode == SWING_VERTICAL:
            if self._prop_horizontal_swing:
                await self.set_property_async(prop=self._prop_horizontal_swing,
                                              value=False)
            await self.set_property_async(prop=self._prop_vertical_swing,
                                          value=True)
        elif swing_mode == SWING_OFF:
            if self._prop_horizontal_swing:
                await self.set_property_async(prop=self._prop_horizontal_swing,
                                              value=False)
            if self._prop_vertical_swing:
                await self.set_property_async(prop=self._prop_vertical_swing,
                                              value=False)
        else:
            raise RuntimeError(
                f'unknown swing_mode, {swing_mode}, {self.entity_id}')

    @property
    def swing_mode(self) -> Optional[str]:
        """The current swing mode of the fan."""
        if (self._prop_horizontal_swing is None and
                self._prop_vertical_swing is None):
            return None
        horizontal: bool = (self.get_prop_value(
            prop=self._prop_horizontal_swing)
                            if self._prop_horizontal_swing else False)
        vertical: bool = (self.get_prop_value(prop=self._prop_vertical_swing)
                          if self._prop_vertical_swing else False)
        if horizontal and vertical:
            return SWING_BOTH
        elif horizontal:
            return SWING_HORIZONTAL
        elif vertical:
            return SWING_VERTICAL
        else:
            return SWING_OFF


class FeatureTemperature(MIoTServiceEntity, ClimateEntity):
    """Temperature of the climate entity."""
    _prop_env_temperature: Optional[MIoTSpecProperty]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the feature class."""
        self._prop_env_temperature = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)
        # properties
        for prop in entity_data.props:
            if prop.name == 'temperature':
                self._prop_env_temperature = prop
                break

    @property
    def current_temperature(self) -> Optional[float]:
        """The current environment temperature."""
        return (self.get_prop_value(prop=self._prop_env_temperature)
                if self._prop_env_temperature else None)


class FeatureHumidity(MIoTServiceEntity, ClimateEntity):
    """Humidity of the climate entity."""
    _prop_env_humidity: Optional[MIoTSpecProperty]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the feature class."""
        self._prop_env_humidity = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)
        # properties
        for prop in entity_data.props:
            if prop.name == 'relative-humidity':
                self._prop_env_humidity = prop
                break

    @property
    def current_humidity(self) -> Optional[float]:
        """The current environment humidity."""
        return (self.get_prop_value(
            prop=self._prop_env_humidity) if self._prop_env_humidity else None)


class FeatureTargetHumidity(MIoTServiceEntity, ClimateEntity):
    """TARGET_HUMIDITY feature of the climate entity."""
    _prop_target_humidity: Optional[MIoTSpecProperty]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the feature class."""
        self._prop_target_humidity = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)
        # properties
        for prop in entity_data.props:
            if prop.name == 'target-humidity':
                if not prop.value_range:
                    _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_humidity = prop
                break

    async def async_set_humidity(self, humidity):
        """Set the target humidity."""
        if humidity > self._attr_max_humidity:
            humidity = self._attr_max_humidity
        elif humidity < self._attr_min_humidity:
            humidity = self._attr_min_humidity
        await self.set_property_async(prop=self._prop_target_humidity,
                                      value=humidity)

    @property
    def target_humidity(self) -> Optional[int]:
        """The current target humidity."""
        return (self.get_prop_value(prop=self._prop_target_humidity)
                if self._prop_target_humidity else None)


class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
             FeatureHumidity, FeaturePresetMode):
    """Heater"""

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the heater."""
        super().__init__(miot_device=miot_device, entity_data=entity_data)

        self._attr_icon = 'mdi:radiator'
        # hvac modes
        self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
        # on/off
        self._init_on_off('heater', 'on')
        # preset modes
        self._init_preset_modes('heater', 'heat-level')

    async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
        """Set the target hvac mode."""
        await self.set_property_async(
            prop=self._prop_on,
            value=False if hvac_mode == HVACMode.OFF else True)

    @property
    def hvac_mode(self) -> Optional[HVACMode]:
        """The current hvac mode."""
        return (HVACMode.HEAT if self.get_prop_value(
            prop=self._prop_on) else HVACMode.OFF)

    @property
    def hvac_action(self) -> Optional[HVACAction]:
        """The current hvac action."""
        if self.hvac_mode == HVACMode.HEAT:
            return HVACAction.HEATING
        return HVACAction.OFF


class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
                     FeatureTargetHumidity, FeatureTemperature, FeatureHumidity,
                     FeatureFanMode, FeatureSwingMode):
    """Air conditioner"""
    _prop_mode: Optional[MIoTSpecProperty]
    _hvac_mode_map: Optional[dict[int, HVACMode]]
    _prop_ac_state: Optional[MIoTSpecProperty]
    _value_ac_state: Optional[dict[str, int]]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the air conditioner."""
        self._prop_mode = None
        self._hvac_mode_map = None
        self._prop_ac_state = None
        self._value_ac_state = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)
        self._attr_icon = 'mdi:air-conditioner'
        # on/off
        self._init_on_off('air-conditioner', 'on')
        # hvac modes
        self._attr_hvac_modes = None
        for prop in entity_data.props:
            if prop.name == 'mode' and prop.service.name == 'air-conditioner':
                if 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.items:
                    if item.name in {'off', 'idle'}:
                        self._hvac_mode_map[item.value] = HVACMode.OFF
                    elif item.name in {'auto'}:
                        self._hvac_mode_map[item.value] = HVACMode.AUTO
                    elif item.name in {'cool'}:
                        self._hvac_mode_map[item.value] = HVACMode.COOL
                    elif item.name in {'heat'}:
                        self._hvac_mode_map[item.value] = HVACMode.HEAT
                    elif item.name in {'dry'}:
                        self._hvac_mode_map[item.value] = HVACMode.DRY
                    elif item.name in {'fan'}:
                        self._hvac_mode_map[item.value] = HVACMode.FAN_ONLY
                    elif item.name in {'heat_cool'}:
                        self._hvac_mode_map[item.value] = HVACMode.HEAT_COOL
                self._attr_hvac_modes = list(self._hvac_mode_map.values())
                self._prop_mode = 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)

        if self._attr_hvac_modes is None:
            self._attr_hvac_modes = [HVACMode.OFF]
        elif HVACMode.OFF not in self._attr_hvac_modes:
            self._attr_hvac_modes.append(HVACMode.OFF)

    async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
        """Set the target hvac mode."""
        # set the device off
        if hvac_mode == HVACMode.OFF:
            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
        # set the device on
        if self.get_prop_value(prop=self._prop_on) is not True:
            await self.set_property_async(prop=self._prop_on,
                                          value=True,
                                          write_ha_state=False)
        # set mode
        if self._prop_mode is None:
            return
        mode_value = self.get_map_key(map_=self._hvac_mode_map, value=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}')

    @property
    def hvac_mode(self) -> Optional[HVACMode]:
        """The current hvac mode."""
        if self.get_prop_value(prop=self._prop_on) is False:
            return HVACMode.OFF
        return (self.get_map_value(map_=self._hvac_mode_map,
                                   key=self.get_prop_value(
                                       prop=self._prop_mode))
                if self._prop_mode else None)

    @property
    def hvac_action(self) -> Optional[HVACAction]:
        """The current hvac action."""
        if self.hvac_mode is None:
            return None
        if self.hvac_mode == HVACMode.OFF:
            return HVACAction.OFF
        if self.hvac_mode == HVACMode.FAN_ONLY:
            return HVACAction.FAN
        if self.hvac_mode == HVACMode.COOL:
            return HVACAction.COOLING
        if self.hvac_mode == HVACMode.HEAT:
            return HVACAction.HEATING
        if self.hvac_mode == HVACMode.DRY:
            return HVACAction.DRYING
        return HVACAction.IDLE

    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_key(
                                        map_=self._hvac_mode_map, value=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 self._attr_swing_modes 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)


class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
                    FeatureFanMode, FeatureSwingMode, FeaturePresetMode):
    """Ptc bath heater"""
    _prop_mode: Optional[MIoTSpecProperty]
    _hvac_mode_map: Optional[dict[int, HVACMode]]

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the ptc bath heater."""
        self._prop_mode = None
        self._hvac_mode_map = None

        super().__init__(miot_device=miot_device, entity_data=entity_data)
        self._attr_icon = 'mdi:hvac'
        # hvac modes
        for prop in entity_data.props:
            if prop.name == 'mode' and prop.service.name == 'ptc-bath-heater':
                if 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.items:
                    if item.name in {'off', 'idle'}:
                        self._hvac_mode_map[item.value] = HVACMode.OFF
                        break
                if self._hvac_mode_map:
                    self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
                else:
                    _LOGGER.error('no idle mode, %s', self.entity_id)
        # preset modes
        self._init_preset_modes('ptc-bath-heater', 'mode')

    async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
        """Set the target hvac mode."""
        if self._prop_mode is None or hvac_mode != HVACMode.OFF:
            return
        mode_value = self.get_map_key(map_=self._hvac_mode_map, value=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 ptc-bath-heater {hvac_mode} failed, {self.entity_id}')

    @property
    def hvac_mode(self) -> Optional[HVACMode]:
        """The current hvac mode."""
        if self._prop_mode is None:
            return None
        current_mode = self.get_prop_value(prop=self._prop_mode)
        if current_mode is None:
            return None
        mode_value = self.get_map_value(map_=self._hvac_mode_map,
                                        key=current_mode)
        return HVACMode.OFF if mode_value == HVACMode.OFF else HVACMode.AUTO


class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
                 FeatureHumidity, FeatureFanMode, FeaturePresetMode):
    """Thermostat"""

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the thermostat."""
        super().__init__(miot_device=miot_device, entity_data=entity_data)

        self._attr_icon = 'mdi:thermostat'
        # hvac modes
        self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
        # on/off
        self._init_on_off('thermostat', 'on')
        # preset modes
        self._init_preset_modes('thermostat', 'mode')

    async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
        """Set the target hvac mode."""
        await self.set_property_async(
            prop=self._prop_on,
            value=False if hvac_mode == HVACMode.OFF else True)

    @property
    def hvac_mode(self) -> Optional[HVACMode]:
        """The current hvac mode."""
        return (HVACMode.AUTO if self.get_prop_value(
            prop=self._prop_on) else HVACMode.OFF)


class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
                      FeatureTemperature, FeaturePresetMode):
    """Electric blanket"""

    def __init__(self, miot_device: MIoTDevice,
                 entity_data: MIoTEntityData) -> None:
        """Initialize the electric blanket."""
        super().__init__(miot_device=miot_device, entity_data=entity_data)

        self._attr_icon = 'mdi:rug'
        # hvac modes
        self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
        # on/off
        self._init_on_off('electric-blanket', 'on')
        # preset modes
        self._init_preset_modes('electric-blanket', 'mode')

    async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
        """Set the target hvac mode."""
        await self.set_property_async(
            prop=self._prop_on,
            value=False if hvac_mode == HVACMode.OFF else True)

    @property
    def hvac_mode(self) -> Optional[HVACMode]:
        """The current hvac mode."""
        return (HVACMode.HEAT if self.get_prop_value(
            prop=self._prop_on) else HVACMode.OFF)

    @property
    def hvac_action(self) -> Optional[HVACAction]:
        """The current hvac action."""
        if self.hvac_mode == HVACMode.OFF:
            return HVACAction.OFF
        return HVACAction.HEATING


================================================
FILE: custom_components/xiaomi_home/config_flow.py
================================================
# -*- 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.

Config flow for Xiaomi Home.
"""
import asyncio
import hashlib
import ipaddress
import json
import secrets
import traceback
from typing import Optional, Set, Tuple
from urllib.parse import urlparse
from aiohttp import web
from aiohttp.hdrs import METH_GET
import voluptuous as vol
import logging

from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.components.zeroconf import HaAsyncZeroconf
from homeassistant.components.webhook import (
    async_register as webhook_async_register,
    async_unregister as webhook_async_unregister,
    async_generate_path as webhook_async_generate_path
)
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.instance_id import async_get
import homeassistant.helpers.config_validation as cv

from .miot.const import (
    DEFAULT_CLOUD_SERVER,
    DEFAULT_CTRL_MODE,
    DEFAULT_INTEGRATION_LANGUAGE,
    DEFAULT_COVER_DEAD_ZONE_WIDTH,
    MIN_COVER_DEAD_ZONE_WIDTH,
    MAX_COVER_DEAD_ZONE_WIDTH,
    DEFAULT_NICK_NAME,
    DEFAULT_OAUTH2_API_HOST,
    DEFAULT_CLOUD_BROKER_HOST,
    DOMAIN,
    OAUTH2_AUTH_URL,
    OAUTH2_CLIENT_ID,
    CLOUD_SERVERS,
    OAUTH_REDIRECT_URL,
    INTEGRATION_LANGUAGES,
    SUPPORT_CENTRAL_GATEWAY_CTRL,
    NETWORK_REFRESH_INTERVAL,
    MIHOME_CERT_EXPIRE_MARGIN
)
from .miot.miot_cloud import MIoTHttpClient, MIoTOauthClient
from .miot.miot_storage import MIoTStorage, MIoTCert
from .miot.miot_mdns import MipsService
from .miot.web_pages import oauth_redirect_page
from .miot.miot_error import (
    MIoTConfigError, MIoTError, MIoTErrorCode, MIoTOauthError)
from .miot.miot_i18n import MIoTI18n
from .miot.miot_network import MIoTNetwork
from .miot.miot_client import MIoTClient, get_miot_instance_async
from .miot.miot_spec import MIoTSpecParser
from .miot.miot_lan import MIoTLan

_LOGGER = logging.getLogger(__name__)


class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Xiaomi Home config flow."""
    # pylint: disable=unused-argument, inconsistent-quotes
    VERSION = 1
    MINOR_VERSION = 1
    DEFAULT_AREA_NAME_RULE = 'room'
    _main_loop: asyncio.AbstractEventLoop
    _miot_network: MIoTNetwork
    _mips_service: MipsService
    _miot_storage: MIoTStorage
    _miot_i18n: MIoTI18n
    _miot_oauth: Optional[MIoTOauthClient]
    _miot_http: Optional[MIoTHttpClient]

    _storage_path: str
    _virtual_did: str
    _uid: str
    _uuid: str
    _ctrl_mode: str
    _area_name_rule: str
    _action_debug: bool
    _hide_non_standard_entities: bool
    _display_binary_mode: list[str]
    _display_devices_changed_notify: list[str]

    _cloud_server: str
    _integration_language: str
    _cover_dz_width: int
    _auth_info: dict
    _nick_name: str
    _home_selected: dict
    _devices_filter: dict
    _device_list_sorted: dict
    _oauth_redirect_url_full: str

    # Config cache
    _cc_home_info: dict
    _cc_home_list_show: dict
    _cc_network_detect_addr: str
    _cc_oauth_auth_url: str
    _cc_user_cert_done: bool
    _cc_task_oauth: Optional[asyncio.Task[None]]
    _cc_config_rc: Optional[str]
    _cc_fut_oauth_code: Optional[asyncio.Future]
    _opt_check_network_deps: bool

    def __init__(self) -> None:
        self._main_loop = asyncio.get_running_loop()
        self._cloud_server = DEFAULT_CLOUD_SERVER
        self._integration_language = DEFAULT_INTEGRATION_LANGUAGE
        self._cover_dz_width = DEFAULT_COVER_DEAD_ZONE_WIDTH
        self._storage_path = ''
        self._virtual_did = ''
        self._uid = ''
        self._uuid = ''   # MQTT client id
        self._ctrl_mode = DEFAULT_CTRL_MODE
        self._area_name_rule = self.DEFAULT_AREA_NAME_RULE
        self._action_debug = False
        self._hide_non_standard_entities = False
        self._display_binary_mode = ['bool']
        self._display_devices_changed_notify = ['add', 'del', 'offline']
        self._auth_info = {}
        self._nick_name = DEFAULT_NICK_NAME
        self._home_selected = {}
        self._devices_filter = {}
        self._device_list_sorted = {}
        self._oauth_redirect_url_full = ''
        self._miot_oauth = None
        self._miot_http = None

        self._cc_home_info = {}
        self._cc_home_list_show = {}
        self._cc_network_detect_addr = ''
        self._cc_oauth_auth_url = ''
        self._cc_user_cert_done = False
        self._cc_task_oauth = None
        self._cc_config_rc = None
        self._cc_fut_oauth_code = None
        self._opt_check_network_deps = False

    async def async_step_user(
        self, user_input: Optional[dict] = None
    ):
        self.hass.data.setdefault(DOMAIN, {})
        if not self._virtual_did:
            self._virtual_did = str(secrets.randbits(64))
            self.hass.data[DOMAIN].setdefault(self._virtual_did, {})
        if not self._storage_path:
            self._storage_path = self.hass.config.path('.storage', DOMAIN)
        # MIoT storage
        self._miot_storage = self.hass.data[DOMAIN].get('miot_storage', None)
        if not self._miot_storage:
            self._miot_storage = MIoTStorage(
                root_path=self._storage_path, loop=self._main_loop)
            self.hass.data[DOMAIN]['miot_storage'] = self._miot_storage
            _LOGGER.info(
                'async_step_user, create miot storage, %s', self._storage_path)
        # MIoT network
        network_detect_addr = (await self._miot_storage.load_user_config_async(
            uid='global_config', cloud_server='all',
            keys=['network_detect_addr'])).get('network_detect_addr', {})
        self._cc_network_detect_addr = ','.join(
            network_detect_addr.get('ip', [])
            + network_detect_addr.get('url', []))
        self._miot_network = self.hass.data[DOMAIN].get('miot_network', None)
        if not self._miot_network:
            self._miot_network = MIoTNetwork(
                ip_addr_list=network_detect_addr.get('ip', []),
                url_addr_list=network_detect_addr.get('url', []),
                refresh_interval=NETWORK_REFRESH_INTERVAL,
                loop=self._main_loop)
            self.hass.data[DOMAIN]['miot_network'] = self._miot_network
            await self._miot_network.init_async()
            _LOGGER.info('async_step_user, create miot network')
        # MIPS service
        self._mips_service = self.hass.data[DOMAIN].get('mips_service', None)
        if not self._mips_service:
            aiozc: HaAsyncZeroconf = await zeroconf.async_get_async_instance(
                self.hass)
            self._mips_service = MipsService(aiozc=aiozc, loop=self._main_loop)
            self.hass.data[DOMAIN]['mips_service'] = self._mips_service
            await self._mips_service.init_async()
            _LOGGER.info('async_step_user, create mips service')

        return await self.async_step_eula(user_input)

    async def async_step_eula(
        self, user_input: Optional[dict] = None
    ):
        if user_input:
            if user_input.get('eula', None) is True:
                return await self.async_step_auth_config()
            return await self.__show_eula_form('eula_not_agree')
        return await self.__show_eula_form('')

    async def __show_eula_form(self, reason: str):
        return self.async_show_form(
            step_id='eula',
            data_schema=vol.Schema({
                vol.Required('eula', default=False): bool,  # type: ignore
            }),
            last_step=False,
            errors={'base': reason},
        )

    async def async_step_auth_config(
        self, user_input: Optional[dict] = None
    ):
        if user_input:
            self._cloud_server = user_input.get(
                'cloud_server', self._cloud_server)
            # Gen instance uuid
            ha_uuid = await async_get(self.hass)
            if not ha_uuid:
                raise AbortFlow(reason='ha_uuid_get_failed')
            self._uuid = hashlib.sha256(
                f'{ha_uuid}.{self._virtual_did}.{self._cloud_server}'.encode(
                    'utf-8')).hexdigest()[:32]
            self._integration_language = user_input.get(
                'integration_language', DEFAULT_INTEGRATION_LANGUAGE)
            self._miot_i18n = MIoTI18n(
                lang=self._integration_language, loop=self._main_loop)
            await self._miot_i18n.init_async()
            webhook_path = webhook_async_generate_path(
                webhook_id=self._virtual_did)
            self._oauth_redirect_url_full = (
                f'{user_input.get("oauth_redirect_url")}{webhook_path}')

            if user_input.get('network_detect_config', False):
                return await self.async_step_network_detect_config()
            return await self.async_step_oauth(user_input)
        return await self.__show_auth_config_form('')

    async def __show_auth_config_form(self, reason: str):
        # Generate default language from HomeAssistant config (not user config)
        default_language: str = self.hass.config.language
        if default_language not in INTEGRATION_LANGUAGES:
            if default_language.split('-', 1)[0] not in INTEGRATION_LANGUAGES:
                default_language = DEFAULT_INTEGRATION_LANGUAGE
            else:
                default_language = default_language.split('-', 1)[0]
        return self.async_show_form(
            step_id='auth_config',
            data_schema=vol.Schema({
                vol.Required(
                    'cloud_server',
                    default=self._cloud_server  # type: ignore
                ):  vol.In(CLOUD_SERVERS),
                vol.Required(
                    'integration_language',
                    default=default_language  # type: ignore
                ):   vol.In(INTEGRATION_LANGUAGES),
                vol.Required(
                    'oauth_redirect_url',
                    default=OAUTH_REDIRECT_URL  # type: ignore
                ): vol.In([OAUTH_REDIRECT_URL]),
                vol.Required(
                    'network_detect_config',
                    default=False  # type: ignore
                ): bool,
            }),
            errors={'base': reason},
            last_step=False,
        )

    async def async_step_network_detect_config(
        self, user_input: Optional[dict] = None
    ):
        if not user_input:
            return await self.__show_network_detect_config_form(reason='')
        self._cc_network_detect_addr = user_input.get(
            'network_detect_addr', self._cc_network_detect_addr)

        ip_list, url_list, invalid_list = _handle_network_detect_addr(
            addr_str=self._cc_network_detect_addr)
        if invalid_list:
            return await self.__show_network_detect_config_form(
                reason='invalid_network_addr')
        if ip_list or url_list:
            if ip_list and not await self._miot_network.ping_multi_async(
                    ip_list=ip_list):
                return await self.__show_network_detect_config_form(
                    reason='invalid_ip_addr')
            if url_list and not await self._miot_network.http_multi_async(
                    url_list=url_list):
                return await self.__show_network_detect_config_form(
                    reason='invalid_http_addr')
        else:
            if not await self._miot_network.get_network_status_async():
                return await self.__show_network_detect_config_form(
                    reason='invalid_default_addr')
        network_detect_addr: dict = {'ip': ip_list, 'url': url_list}
        # Save
        if await self._miot_storage.update_user_config_async(
            uid='global_config', cloud_server='all', config={
                'network_detect_addr': network_detect_addr}):
            _LOGGER.info(
                'update network_detect_addr, %s', network_detect_addr)
        await self._miot_network.update_addr_list_async(
            ip_addr_list=ip_list, url_addr_list=url_list)
        # Check network deps
        self._opt_check_network_deps = user_input.get(
            'check_network_deps', self._opt_check_network_deps)
        if self._opt_check_network_deps:
            # OAuth2
            if not await self._miot_network.http_multi_async(
                    url_list=[OAUTH2_AUTH_URL]):
                return await self.__show_network_detect_config_form(
                    reason='unreachable_oauth2_host')
            # HTTP API
            http_host = (
                DEFAULT_OAUTH2_API_HOST
                if self._cloud_server == DEFAULT_CLOUD_SERVER
                else f'{self._cloud_server}.{DEFAULT_OAUTH2_API_HOST}')
            if not await self._miot_network.http_multi_async(
                    url_list=[
                        f'https://{http_host}/app/v2/ha/oauth/get_token']):
                return await self.__show_network_detect_config_form(
                    reason='unreachable_http_host')
            # SPEC API
            if not await self._miot_network.http_multi_async(
                    url_list=[
                        'https://miot-spec.org/miot-spec-v2/template/list/'
                        'device']):
                return await self.__show_network_detect_config_form(
                    reason='unreachable_spec_host')
            # MQTT Broker
            # pylint: disable=import-outside-toplevel
            try:
                from paho.mqtt import client
                mqtt_client = client.Client(
                    client_id=f'ha.{self._uid}',
                    protocol=client.MQTTv5)  # type: ignore
                if mqtt_client.connect(
                    host=f'{self._cloud_server}-{DEFAULT_CLOUD_BROKER_HOST}',
                    port=8883) != 0:
                    raise RuntimeError('mqtt connect error')
                mqtt_client.disconnect()
            except Exception as err:  # pylint: disable=broad-exception-caught
                _LOGGER.error('try connect mqtt broker error, %s', err)
                return await self.__show_network_detect_config_form(
                    reason='unreachable_mqtt_broker')

        return await self.async_step_oauth()

    async def __show_network_detect_config_form(self, reason: str):
        if not self._cc_network_detect_addr:
            addr_list: dict = (await self._miot_storage.load_user_config_async(
                'global_config', 'all', ['network_detect_addr'])).get(
                    'network_detect_addr', {})
            self._cc_network_detect_addr = ','.join(
                addr_list.get('ip', [])+addr_list.get('url', []))
        return self.async_show_form(
            step_id='network_detect_config',
            data_schema=vol.Schema({
                vol.Optional(
                    'network_detect_addr',
                    default=self._cc_network_detect_addr  # type: ignore
                ): str,
                vol.Optional(
                    'check_network_deps',
                    default=self._opt_check_network_deps  # type: ignore
                ): bool,
            }),
            errors={'base': reason},
            description_placeholders={
                'broker_host':
                    f'{self._cloud_server}-{DEFAULT_CLOUD_BROKER_HOST}:8883',
                'http_host': (
                    DEFAULT_OAUTH2_API_HOST
                    if self._cloud_server == DEFAULT_CLOUD_SERVER
                    else f'{self._cloud_server}.{DEFAULT_OAUTH2_API_HOST}')},
            last_step=False
        )

    async def async_step_oauth(
        self, user_input: Optional[dict] = None
    ):
        # 1: Init miot_oauth, generate auth url
        try:
            if not self._miot_oauth:
                _LOGGER.info(
                    'async_step_oauth, redirect_url: %s',
                    self._oauth_redirect_url_full)
                miot_oauth = MIoTOauthClient(
                    client_id=OAUTH2_CLIENT_ID,
                    redirect_url=self._oauth_redirect_url_full,
                    cloud_server=self._cloud_server,
                    uuid=self._uuid,
                    loop=self._main_loop)
                self._cc_oauth_auth_url = miot_oauth.gen_auth_url(
                    redirect_url=self._oauth_redirect_url_full)
                self.hass.data[DOMAIN][self._virtual_did]['oauth_state'] = (
                    miot_oauth.state)
                self.hass.data[DOMAIN][self._virtual_did]['i18n'] = (
                    self._miot_i18n)
                _LOGGER.info(
                    'async_step_oauth, oauth_url: %s', self._cc_oauth_auth_url)
                webhook_async_unregister(
                    self.hass, webhook_id=self._virtual_did)
                webhook_async_register(
                    self.hass,
                    domain=DOMAIN,
                    name='oauth redirect url webhook',
                    webhook_id=self._virtual_did,
                    handler=_handle_oauth_webhook,
                    allowed_methods=(METH_GET,),
                )
                self._cc_fut_oauth_code = self.hass.data[DOMAIN][
                    self._virtual_did].get('fut_oauth_code', None)
                if not self._cc_fut_oauth_code:
                    self._cc_fut_oauth_code = self._main_loop.create_future()
                    self.hass.data[DOMAIN][self._virtual_did][
                        'fut_oauth_code'] = self._cc_fut_oauth_code
                _LOGGER.info(
                    'async_step_oauth, webhook.async_register: %s',
                    self._virtual_did)
                self._miot_oauth = miot_oauth
        except Exception as err:  # pylint: disable=broad-exception-caught
            _LOGGER.error(
                'async_step_oauth, %s, %s', err, traceback.format_exc())
            return self.async_show_progress_done(next_step_id='oauth_error')

        # 2: show OAuth2 loading page
        if self._cc_task_oauth is None:
            self._cc_task_oauth = self.hass.async_create_task(
                self.__check_oauth_async())
        if self._cc_task_oauth.done():
            if (error := self._cc_task_oauth.exception()):
                _LOGGER.error('task_oauth exception, %s', error)
                self._cc_config_rc = str(error)
                return self.async_show_progress_done(next_step_id='oauth_error')
            if self._miot_oauth:
                await self._miot_oauth.deinit_async()
                self._miot_oauth = None
            return self.async_show_progress_done(next_step_id='homes_select')
        # pylint: disable=unexpected-keyword-arg
        return self.async_show_progress(
            step_id='oauth',
            progress_action='oauth',
            description_placeholders={
                'link_left':
                    f'<a href="{self._cc_oauth_auth_url}" target="_blank">',
                'link_right': '</a>'
            },
            progress_task=self._cc_task_oauth,  # type: ignore
        )

    async def __check_oauth_async(self) -> None:
        # TASK 1: Get oauth code
        if not self._cc_fut_oauth_code:
            raise MIoTConfigError('oauth_code_fut_error')
        oauth_code: Optional[str] = await self._cc_fut_oauth_code
        if not oauth_code:
            raise MIoTConfigError('oauth_code_error')
        # TASK 2: Get access_token and user_info from miot_oauth
        if not self._auth_info:
            try:
                if not self._miot_oauth:
                    raise MIoTConfigError('oauth_client_error')
                auth_info = await self._miot_oauth.get_access_token_async(
                    code=oauth_code)
                if not self._miot_http:
                    self._miot_http = MIoTHttpClient(
                        cloud_server=self._cloud_server,
                        client_id=OAUTH2_CLIENT_ID,
                        access_token=auth_info['access_token'])
                else:
                    self._miot_http.update_http_header(
                        cloud_server=self._cloud_server,
                        client_id=OAUTH2_CLIENT_ID,
                        access_token=auth_info['access_token'])
                self._auth_info = auth_info
                try:
                    self._nick_name = (
                        await self._miot_http.get_user_info_async() or {}
                    ).get('miliaoNick', self._nick_name)
                except (MIoTOauthError, json.JSONDecodeError):
                    self._nick_name = DEFAULT_NICK_NAME
                    _LOGGER.error('get nick name failed')
            except Exception as err:
                _LOGGER.error(
                    'get_access_token, %s, %s', err, traceback.format_exc())
                raise MIoTConfigError('get_token_error') from err

        # TASK 3: Get home info
        try:
            if not self._miot_http:
                raise MIoTConfigError('http_client_error')
            self._cc_home_info = (
                await self._miot_http.get_devices_async())
            _LOGGER.info('get_homeinfos response: %s', self._cc_home_info)
            self._uid = self._cc_home_info['uid']
            if self._uid == self._nick_name:
                self._nick_name = DEFAULT_NICK_NAME
            # Save auth_info
            if not (await self._miot_storage.update_user_config_async(
                    uid=self._uid, cloud_server=self._cloud_server, config={
                        'auth_info': self._auth_info
                    })):
                raise MIoTError('miot_storage.update_user_config_async error')
        except Exception as err:
            _LOGGER.error(
                'get_homeinfos error, %s, %s', err, traceback.format_exc())
            raise MIoTConfigError('get_homeinfo_error') from err

        # TASK 4: Abort if unique_id configured
        # Each MiHome account can only configure one instance
        await self.async_set_unique_id(f'{self._cloud_server}{self._uid}')
        self._abort_if_unique_id_configured()

        # TASK 5: Query mdns info
        mips_list = None
        if self._cloud_server in SUPPORT_CENTRAL_GATEWAY_CTRL:
            try:
                mips_list = self._mips_service.get_services()
            except Exception as err:
                _LOGGER.error(
                    'async_update_services error, %s, %s',
                    err, traceback.format_exc())
                raise MIoTConfigError('mdns_discovery_error') from err

        # TASK 6: Generate devices filter
        home_list = {}
        tip_devices = self._miot_i18n.translate(key='config.other.devices')
        # home list
        for device_source in ['home_list','share_home_list',
                              'separated_shared_list']:
            if device_source not in self._cc_home_info['homes']:
                continue
            for home_id, home_info in self._cc_home_info[
                    'homes'][device_source].items():
                # i18n
                tip_central = ''
                group_id = home_info.get('group_id', None)
                dev_list = {
                    device['did']: device
                    for device in list(self._cc_home_info['devices'].values())
                    if device.get('home_id', None) == home_id}
                if (
                    mips_list
                    and group_id in mips_list
                    and mips_list[group_id].get('did', None) in dev_list
                ):
                    # i18n
                    tip_central = self._miot_i18n.translate(
                        key='config.other.found_central_gateway')
                    home_info['central_did'] = mips_list[group_id].get(
                        'did', None)
                home_list[home_id] = (
                    f'{home_info["home_name"]} '
                    f'[ {len(dev_list)} {tip_devices} {tip_central} ]')

        self._cc_home_list_show = dict(sorted(home_list.items()))

        # TASK 7: Get user's MiHome certificate
        if self._cloud_server in SUPPORT_CENTRAL_GATEWAY_CTRL:
            miot_cert = MIoTCert(
                storage=self._miot_storage,
                uid=self._uid, cloud_server=self._cloud_server)
            if not self._cc_user_cert_done:
                try:
                    if await miot_cert.user_cert_remaining_time_async(
                            did=self._virtual_did) < MIHOME_CERT_EXPIRE_MARGIN:
                        user_key = await miot_cert.load_user_key_async()
                        if user_key is None:
                            user_key = miot_cert.gen_user_key()
                            if not await miot_cert.update_user_key_async(
                                    key=user_key):
                                raise MIoTError('update_user_key_async failed')
                        csr_str = miot_cert.gen_user_csr(
                            user_key=user_key, did=self._virtual_did)
                        crt_str = await self._miot_http.get_central_cert_async(
                            csr_str)
                        if not crt_str:
                            raise MIoTError('get_central_cert_async failed')
                        if not await miot_cert.update_user_cert_async(
                                cert=crt_str):
                            raise MIoTError('update_user_cert_async failed')
                        self._cc_user_cert_done = True
                        _LOGGER.info(
                            'get mihome cert success, %s, %s',
                            self._uid, self._virtual_did)
                except Exception as err:
                    _LOGGER.error(
                        'get user cert error, %s, %s',
                        err, traceback.format_exc())
                    raise MIoTConfigError('get_cert_error') from err

        # Auth success, unregister oauth webhook
        webhook_async_unregister(self.hass, webhook_id=self._virtual_did)
        if self._miot_http:
            await self._miot_http.deinit_async()
            self._miot_http = None
        _LOGGER.info(
            '__check_oauth_async, webhook.async_unregister: %s',
            self._virtual_did)

    # Show setup error message
    async def async_step_oauth_error(self, user_input=None):
        if self._cc_config_rc is None:
            return await self.async_step_oauth()
        if self._cc_config_rc.startswith('Flow aborted: '):
            raise AbortFlow(
                reason=self._cc_config_rc.replace('Flow aborted: ', ''))
        error_reason = self._cc_config_rc
        self._cc_config_rc = None
        return self.async_show_form(
            step_id='oauth_error',
            data_schema=vol.Schema({}),
            last_step=False,
            errors={'base': error_reason},
        )

    async def async_step_homes_select(
        self, user_input: Optional[dict] = None
    ):
        _LOGGER.debug('async_step_homes_select')
        try:
            if not user_input:
                return await self.__show_homes_select_form('')

            home_selected: list = user_input.get('home_infos', [])
            if not home_selected:
                return await self.__show_homes_select_form(
                    'no_family_selected')
            for device_source in ['home_list','share_home_list',
                                  'separated_shared_list']:
                if device_source not in self._cc_home_info['homes']:
                    continue
                for home_id, home_info in self._cc_home_info[
                        'homes'][device_source].items():
                    if home_id in home_selected:
                        self._home_selected[home_id] = home_info
            self._area_name_rule = user_input.get(
                'area_name_rule', self._area_name_rule)
            # Storage device list
            devices_list: dict[str, dict] = {
                did: dev_info
                for did, dev_info in self._cc_home_info['devices'].items()
                if dev_info['home_id'] in home_selected}
            if not devices_list:
                return await self.__show_homes_select_form('no_devices')
            self._device_list_sorted = dict(sorted(
                devices_list.items(), key=lambda item:
                    item[1].get('home_id', '')+item[1].get('room_id', '')))

            if not await self._miot_storage.save_async(
                    domain='miot_devices',
                    name=f'{self._uid}_{self._cloud_server}',
                    data=self._device_list_sorted):
                _LOGGER.error(
                    'save devices async failed, %s, %s',
                    self._uid, self._cloud_server)
                return await self.__show_homes_select_form(
                    'devices_storage_failed')
            if user_input.get('advanced_options', False):
                return await self.async_step_advanced_options()
            return await self.config_flow_done()
        except Exception as err:
            _LOGGER.error(
                'async_step_homes_select, %s, %s',
                err, traceback.format_exc())
            raise AbortFlow(
                reason='config_flow_error',
                description_placeholders={
                    'error': f'config_flow error, {err}'}
            ) from err

    async def __show_homes_select_form(self, reason: str):
        return self.async_show_form(
            step_id='homes_select',
            data_schema=vol.Schema({
                vol.Required('home_infos'): cv.multi_select(
                    self._cc_home_list_show),
                vol.Required(
                    'area_name_rule',
                    default=self._area_name_rule  # type: ignore
                ): vol.In(self._miot_i18n.translate(
                    key='config.room_name_rule')),
                vol.Required(
                    'advanced_options', default=False  # type: ignore
                ): bool,
            }),
            errors={'base': reason},
            description_placeholders={
                'nick_name': self._nick_name,
            },
            last_step=False,
        )

    async def async_step_advanced_options(
        self, user_input: Optional[dict] = None
    ):
        if user_input:
            self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode)
            self._action_debug = user_input.get(
                'action_debug', self._action_debug)
            self._hide_non_standard_entities = user_input.get(
                'hide_non_standard_entities', self._hide_non_standard_entities)
            self._display_binary_mode = user_input.get(
                'display_binary_mode', self._display_binary_mode)
            self._display_devices_changed_notify = user_input.get(
                'display_devices_changed_notify',
                self._display_devices_changed_notify)
            # Device filter
            if user_input.get('devices_filter', False):
                return await self.async_step_devices_filter()
            return await self.config_flow_done()
        return self.async_show_form(
            step_id='advanced_options',
            data_schema=vol.Schema({
                vol.Required(
                    'devices_filter', default=False): bool,  # type: ignore
                vol.Required(
                    'ctrl_mode', default=self._ctrl_mode  # type: ignore
                ): vol.In(self._miot_i18n.translate(key='config.control_mode')),
                vol.Required(
                    'action_debug', default=self._action_debug  # type: ignore
                ): bool,
                vol.Required(
                    'hide_non_standard_entities',
                    default=self._hide_non_standard_entities  # type: ignore
                ): bool,
                vol.Required(
                    'display_binary_mode',
                    default=self._display_binary_mode  # type: ignore
                ): cv.multi_select(
                    self._miot_i18n.translate(
                        key='config.binary_mode')),  # type: ignore
                vol.Required(
                    'display_devices_changed_notify',
                    default=self._display_devices_changed_notify  # type: ignore
                ): cv.multi_select(
                    self._miot_i18n.translate(
                        key='config.device_state')),  # type: ignore
            }),
            last_step=False,
        )

    async def async_step_devices_filter(
        self, user_input: Optional[dict] = None
    ):
        if user_input:
            # Room filter
            include_items: dict = {}
            exclude_items: dict = {}
            room_list_in: list = user_input.get('room_list', [])
            if room_list_in:
                if user_input.get(
                        'room_filter_mode', 'exclude') == 'include':
                    include_items['room_id'] = room_list_in
                else:
                    exclude_items['room_id'] = room_list_in
            # Connect Type filter
            type_list_in: list = user_input.get('type_list', [])
            if type_list_in:
                if user_input.get(
                        'type_filter_mode', 'exclude') == 'include':
                    include_items['connect_type'] = type_list_in
                else:
                    exclude_items['connect_type'] = type_list_in
            # Model filter
            model_list_in: list = user_input.get('model_list', [])
            if model_list_in:
                if user_input.get(
                        'model_filter_mode', 'exclude') == 'include':
                    include_items['model'] = model_list_in
                else:
                    exclude_items['model'] = model_list_in
            # Device filter
            device_list_in: list = user_input.get('device_list', [])
            if device_list_in:
                if user_input.get(
                        'devices_filter_mode', 'exclude') == 'include':
                    include_items['did'] = device_list_in
                else:
                    exclude_items['did'] = device_list_in
            device_filter_list = _handle_devices_filter(
                devices=self._device_list_sorted,
                logic_or=(user_input.get('statistics_logic', 'or') == 'or'),
                item_in=include_items, item_ex=exclude_items)
            if not device_filter_list:
                return await self.__show_devices_filter_form(
                    reason='no_filter_devices')
            self._device_list_sorted = dict(sorted(
                device_filter_list.items(), key=lambda item:
                    item[1].get('home_id', '')+item[1].get('room_id', '')))
            # Save devices
            if not await self._miot_storage.save_async(
                    domain='miot_devices',
                    name=f'{self._uid}_{self._cloud_server}',
                    data=self._device_list_sorted):
                _LOGGER.error(
                    'save devices async failed, %s, %s',
                    self._uid, self._cloud_server)
                raise AbortFlow(
                    reason='storage_error', description_placeholders={
                        'error': 'save user devices error'})
            self._devices_filter = {
                'room_list': {
                    'items': room_list_in,
                    'mode': user_input.get('room_filter_mode', 'exclude')},
                'type_list': {
                    'items': type_list_in,
                    'mode': user_input.get('type_filter_mode', 'exclude')},
                'model_list': {
                    'items': model_list_in,
                    'mode': user_input.get('model_filter_mode', 'exclude')},
                'device_list': {
                    'items': device_list_in,
                    'mode': user_input.get('devices_filter_mode', 'exclude')},
                'statistics_logic': user_input.get('statistics_logic', 'or'),
            }
            return await self.config_flow_done()
        return await self.__show_devices_filter_form(reason='')

    async def __show_devices_filter_form(self, reason: str):
        tip_devices: str = self._miot_i18n.translate(
            key='config.other.devices')  # type: ignore
        tip_without_room: str = self._miot_i18n.translate(
            key='config.other.without_room')  # type: ignore
        trans_statistics_logic: dict = self._miot_i18n.translate(
            key='config.statistics_logic')  # type: ignore
        trans_filter_mode: dict = self._miot_i18n.translate(
            key='config.filter_mode')  # type: ignore
        trans_connect_type: dict = self._miot_i18n.translate(
            key='config.connect_type')  # type: ignore

        room_device_count: dict = {}
        model_device_count: dict = {}
        connect_type_count: dict = {}
        device_list: dict = {}
        for did, info in self._device_list_sorted.items():
            device_list[did] = (
                f'[ {info["home_name"]} {info["room_name"]} ] ' +
                f'{info["name"]}, {did}')
            room_device_count.setdefault(info['room_id'], 0)
            room_device_count[info['room_id']] += 1
            model_device_count.setdefault(info['model'], 0)
            model_device_count[info['model']] += 1
            connect_type_count.setdefault(str(info['connect_type']), 0)
            connect_type_count[str(info['connect_type'])] += 1
        model_list: dict = {}
        for model, count in model_device_count.items():
            model_list[model] = f'{model} [ {count} {tip_devices} ]'
        type_list: dict = {
            k: f'{trans_connect_type.get(k, f"Connect Type ({k})")} '
            f'[ {v} {tip_devices} ]'
            for k, v in connect_type_count.items()}
        room_list: dict = {}
        for home_id, home_info in self._home_selected.items():
            for room_id, room_name in home_info['room_info'].items():
                if room_id not in room_device_count:
                    continue
                room_list[room_id] = (
                    f'{home_info["home_name"]} {room_name}'
                    f' [ {room_device_count[room_id]}{tip_devices} ]')
            if home_id in room_device_count:
                room_list[home_id] = (
                    f'{home_info["home_name"]} {tip_without_room}'
                    f' [ {room_device_count[home_id]}{tip_devices} ]')
        return self.async_show_form(
            step_id='devices_filter',
            data_schema=vol.Schema({
                vol.Required(
                    'room_filter_mode', default='exclude'  # type: ignore
                ): vol.In(trans_filter_mode),
                vol.Optional('room_list'): cv.multi_select(room_list),
                vol.Required(
                    'type_filter_mode', default='exclude'  # type: ignore
                ): vol.In(trans_filter_mode),
                vol.Optional('type_list'): cv.multi_select(type_list),
                vol.Required(
                    'model_filter_mode', default='exclude'  # type: ignore
                ): vol.In(trans_filter_mode),
                vol.Optional('model_list'): cv.multi_select(dict(sorted(
                    model_list.items(), key=lambda item: item[0]))),
                vol.Required(
                    'devices_filter_mode', default='exclude'  # type: ignore
                ): vol.In(trans_filter_mode),
                vol.Optional('device_list'): cv.multi_select(dict(sorted(
                    device_list.items(), key=lambda device: device[1]))),
                vol.Required(
                    'statistics_logic', default='or'  # type: ignore
                ): vol.In(trans_statistics_logic),
            }),
            errors={'base': reason},
            last_step=False
        )

    async def config_flow_done(self):
        return self.async_create_entry(
            title=(
                f'{self._nick_name}: {self._uid} '
                f'[{CLOUD_SERVERS[self._cloud_server]}]'),
            data={
                'virtual_did': self._virtual_did,
                'uuid': self._uuid,
                'integration_language': self._integration_language,
                'storage_path': self._storage_path,
                'uid': self._uid,
                'nick_name': self._nick_name,
                'cloud_server': self._cloud_server,
                'oauth_redirect_url': self._oauth_redirect_url_full,
                'ctrl_mode': self._ctrl_mode,
                'home_selected': self._home_selected,
                'devices_filter': self._devices_filter,
                'area_name_rule': self._area_name_rule,
                'action_debug': self._action_debug,
                'hide_non_standard_entities':
                    self._hide_non_standard_entities,
                'cover_dead_zone_width': self._cover_dz_width,
                'display_binary_mode': self._display_binary_mode,
                'display_devices_changed_notify':
                    self._display_devices_changed_notify
            })

    @ staticmethod
    @ callback
    def async_get_options_flow(
            config_entry: config_entries.ConfigEntry,
    ) -> config_entries.OptionsFlow:
        return OptionsFlowHandler(config_entry)


class OptionsFlowHandler(config_entries.OptionsFlow):
    """Xiaomi MiHome options flow."""
    # pylint: disable=unused-argument
    # pylint: disable=inconsistent-quotes
    _config_entry: config_entries.ConfigEntry
    _main_loop: asyncio.AbstractEventLoop
    _miot_client: MIoTClient

    _miot_network: MIoTNetwork
    _miot_storage: MIoTStorage
    _mips_service: MipsService
    _miot_oauth: MIoTOauthClient
    _miot_http: MIoTHttpClient
    _miot_i18n: MIoTI18n
    _miot_lan: MIoTLan

    _entry_data: dict
    _virtual_did: str
    _uid: str
    _storage_path: str
    _cloud_server: str

    _integration_language: str
    _ctrl_mode: str
    _nick_name: str
    _home_selected_list: list
    _devices_filter: dict
    _action_debug: bool
    _hide_non_standard_entities: bool
    _display_binary_mode: list[str]
    _display_devs_notify: list[str]
    _cover_dz_width: int

    _oauth_redirect_url_full: str
    _auth_info: dict
    _home_selected: dict
    _device_list_sorted: dict
    _devices_add: list[str]
    _devices_remove: list[str]

    # Config options
    _lang_new: str
    _nick_name_new: Optional[str]
    _action_debug_new: bool
    _hide_non_standard_entities_new: bool
    _display_binary_mode_new: list[str]
    _update_user_info: bool
    _update_devices: bool
    _update_trans_rules: bool
    _opt_lan_ctrl_cfg: bool
    _opt_network_detect_cfg: bool
    _opt_check_network_deps: bool
    _cover_width_new: int

    _trans_rules_count: int
    _trans_rules_count_success: int

    _need_reload: bool

    # Config cache
    _cc_home_info: dict
    _cc_home_list_show: dict
    _cc_oauth_auth_url: Optional[str]
    _cc_task_oauth: Optional[asyncio.Task[None]]
    _cc_config_rc: Optional[str]
    _cc_fut_oauth_code: Optional[asyncio.Future]
    _cc_devices_local: dict
    _cc_network_detect_addr: str

    def __init__(self, config_entry: config_entries.ConfigEntry):
        self._config_entry = config_entry
        self._main_loop = asyncio.get_event_loop()

        self._entry_data = dict(config_entry.data)
        self._virtual_did = self._entry_data['virtual_did']
        self._uid = self._entry_data['uid']
        self._storage_path = self._entry_data['storage_path']
        self._cloud_server = self._entry_data['cloud_server']
        self._ctrl_mode = self._entry_data.get('ctrl_mode', DEFAULT_CTRL_MODE)
        self._integration_language = self._entry_data.get(
            'integration_language', DEFAULT_INTEGRATION_LANGUAGE)
        self._cover_dz_width = self._entry_data.get(
            'cover_dead_zone_width', DEFAULT_COVER_DEAD_ZONE_WIDTH)
        self._nick_name = self._entry_data.get('nick_name', DEFAULT_NICK_NAME)
        self._action_debug = self._entry_data.get('action_debug', False)
        self._hide_non_standard_entities = self._entry_data.get(
            'hide_non_standard_entities', False)
        self._display_binary_mode = self._entry_data.get(
            'display_binary_mode', ['text'])
        self._display_devs_notify = self._entry_data.get(
            'display_devices_changed_notify', ['add', 'del', 'offline'])
        self._home_selected_list = list(
            self._entry_data['home_selected'].keys())
        self._devices_filter = self._entry_data.get('devices_filter', {})

        self._oauth_redirect_url_full = ''
        self._auth_info = {}
        self._home_selected = {}
        self._device_list_sorted = {}

        self._devices_add = []
        self._devices_remove = []

        self._lang_new = self._integration_language
        self._nick_name_new = None
        self._action_debug_new = False
        self._hide_non_standard_entities_new = False
        self._display_binary_mode_new = []
        self._cover_width_new = self._cover_dz_width
        self._update_user_info = False
        self._update_devices = False
        self._update_trans_rules = False
        self._opt_lan_ctrl_cfg = False
        self._opt_network_detect_cfg = False
        self._opt_check_network_deps = False
        self._trans_rules_count = 0
        self._trans_rules_count_success = 0

        self._need_reload = False

        self._cc_home_info = {}
        self._cc_home_list_show = {}
        self._cc_oauth_auth_url = None
        self._cc_task_oauth = None
        self._cc_config_rc = None
        self._cc_fut_oauth_code = None
        self._cc_devices_local = {}
        self._cc_network_detect_addr = ''

        _LOGGER.info(
            'options init, %s, %s, %s, %s', config_entry.entry_id,
            config_entry.unique_id, config_entry.data, config_entry.options)

    async def async_step_init(self, user_input=None):
        self.hass.data.setdefault(DOMAIN, {})
        self.hass.data[DOMAIN].setdefault(self._virtual_did, {})
        try:
            # MIoT client
            self._miot_client = await get_miot_instance_async(
                hass=self.hass, entry_id=self._config_entry.entry_id)
            if not self._miot_client:
                raise MIoTConfigError('invalid miot client')
            # MIoT network
            self._miot_network = self._miot_client.miot_network
            if not self._miot_network:
                raise MIoTConfigError('invalid miot network')
            # MIoT storage
            self._miot_storage = self._miot_client.miot_storage
            if not self._miot_storage:
                raise MIoTConfigError('invalid miot storage')
            # Mips service
            self._mips_service = self._miot_client.mips_service
            if not self._mips_service:
                raise MIoTConfigError('invalid mips service')
            # MIoT oauth
            self._miot_oauth = self._miot_client.miot_oauth
            if not self._miot_oauth:
                raise MIoTConfigError('invalid miot oauth')
            # MIoT http
            self._miot_http = self._miot_client.miot_http
            if not self._miot_http:
                raise MIoTConfigError('invalid miot http')
            self._miot_i18n = self._miot_client.miot_i18n
            if not self._miot_i18n:
                raise MIoTConfigError('invalid miot i18n')
            self._miot_lan = self._miot_client.miot_lan
            if not self._miot_lan:
                raise MIoTConfigError('invalid miot lan')
            # Check token
            if not await self._miot_client.refresh_oauth_info_async():
                # Check network
                if not await self._miot_network.get_network_status_async():
                    raise AbortFlow(
                        reason='network_connect_error',
                        description_placeholders={})
                self._need_reload = True
                return await self.async_step_auth_config()
            return await self.async_step_config_options()
        except MIoTConfigError as err:
            raise AbortFlow(
                reason='options_flow_error',
                description_placeholders={'error': str(err)}
            ) from err
        except AbortFlow as err:
            raise err
        except Exception as err:
            _LOGGER.error(
                'async_step_init error, %s, %s',
                err, traceback.format_exc())
            raise AbortFlow(
                reason='re_add',
                description_placeholders={'error': str(err)},
            ) from err

    async def async_step_auth_config(self, user_input=None):
        if user_input:
            webhook_path = webhook_async_generate_path(
                webhook_id=self._virtual_did)
            self._oauth_redirect_url_full = (
                f'{user_input.get("oauth_redirect_url")}{webhook_path}')
            return await self.async_step_oauth(user_input)
        return self.async_show_form(
            step_id='auth_config',
            data_schema=vol.Schema({
                vol.Required(
                    'oauth_redirect_url',
                    default=OAUTH_REDIRECT_URL  # type: ignore
                ): vol.In([OAUTH_REDIRECT_URL]),
            }),
            description_placeholders={
                'cloud_server': CLOUD_SERVERS[self._cloud_server],
            },
            last_step=False,
        )

    async def async_step_oauth(self, user_input=None):
        try:
            if self._cc_task_oauth is None:
                self._cc_oauth_auth_url = self._miot_oauth.gen_auth_url(
                    redirect_url=self._oauth_redirect_url_full)
                self.hass.data[DOMAIN][self._virtual_did]['oauth_state'] = (
                    self._miot_oauth.state)
                self.hass.data[DOMAIN][self._virtual_did]['i18n'] = (
                    self._miot_i18n)
                _LOGGER.info(
                    'async_step_oauth, oauth_url: %s', self._cc_oauth_auth_url)
                webhook_async_unregister(
                    self.hass, webhook_id=self._virtual_did)
                webhook_async_register(
                    self.hass,
                    domain=DOMAIN,
                    name='oauth redirect url webhook',
                    webhook_id=self._virtual_did,
                    handler=_handle_oauth_webhook,
                    allowed_methods=(METH_GET,),
                )
                self._cc_fut_oauth_code = self.hass.data[DOMAIN][
                    self._virtual_did].get('fut_oauth_code', None)
                if self._cc_fut_oauth_code is None:
                    self._cc_fut_oauth_code = self._main_loop.create_future()
                    self.hass.data[DOMAIN][self._virtual_did][
                        'fut_oauth_code'] = self._cc_fut_oauth_code
                self._cc_task_oauth = self.hass.async_create_task(
                    self.__check_oauth_async())
                _LOGGER.info(
                    'async_step_oauth, webhook.async_register: %s',
                    self._virtual_did)

            if self._cc_task_oauth.done():
                if (error := self._cc_task_oauth.exception()):
                    _LOGGER.error('task_oauth exception, %s', error)
                    self._cc_config_rc = str(error)
            
Download .txt
gitextract_vsvd5d4t/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   └── config.yml
│   └── workflows/
│       ├── release.yaml
│       ├── test.yaml
│       └── validate.yaml
├── .gitignore
├── .pylintrc
├── CHANGELOG.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE.md
├── LegalNotice.md
├── README.md
├── custom_components/
│   └── xiaomi_home/
│       ├── __init__.py
│       ├── binary_sensor.py
│       ├── button.py
│       ├── climate.py
│       ├── config_flow.py
│       ├── cover.py
│       ├── device_tracker.py
│       ├── event.py
│       ├── fan.py
│       ├── humidifier.py
│       ├── light.py
│       ├── manifest.json
│       ├── media_player.py
│       ├── miot/
│       │   ├── common.py
│       │   ├── const.py
│       │   ├── i18n/
│       │   │   ├── de.json
│       │   │   ├── en.json
│       │   │   ├── es.json
│       │   │   ├── fr.json
│       │   │   ├── it.json
│       │   │   ├── ja.json
│       │   │   ├── nl.json
│       │   │   ├── pt-BR.json
│       │   │   ├── pt.json
│       │   │   ├── ru.json
│       │   │   ├── tr.json
│       │   │   ├── zh-Hans.json
│       │   │   └── zh-Hant.json
│       │   ├── lan/
│       │   │   └── profile_models.yaml
│       │   ├── miot_client.py
│       │   ├── miot_cloud.py
│       │   ├── miot_device.py
│       │   ├── miot_error.py
│       │   ├── miot_i18n.py
│       │   ├── miot_lan.py
│       │   ├── miot_mdns.py
│       │   ├── miot_mips.py
│       │   ├── miot_network.py
│       │   ├── miot_spec.py
│       │   ├── miot_storage.py
│       │   ├── resource/
│       │   │   └── oauth_redirect_page.html
│       │   ├── specs/
│       │   │   ├── bool_trans.yaml
│       │   │   ├── multi_lang.json
│       │   │   ├── spec_add.json
│       │   │   ├── spec_filter.yaml
│       │   │   ├── spec_modify.yaml
│       │   │   └── specv2entity.py
│       │   └── web_pages.py
│       ├── notify.py
│       ├── number.py
│       ├── select.py
│       ├── sensor.py
│       ├── switch.py
│       ├── text.py
│       ├── translations/
│       │   ├── de.json
│       │   ├── en.json
│       │   ├── es.json
│       │   ├── fr.json
│       │   ├── it.json
│       │   ├── ja.json
│       │   ├── nl.json
│       │   ├── pt-BR.json
│       │   ├── pt.json
│       │   ├── ru.json
│       │   ├── tr.json
│       │   ├── zh-Hans.json
│       │   └── zh-Hant.json
│       ├── vacuum.py
│       └── water_heater.py
├── doc/
│   ├── CONTRIBUTING_zh.md
│   └── README_zh.md
├── hacs.json
├── install.sh
├── test/
│   ├── .gitignore
│   ├── check_rule_format.py
│   ├── conftest.py
│   ├── pytest.ini
│   ├── test_cloud.py
│   ├── test_common.py
│   ├── test_lan.py
│   ├── test_mdns.py
│   ├── test_mips.py
│   ├── test_network.py
│   ├── test_spec.py
│   └── test_storage.py
└── tools/
    ├── common.py
    └── update_lan_rule.py
Download .txt
SYMBOL INDEX (1008 symbols across 45 files)

FILE: custom_components/xiaomi_home/__init__.py
  function async_setup (line 71) | async def async_setup(hass: HomeAssistant, hass_config: dict) -> bool:
  function async_setup_entry (line 85) | async def async_setup_entry(
  function async_unload_entry (line 282) | async def async_unload_entry(
  function async_remove_entry (line 302) | async def async_remove_entry(
  function async_remove_config_entry_device (line 325) | async def async_remove_config_entry_device(

FILE: custom_components/xiaomi_home/binary_sensor.py
  function async_setup_entry (line 60) | async def async_setup_entry(
  class BinarySensor (line 80) | class BinarySensor(MIoTPropertyEntity, BinarySensorEntity):
    method __init__ (line 83) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) ->...
    method is_on (line 90) | def is_on(self) -> bool:

FILE: custom_components/xiaomi_home/button.py
  function async_setup_entry (line 60) | async def async_setup_entry(
  class Button (line 78) | class Button(MIoTActionEntity, ButtonEntity):
    method __init__ (line 81) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecAction) -> N...
    method async_press (line 86) | async def async_press(self) -> None:

FILE: custom_components/xiaomi_home/climate.py
  function async_setup_entry (line 67) | async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry,
  class FeatureOnOff (line 95) | class FeatureOnOff(MIoTServiceEntity, ClimateEntity):
    method __init__ (line 99) | def __init__(self, miot_device: MIoTDevice,
    method _init_on_off (line 106) | def _init_on_off(self, service_name: str, prop_name: str) -> None:
    method async_turn_on (line 119) | async def async_turn_on(self) -> None:
    method async_turn_off (line 123) | async def async_turn_off(self) -> None:
  class FeatureTargetTemperature (line 128) | class FeatureTargetTemperature(MIoTServiceEntity, ClimateEntity):
    method __init__ (line 132) | def __init__(self, miot_device: MIoTDevice,
    method async_set_temperature (line 159) | async def async_set_temperature(self, **kwargs):
    method target_temperature (line 172) | def target_temperature(self) -> Optional[float]:
  class FeaturePresetMode (line 178) | class FeaturePresetMode(MIoTServiceEntity, ClimateEntity):
    method __init__ (line 183) | def __init__(self, miot_device: MIoTDevice,
    method _init_preset_modes (line 191) | def _init_preset_modes(self, service_name: str, prop_name: str) -> None:
    method async_set_preset_mode (line 206) | async def async_set_preset_mode(self, preset_mode: str) -> None:
    method preset_mode (line 214) | def preset_mode(self) -> Optional[str]:
  class FeatureFanMode (line 221) | class FeatureFanMode(MIoTServiceEntity, ClimateEntity):
    method __init__ (line 227) | def __init__(self, miot_device: MIoTDevice,
    method async_set_fan_mode (line 259) | async def async_set_fan_mode(self, fan_mode):
    method fan_mode (line 274) | def fan_mode(self) -> Optional[str]:
  class FeatureSwingMode (line 286) | class FeatureSwingMode(MIoTServiceEntity, ClimateEntity):
    method __init__ (line 291) | def __init__(self, miot_device: MIoTDevice,
    method async_set_swing_mode (line 315) | async def async_set_swing_mode(self, swing_mode):
    method swing_mode (line 346) | def swing_mode(self) -> Optional[str]:
  class FeatureTemperature (line 366) | class FeatureTemperature(MIoTServiceEntity, ClimateEntity):
    method __init__ (line 370) | def __init__(self, miot_device: MIoTDevice,
    method current_temperature (line 383) | def current_temperature(self) -> Optional[float]:
  class FeatureHumidity (line 389) | class FeatureHumidity(MIoTServiceEntity, ClimateEntity):
    method __init__ (line 393) | def __init__(self, miot_device: MIoTDevice,
    method current_humidity (line 406) | def current_humidity(self) -> Optional[float]:
  class FeatureTargetHumidity (line 412) | class FeatureTargetHumidity(MIoTServiceEntity, ClimateEntity):
    method __init__ (line 416) | def __init__(self, miot_device: MIoTDevice,
    method async_set_humidity (line 437) | async def async_set_humidity(self, humidity):
    method target_humidity (line 447) | def target_humidity(self) -> Optional[int]:
  class Heater (line 453) | class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
    method __init__ (line 457) | def __init__(self, miot_device: MIoTDevice,
    method async_set_hvac_mode (line 470) | async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
    method hvac_mode (line 477) | def hvac_mode(self) -> Optional[HVACMode]:
    method hvac_action (line 483) | def hvac_action(self) -> Optional[HVACAction]:
  class AirConditioner (line 490) | class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
    method __init__ (line 499) | def __init__(self, miot_device: MIoTDevice,
    method async_set_hvac_mode (line 547) | async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
    method hvac_mode (line 571) | def hvac_mode(self) -> Optional[HVACMode]:
    method hvac_action (line 581) | def hvac_action(self) -> Optional[HVACAction]:
    method __ac_state_changed (line 597) | def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None:
  class PtcBathHeater (line 652) | class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
    method __init__ (line 658) | def __init__(self, miot_device: MIoTDevice,
    method async_set_hvac_mode (line 684) | async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
    method hvac_mode (line 695) | def hvac_mode(self) -> Optional[HVACMode]:
  class Thermostat (line 707) | class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperat...
    method __init__ (line 711) | def __init__(self, miot_device: MIoTDevice,
    method async_set_hvac_mode (line 724) | async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
    method hvac_mode (line 731) | def hvac_mode(self) -> Optional[HVACMode]:
  class ElectricBlanket (line 737) | class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
    method __init__ (line 741) | def __init__(self, miot_device: MIoTDevice,
    method async_set_hvac_mode (line 754) | async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
    method hvac_mode (line 761) | def hvac_mode(self) -> Optional[HVACMode]:
    method hvac_action (line 767) | def hvac_action(self) -> Optional[HVACAction]:

FILE: custom_components/xiaomi_home/config_flow.py
  class XiaomiMihomeConfigFlow (line 109) | class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    method __init__ (line 155) | def __init__(self) -> None:
    method async_step_user (line 189) | async def async_step_user(
    method async_step_eula (line 235) | async def async_step_eula(
    method __show_eula_form (line 244) | async def __show_eula_form(self, reason: str):
    method async_step_auth_config (line 254) | async def async_step_auth_config(
    method __show_auth_config_form (line 282) | async def __show_auth_config_form(self, reason: str):
    method async_step_network_detect_config (line 314) | async def async_step_network_detect_config(
    method __show_network_detect_config_form (line 394) | async def __show_network_detect_config_form(self, reason: str):
    method async_step_oauth (line 424) | async def async_step_oauth(
    method __check_oauth_async (line 497) | async def __check_oauth_async(self) -> None:
    method async_step_oauth_error (line 648) | async def async_step_oauth_error(self, user_input=None):
    method async_step_homes_select (line 663) | async def async_step_homes_select(
    method __show_homes_select_form (line 718) | async def __show_homes_select_form(self, reason: str):
    method async_step_advanced_options (line 740) | async def async_step_advanced_options(
    method async_step_devices_filter (line 789) | async def async_step_devices_filter(
    method __show_devices_filter_form (line 866) | async def __show_devices_filter_form(self, reason: str):
    method config_flow_done (line 940) | async def config_flow_done(self):
    method async_get_options_flow (line 969) | def async_get_options_flow(
  class OptionsFlowHandler (line 975) | class OptionsFlowHandler(config_entries.OptionsFlow):
    method __init__ (line 1044) | def __init__(self, config_entry: config_entries.ConfigEntry):
    method async_step_init (line 1108) | async def async_step_init(self, user_input=None):
    method async_step_auth_config (line 1169) | async def async_step_auth_config(self, user_input=None):
    method async_step_oauth (line 1190) | async def async_step_oauth(self, user_input=None):
    method __check_oauth_async (line 1250) | async def __check_oauth_async(self) -> None:
    method async_step_oauth_error (line 1293) | async def async_step_oauth_error(self, user_input=None):
    method async_step_config_options (line 1308) | async def async_step_config_options(self, user_input=None):
    method async_step_update_user_info (line 1405) | async def async_step_update_user_info(self, user_input=None):
    method async_step_homes_select (line 1426) | async def async_step_homes_select(
    method __show_homes_select_form (line 1520) | async def __show_homes_select_form(self, reason: str):
    method async_step_devices_filter (line 1543) | async def async_step_devices_filter(
    method __show_devices_filter_form (line 1609) | async def __show_devices_filter_form(self, reason: str):
    method update_devices_done_async (line 1706) | async def update_devices_done_async(self):
    method async_step_update_trans_rules (line 1722) | async def async_step_update_trans_rules(self, user_input=None):
    method async_step_update_lan_ctrl_config (line 1758) | async def async_step_update_lan_ctrl_config(self, user_input=None):
    method async_step_network_detect_config (line 1821) | async def async_step_network_detect_config(
    method __show_network_detect_config_form (line 1903) | async def __show_network_detect_config_form(self, reason: str):
    method async_step_config_confirm (line 1933) | async def async_step_config_confirm(self, user_input=None):
  function _handle_oauth_webhook (line 2044) | async def _handle_oauth_webhook(hass, webhook_id, request):
  function _handle_devices_filter (line 2105) | def _handle_devices_filter(
  function _handle_network_detect_addr (line 2140) | def _handle_network_detect_addr(

FILE: custom_components/xiaomi_home/cover.py
  function async_setup_entry (line 67) | async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry,
  class Cover (line 91) | class Cover(MIoTServiceEntity, CoverEntity):
    method __init__ (line 111) | def __init__(self, miot_device: MIoTDevice,
    method _position_changed_handler (line 214) | def _position_changed_handler(self, prop: MIoTSpecProperty,
    method async_open_cover (line 220) | async def async_open_cover(self, **kwargs) -> None:
    method async_close_cover (line 231) | async def async_close_cover(self, **kwargs) -> None:
    method async_stop_cover (line 242) | async def async_stop_cover(self, **kwargs) -> None:
    method async_set_cover_position (line 249) | async def async_set_cover_position(self, **kwargs) -> None:
    method current_cover_position (line 263) | def current_cover_position(self) -> Optional[int]:
    method is_opening (line 288) | def is_opening(self) -> Optional[bool]:
    method is_closing (line 298) | def is_closing(self) -> Optional[bool]:
    method is_closed (line 308) | def is_closed(self) -> Optional[bool]:

FILE: custom_components/xiaomi_home/device_tracker.py
  function async_setup_entry (line 61) | async def async_setup_entry(
  class DeviceTracker (line 77) | class DeviceTracker(MIoTServiceEntity, TrackerEntity):
    method __init__ (line 84) | def __init__(self, miot_device: MIoTDevice,
    method battery_level (line 104) | def battery_level(self) -> Optional[int]:
    method latitude (line 111) | def latitude(self) -> Optional[float]:
    method longitude (line 117) | def longitude(self) -> Optional[float]:
    method location_name (line 123) | def location_name(self) -> Optional[str]:

FILE: custom_components/xiaomi_home/event.py
  function async_setup_entry (line 64) | async def async_setup_entry(
  class Event (line 82) | class Event(MIoTEventEntity, EventEntity):
    method __init__ (line 85) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecEvent) -> None:
    method on_event_occurred (line 91) | def on_event_occurred(

FILE: custom_components/xiaomi_home/fan.py
  function async_setup_entry (line 75) | async def async_setup_entry(
  class Fan (line 92) | class Fan(MIoTServiceEntity, FanEntity):
    method __init__ (line 110) | def __init__(
    method async_turn_on (line 195) | async def async_turn_on(
    method async_turn_off (line 228) | async def async_turn_off(self, **kwargs: Any) -> None:
    method async_toggle (line 232) | async def async_toggle(self, **kwargs: Any) -> None:
    method async_set_percentage (line 236) | async def async_set_percentage(self, percentage: int) -> None:
    method async_set_preset_mode (line 258) | async def async_set_preset_mode(self, preset_mode: str) -> None:
    method async_set_direction (line 265) | async def async_set_direction(self, direction: str) -> None:
    method async_oscillate (line 276) | async def async_oscillate(self, oscillating: bool) -> None:
    method is_on (line 282) | def is_on(self) -> Optional[bool]:
    method preset_mode (line 288) | def preset_mode(self) -> Optional[str]:
    method current_direction (line 298) | def current_direction(self) -> Optional[str]:
    method percentage (line 307) | def percentage(self) -> Optional[int]:
    method oscillating (line 321) | def oscillating(self) -> Optional[bool]:

FILE: custom_components/xiaomi_home/humidifier.py
  function async_setup_entry (line 67) | async def async_setup_entry(
  class Humidifier (line 91) | class Humidifier(MIoTServiceEntity, HumidifierEntity):
    method __init__ (line 101) | def __init__(self, miot_device: MIoTDevice,
    method async_turn_on (line 141) | async def async_turn_on(self, **kwargs):
    method async_turn_off (line 145) | async def async_turn_off(self, **kwargs):
    method async_set_humidity (line 149) | async def async_set_humidity(self, humidity: int) -> None:
    method async_set_mode (line 156) | async def async_set_mode(self, mode: str) -> None:
    method is_on (line 163) | def is_on(self) -> Optional[bool]:
    method action (line 168) | def action(self) -> Optional[HumidifierAction]:
    method current_humidity (line 177) | def current_humidity(self) -> Optional[int]:
    method target_humidity (line 183) | def target_humidity(self) -> Optional[int]:
    method mode (line 189) | def mode(self) -> Optional[str]:

FILE: custom_components/xiaomi_home/light.py
  function async_setup_entry (line 76) | async def async_setup_entry(
  class Light (line 95) | class Light(MIoTServiceEntity, LightEntity):
    method __init__ (line 108) | def __init__(
    method is_on (line 209) | def is_on(self) -> Optional[bool]:
    method brightness (line 218) | def brightness(self) -> Optional[int]:
    method color_temp_kelvin (line 226) | def color_temp_kelvin(self) -> Optional[int]:
    method rgb_color (line 231) | def rgb_color(self) -> Optional[tuple[int, int, int]]:
    method effect (line 242) | def effect(self) -> Optional[str]:
    method async_turn_on (line 248) | async def async_turn_on(self, **kwargs) -> None:
    method async_turn_off (line 292) | async def async_turn_off(self, **kwargs) -> None:

FILE: custom_components/xiaomi_home/media_player.py
  function async_setup_entry (line 67) | async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry,
  class FeatureVolumeMute (line 86) | class FeatureVolumeMute(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 90) | def __init__(self, miot_device: MIoTDevice,
    method is_volume_muted (line 104) | def is_volume_muted(self) -> Optional[bool]:
    method async_mute_volume (line 109) | async def async_mute_volume(self, mute: bool) -> None:
  class FeatureVolumeSet (line 114) | class FeatureVolumeSet(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 121) | def __init__(self, miot_device: MIoTDevice,
    method async_set_volume_level (line 148) | async def async_set_volume_level(self, volume: float) -> None:
    method volume_level (line 158) | def volume_level(self) -> Optional[float]:
  class FeaturePlay (line 167) | class FeaturePlay(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 171) | def __init__(self, miot_device: MIoTDevice,
    method async_media_play (line 183) | async def async_media_play(self) -> None:
  class FeaturePause (line 188) | class FeaturePause(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 192) | def __init__(self, miot_device: MIoTDevice,
    method async_media_pause (line 205) | async def async_media_pause(self) -> None:
  class FeatureStop (line 210) | class FeatureStop(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 214) | def __init__(self, miot_device: MIoTDevice,
    method async_media_stop (line 226) | async def async_media_stop(self) -> None:
  class FeatureNextTrack (line 231) | class FeatureNextTrack(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 235) | def __init__(self, miot_device: MIoTDevice,
    method async_media_next_track (line 248) | async def async_media_next_track(self) -> None:
  class FeaturePreviousTrack (line 253) | class FeaturePreviousTrack(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 257) | def __init__(self, miot_device: MIoTDevice,
    method async_media_previous_track (line 270) | async def async_media_previous_track(self) -> None:
  class FeatureSoundMode (line 275) | class FeatureSoundMode(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 280) | def __init__(self, miot_device: MIoTDevice,
    method async_select_sound_mode (line 300) | async def async_select_sound_mode(self, sound_mode: str):
    method sound_mode (line 308) | def sound_mode(self) -> Optional[str]:
  class FeatureTurnOn (line 316) | class FeatureTurnOn(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 320) | def __init__(self, miot_device: MIoTDevice,
    method async_turn_on (line 333) | async def async_turn_on(self) -> None:
  class FeatureTurnOff (line 338) | class FeatureTurnOff(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 342) | def __init__(self, miot_device: MIoTDevice,
    method async_turn_off (line 355) | async def async_turn_off(self) -> None:
  class FeatureSource (line 360) | class FeatureSource(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 365) | def __init__(self, miot_device: MIoTDevice,
    method async_select_source (line 385) | async def async_select_source(self, source: str) -> None:
    method source (line 393) | def source(self) -> Optional[str]:
  class FeatureState (line 401) | class FeatureState(MIoTServiceEntity, MediaPlayerEntity):
    method __init__ (line 406) | def __init__(self, miot_device: MIoTDevice,
    method state (line 436) | def state(self) -> Optional[MediaPlayerState]:
  class WifiSpeaker (line 445) | class WifiSpeaker(FeatureVolumeSet, FeatureVolumeMute, FeaturePlay,
    method __init__ (line 450) | def __init__(self, miot_device: MIoTDevice,
  class Television (line 459) | class Television(FeatureVolumeSet, FeatureVolumeMute, FeaturePlay, Featu...
    method __init__ (line 465) | def __init__(self, miot_device: MIoTDevice,

FILE: custom_components/xiaomi_home/miot/common.py
  function gen_absolute_path (line 63) | def gen_absolute_path(relative_path: str) -> str:
  function calc_group_id (line 68) | def calc_group_id(uid: str, home_id: str) -> str:
  function load_json_file (line 74) | def load_json_file(json_file: str) -> dict:
  function load_yaml_file (line 80) | def load_yaml_file(yaml_file: str) -> dict:
  function randomize_int (line 86) | def randomize_int(value: int, ratio: float) -> int:
  function randomize_float (line 91) | def randomize_float(value: float, ratio: float) -> float:
  function slugify_name (line 96) | def slugify_name(name: str, separator: str = '_') -> str:
  function slugify_did (line 101) | def slugify_did(cloud_server: str, did: str) -> str:
  class MIoTMatcher (line 106) | class MIoTMatcher(MQTTMatcher):
    method iter_all_nodes (line 109) | def iter_all_nodes(self) -> Any:
    method get (line 119) | def get(self, topic: str) -> Optional[Any]:
  class MIoTHttp (line 126) | class MIoTHttp:
    method get (line 129) | def get(
    method get_json (line 143) | def get_json(
    method post (line 150) | def post(
    method post_json (line 156) | def post_json(
    method get_async (line 163) | async def get_async(
    method get_json_async (line 173) | async def get_json_async(
    method post_async (line 182) | async def post_async(

FILE: custom_components/xiaomi_home/miot/miot_client.py
  class MIoTClientSub (line 89) | class MIoTClientSub:
    method __str__ (line 95) | def __str__(self) -> str:
  class CtrlMode (line 99) | class CtrlMode(Enum):
    method load (line 105) | def load(mode: str) -> 'CtrlMode':
  class MIoTClient (line 113) | class MIoTClient:
    method __init__ (line 184) | def __init__(
    method init_async (line 255) | async def init_async(self) -> None:
    method deinit_async (line 375) | async def deinit_async(self) -> None:
    method main_loop (line 443) | def main_loop(self) -> asyncio.AbstractEventLoop:
    method miot_network (line 447) | def miot_network(self) -> MIoTNetwork:
    method miot_storage (line 451) | def miot_storage(self) -> MIoTStorage:
    method mips_service (line 455) | def mips_service(self) -> MipsService:
    method miot_oauth (line 459) | def miot_oauth(self) -> MIoTOauthClient:
    method miot_http (line 463) | def miot_http(self) -> MIoTHttpClient:
    method miot_i18n (line 467) | def miot_i18n(self) -> MIoTI18n:
    method miot_lan (line 471) | def miot_lan(self) -> MIoTLan:
    method user_config (line 475) | def user_config(self) -> dict:
    method area_name_rule (line 479) | def area_name_rule(self) -> Optional[str]:
    method cloud_server (line 483) | def cloud_server(self) -> str:
    method action_debug (line 487) | def action_debug(self) -> bool:
    method hide_non_standard_entities (line 491) | def hide_non_standard_entities(self) -> bool:
    method display_devices_changed_notify (line 496) | def display_devices_changed_notify(self) -> list[str]:
    method display_binary_text (line 500) | def display_binary_text(self) -> bool:
    method display_binary_bool (line 504) | def display_binary_bool(self) -> bool:
    method cover_dead_zone_width (line 508) | def cover_dead_zone_width(self) -> int:
    method display_devices_changed_notify (line 513) | def display_devices_changed_notify(self, value: list[str]) -> None:
    method device_list (line 524) | def device_list(self) -> dict:
    method persistent_notify (line 528) | def persistent_notify(self) -> Callable:
    method persistent_notify (line 532) | def persistent_notify(self, func) -> None:
    method refresh_oauth_info_async (line 536) | async def refresh_oauth_info_async(self) -> bool:
    method refresh_user_cert_async (line 594) | async def refresh_user_cert_async(self) -> bool:
    method set_prop_async (line 638) | async def set_prop_async(
    method request_refresh_prop (line 713) | def request_refresh_prop(
    method get_prop_async (line 729) | async def get_prop_async(self, did: str, siid: int, piid: int) -> Any:
    method action_async (line 770) | async def action_async(
    method sub_prop (line 833) | def sub_prop(
    method unsub_prop (line 849) | def unsub_prop(
    method sub_event (line 860) | def sub_event(
    method unsub_event (line 875) | def unsub_event(
    method sub_device_state (line 886) | def sub_device_state(
    method unsub_device_state (line 898) | def unsub_device_state(self, did: str) -> bool:
    method remove_device_async (line 903) | async def remove_device_async(self, did: str) -> None:
    method remove_device2_async (line 918) | async def remove_device2_async(self, did_tag: str) -> None:
    method __get_exec_error_with_rc (line 925) | def __get_exec_error_with_rc(self, rc: int) -> str:
    method __gen_notify_key (line 936) | def __gen_notify_key(self, name: str) -> str:
    method __request_refresh_auth_info (line 940) | def __request_refresh_auth_info(self, delay_sec: int) -> None:
    method __request_refresh_user_cert (line 949) | def __request_refresh_user_cert(self, delay_sec: int) -> None:
    method __unsub_from (line 958) | def __unsub_from(self, sub_from: str, did: str) -> None:
    method __sub_from (line 978) | def __sub_from(self, sub_from: str, did: str) -> None:
    method __update_device_msg_sub (line 991) | def __update_device_msg_sub(self, did: str) -> None:
    method __on_network_status_changed (line 1029) | async def __on_network_status_changed(self, status: bool) -> None:
    method __on_mips_service_state_change (line 1049) | async def __on_mips_service_state_change(
    method __on_mips_cloud_state_changed (line 1091) | async def __on_mips_cloud_state_changed(
    method __on_mips_local_state_changed (line 1129) | async def __on_mips_local_state_changed(
    method __on_miot_lan_state_change (line 1175) | async def __on_miot_lan_state_change(self, state: bool) -> None:
    method __on_cloud_device_state_changed (line 1223) | def __on_cloud_device_state_changed(
    method __on_gw_device_list_changed (line 1254) | async def __on_gw_device_list_changed(
    method __on_lan_device_state_changed (line 1281) | async def __on_lan_device_state_changed(
    method __on_prop_msg (line 1318) | def __on_prop_msg(self, params: dict, ctx: Any) -> None:
    method __on_event_msg (line 1330) | def __on_event_msg(self, params: dict, ctx: Any) -> None:
    method __check_device_state (line 1340) | def __check_device_state(
    method __load_cache_device_async (line 1351) | async def __load_cache_device_async(self) -> None:
    method __update_devices_from_cloud_async (line 1384) | async def __update_devices_from_cloud_async(
    method __refresh_cloud_devices_async (line 1447) | async def __refresh_cloud_devices_async(self) -> None:
    method __refresh_cloud_device_with_dids_async (line 1492) | async def __refresh_cloud_device_with_dids_async(
    method __request_refresh_cloud_devices (line 1504) | def __request_refresh_cloud_devices(self, immediately=False) -> None:
    method __update_devices_from_gw_async (line 1516) | async def __update_devices_from_gw_async(
    method __refresh_gw_devices_with_group_id_async (line 1588) | async def __refresh_gw_devices_with_group_id_async(
    method __request_refresh_gw_devices_by_group_id (line 1626) | def __request_refresh_gw_devices_by_group_id(
    method __refresh_props_from_cloud (line 1651) | async def __refresh_props_from_cloud(self, patch_len: int = 150) -> bool:
    method __refresh_props_from_gw (line 1696) | async def __refresh_props_from_gw(self) -> bool:
    method __refresh_props_from_lan (line 1745) | async def __refresh_props_from_lan(self) -> bool:
    method __refresh_props_handler (line 1788) | async def __refresh_props_handler(self) -> None:
    method __show_client_error_notify (line 1823) | def __show_client_error_notify(
    method __show_devices_changed_notify (line 1846) | def __show_devices_changed_notify(self) -> None:
    method __request_show_devices_changed_notify (line 1951) | def __request_show_devices_changed_notify(
    method __show_central_state_changed_notify (line 1964) | def __show_central_state_changed_notify(self, connected: bool) -> None:
  function get_miot_instance_async (line 1982) | async def get_miot_instance_async(

FILE: custom_components/xiaomi_home/miot/miot_cloud.py
  class MIoTOauthClient (line 73) | class MIoTOauthClient:
    method __init__ (line 83) | def __init__(
    method state (line 109) | def state(self) -> str:
    method deinit_async (line 112) | async def deinit_async(self) -> None:
    method set_redirect_url (line 116) | def set_redirect_url(self, redirect_url: str) -> None:
    method gen_auth_url (line 121) | def gen_auth_url(
    method __get_token_async (line 160) | async def __get_token_async(self, data) -> dict:
    method get_access_token_async (line 193) | async def get_access_token_async(self, code: str) -> dict:
    method refresh_access_token_async (line 212) | async def refresh_access_token_async(self, refresh_token: str) -> dict:
  class MIoTHttpClient (line 231) | class MIoTHttpClient:
    method __init__ (line 246) | def __init__(
    method deinit_async (line 272) | async def deinit_async(self) -> None:
    method update_http_header (line 284) | def update_http_header(
    method __api_request_headers (line 299) | def __api_request_headers(self) -> dict:
    method __mihome_api_get_async (line 309) | async def __mihome_api_get_async(
    method __mihome_api_post_async (line 337) | async def __mihome_api_post_async(
    method get_user_info_async (line 365) | async def get_user_info_async(self) -> dict:
    method get_central_cert_async (line 386) | async def get_central_cert_async(self, csr: str) -> str:
    method __get_dev_room_page_async (line 404) | async def __get_dev_room_page_async(
    method get_separated_shared_devices_async (line 448) | async def get_separated_shared_devices_async(self) -> dict[str, dict]:
    method get_homeinfos_async (line 459) | async def get_homeinfos_async(self) -> dict:
    method get_uid_async (line 537) | async def get_uid_async(self) -> str:
    method __get_device_list_page_async (line 540) | async def __get_device_list_page_async(
    method get_devices_with_dids_async (line 614) | async def get_devices_with_dids_async(
    method get_devices_async (line 627) | async def get_devices_async(
    method get_props_async (line 715) | async def get_props_async(self, params: list) -> list:
    method __get_prop_async (line 731) | async def __get_prop_async(self, did: str, siid: int, piid: int) -> Any:
    method __get_prop_handler (line 741) | async def __get_prop_handler(self) -> bool:
    method get_prop_async (line 790) | async def get_prop_async(
    method set_prop_async (line 812) | async def set_prop_async(self, params: list) -> list:
    method action_async (line 828) | async def action_async(

FILE: custom_components/xiaomi_home/miot/miot_device.py
  class MIoTEntityData (line 111) | class MIoTEntityData:
    method __init__ (line 121) | def __init__(
  class MIoTDevice (line 132) | class MIoTDevice:
    method __init__ (line 165) | def __init__(
    method online (line 225) | def online(self) -> bool:
    method entity_list (line 229) | def entity_list(self) -> dict[str, list[MIoTEntityData]]:
    method prop_list (line 233) | def prop_list(self) -> dict[str, list[MIoTSpecProperty]]:
    method event_list (line 237) | def event_list(self) -> dict[str, list[MIoTSpecEvent]]:
    method action_list (line 241) | def action_list(self) -> dict[str, list[MIoTSpecAction]]:
    method action_async (line 244) | async def action_async(self, siid: int, aiid: int, in_list: list) -> l...
    method sub_device_state (line 248) | def sub_device_state(
    method unsub_device_state (line 258) | def unsub_device_state(self, key: str, sub_id: int) -> None:
    method sub_property (line 265) | def sub_property(
    method unsub_property (line 283) | def unsub_property(self, siid: int, piid: int, sub_id: int) -> None:
    method sub_event (line 293) | def sub_event(
    method unsub_event (line 311) | def unsub_event(self, siid: int, eiid: int, sub_id: int) -> None:
    method device_info (line 322) | def device_info(self) -> DeviceInfo:
    method did (line 337) | def did(self) -> str:
    method did_tag (line 342) | def did_tag(self) -> str:
    method gen_device_entity_id (line 346) | def gen_device_entity_id(self, ha_domain: str) -> str:
    method gen_service_entity_id (line 351) | def gen_service_entity_id(self, ha_domain: str, siid: int,
    method gen_prop_entity_id (line 357) | def gen_prop_entity_id(
    method gen_event_entity_id (line 365) | def gen_event_entity_id(
    method gen_action_entity_id (line 373) | def gen_action_entity_id(
    method name (line 382) | def name(self) -> str:
    method model (line 386) | def model(self) -> str:
    method icon (line 390) | def icon(self) -> str:
    method append_entity (line 393) | def append_entity(self, entity_data: MIoTEntityData) -> None:
    method append_prop (line 397) | def append_prop(self, prop: MIoTSpecProperty) -> None:
    method append_event (line 403) | def append_event(self, event: MIoTSpecEvent) -> None:
    method append_action (line 409) | def append_action(self, action: MIoTSpecAction) -> None:
    method parse_miot_device_entity (line 415) | def parse_miot_device_entity(
    method parse_miot_service_entity (line 532) | def parse_miot_service_entity(
    method parse_miot_property_entity (line 581) | def parse_miot_property_entity(self, miot_prop: MIoTSpecProperty) -> b...
    method spec_transform (line 622) | def spec_transform(self) -> None:
    method unit_convert (line 686) | def unit_convert(self, spec_unit: str) -> Optional[str]:
    method icon_convert (line 828) | def icon_convert(self, spec_unit: str) -> Optional[str]:
    method __gen_sub_id (line 887) | def __gen_sub_id(self) -> int:
    method __on_device_state_changed (line 891) | def __on_device_state_changed(
  class MIoTServiceEntity (line 901) | class MIoTServiceEntity(Entity):
    method __init__ (line 920) | def __init__(
    method event_occurred_handler (line 962) | def event_occurred_handler(
    method event_occurred_handler (line 968) | def event_occurred_handler(self, func) -> None:
    method sub_prop_changed (line 971) | def sub_prop_changed(
    method unsub_prop_changed (line 981) | def unsub_prop_changed(self, prop: MIoTSpecProperty) -> None:
    method device_info (line 985) | def device_info(self) -> Optional[DeviceInfo]:
    method async_added_to_hass (line 988) | async def async_added_to_hass(self) -> None:
    method async_will_remove_from_hass (line 1013) | async def async_will_remove_from_hass(self) -> None:
    method get_map_value (line 1039) | def get_map_value(
    method get_map_key (line 1046) | def get_map_key(
    method get_prop_value (line 1056) | def get_prop_value(self, prop: Optional[MIoTSpecProperty]) -> Any:
    method set_prop_value (line 1064) | def set_prop_value(
    method set_property_async (line 1074) | async def set_property_async(
    method get_property_async (line 1105) | async def get_property_async(self, prop: MIoTSpecProperty) -> Any:
    method action_async (line 1131) | async def action_async(
    method __on_properties_changed (line 1146) | def __on_properties_changed(self, params: dict, ctx: Any) -> None:
    method __on_event_occurred (line 1164) | def __on_event_occurred(self, params: dict, ctx: Any) -> None:
    method __on_device_state_changed (line 1183) | def __on_device_state_changed(
    method __refresh_props_value (line 1195) | def __refresh_props_value(self) -> None:
    method __write_ha_state_handler (line 1206) | def __write_ha_state_handler(self) -> None:
  class MIoTPropertyEntity (line 1211) | class MIoTPropertyEntity(Entity):
    method __init__ (line 1229) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) ->...
    method device_info (line 1261) | def device_info(self) -> Optional[DeviceInfo]:
    method async_added_to_hass (line 1264) | async def async_added_to_hass(self) -> None:
    method async_will_remove_from_hass (line 1277) | async def async_will_remove_from_hass(self) -> None:
    method get_vlist_description (line 1288) | def get_vlist_description(self, value: Any) -> Optional[str]:
    method get_vlist_value (line 1293) | def get_vlist_value(self, description: str) -> Any:
    method set_property_async (line 1298) | async def set_property_async(self, value: Any) -> bool:
    method get_property_async (line 1316) | async def get_property_async(self) -> Any:
    method __on_value_changed (line 1330) | def __on_value_changed(self, params: dict, ctx: Any) -> None:
    method __on_device_state_changed (line 1338) | def __on_device_state_changed(
    method __request_refresh_prop (line 1348) | def __request_refresh_prop(self) -> None:
    method __write_ha_state_handler (line 1358) | def __write_ha_state_handler(self) -> None:
  class MIoTEventEntity (line 1363) | class MIoTEventEntity(Entity):
    method __init__ (line 1377) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecEvent) -> None:
    method device_info (line 1410) | def device_info(self) -> Optional[DeviceInfo]:
    method async_added_to_hass (line 1413) | async def async_added_to_hass(self) -> None:
    method async_will_remove_from_hass (line 1423) | async def async_will_remove_from_hass(self) -> None:
    method on_event_occurred (line 1432) | def on_event_occurred(
    method __on_event_occurred (line 1436) | def __on_event_occurred(self, params: dict, ctx: Any) -> None:
    method __on_device_state_changed (line 1464) | def __on_device_state_changed(
  class MIoTActionEntity (line 1474) | class MIoTActionEntity(Entity):
    method __init__ (line 1487) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecAction) -> N...
    method device_info (line 1514) | def device_info(self) -> Optional[DeviceInfo]:
    method async_added_to_hass (line 1517) | async def async_added_to_hass(self) -> None:
    method async_will_remove_from_hass (line 1522) | async def async_will_remove_from_hass(self) -> None:
    method action_async (line 1527) | async def action_async(
    method __on_device_state_changed (line 1539) | def __on_device_state_changed(

FILE: custom_components/xiaomi_home/miot/miot_error.py
  class MIoTErrorCode (line 52) | class MIoTErrorCode(Enum):
  class MIoTError (line 82) | class MIoTError(Exception):
    method __init__ (line 87) | def __init__(
    method to_str (line 94) | def to_str(self) -> str:
    method to_dict (line 97) | def to_dict(self) -> dict:
  class MIoTOauthError (line 101) | class MIoTOauthError(MIoTError):
  class MIoTHttpError (line 105) | class MIoTHttpError(MIoTError):
  class MIoTMipsError (line 109) | class MIoTMipsError(MIoTError):
  class MIoTDeviceError (line 113) | class MIoTDeviceError(MIoTError):
  class MIoTSpecError (line 117) | class MIoTSpecError(MIoTError):
  class MIoTStorageError (line 121) | class MIoTStorageError(MIoTError):
  class MIoTCertError (line 125) | class MIoTCertError(MIoTError):
  class MIoTClientError (line 129) | class MIoTClientError(MIoTError):
  class MIoTEvError (line 133) | class MIoTEvError(MIoTError):
  class MipsServiceError (line 137) | class MipsServiceError(MIoTError):
  class MIoTConfigError (line 141) | class MIoTConfigError(MIoTError):
  class MIoTOptionsError (line 145) | class MIoTOptionsError(MIoTError):
  class MIoTLanError (line 149) | class MIoTLanError(MIoTError):

FILE: custom_components/xiaomi_home/miot/miot_i18n.py
  class MIoTI18n (line 59) | class MIoTI18n:
    method __init__ (line 69) | def __init__(
    method init_async (line 76) | async def init_async(self) -> None:
    method deinit_async (line 96) | async def deinit_async(self) -> None:
    method translate (line 99) | def translate(

FILE: custom_components/xiaomi_home/miot/miot_lan.py
  class _MIoTLanGetDevListData (line 79) | class _MIoTLanGetDevListData:
  class _MIoTLanUnregisterBroadcastData (line 86) | class _MIoTLanUnregisterBroadcastData:
  class _MIoTLanRegisterBroadcastData (line 91) | class _MIoTLanRegisterBroadcastData:
  class _MIoTLanUnsubDeviceData (line 98) | class _MIoTLanUnsubDeviceData:
  class _MIoTLanSubDeviceData (line 103) | class _MIoTLanSubDeviceData:
  class _MIoTLanNetworkUpdateData (line 110) | class _MIoTLanNetworkUpdateData:
  class _MIoTLanRequestData (line 116) | class _MIoTLanRequestData:
  class _MIoTLanDeviceState (line 123) | class _MIoTLanDeviceState(Enum):
  class _MIoTLanDevice (line 131) | class _MIoTLanDevice:
    method __init__ (line 167) | def __init__(
    method keep_alive (line 201) | def keep_alive(self, ip: str, if_name: str) -> None:
    method online (line 210) | def online(self) -> bool:
    method online (line 214) | def online(self, online: bool) -> None:
    method if_name (line 223) | def if_name(self) -> Optional[str]:
    method gen_packet (line 226) | def gen_packet(
    method decrypt_packet (line 245) | def decrypt_packet(self, encrypted_data: bytearray) -> dict:
    method subscribe (line 262) | def subscribe(self) -> None:
    method unsubscribe (line 287) | def unsubscribe(self) -> None:
    method on_delete (line 308) | def on_delete(self) -> None:
    method update_info (line 317) | def update_info(self, info: dict) -> None:
    method __subscribe_handler (line 332) | def __subscribe_handler(self, msg: dict, sub_ts: int) -> None:
    method __unsubscribe_handler (line 347) | def __unsubscribe_handler(self, msg: dict, ctx: Any) -> None:
    method __update_keep_alive (line 357) | def __update_keep_alive(self, state: _MIoTLanDeviceState) -> None:
    method __get_next_ka_timeout (line 398) | def __get_next_ka_timeout(self) -> float:
    method __change_online (line 402) | def __change_online(self, online: bool) -> None:
    method __online_resume_handler (line 428) | def __online_resume_handler(self) -> None:
    method __md5 (line 432) | def __md5(self, data: bytes) -> bytes:
  class MIoTLan (line 438) | class MIoTLan:
    method __init__ (line 488) | def __init__(
    method __assert_service_ready (line 552) | def __assert_service_ready(self) -> None:
    method virtual_did (line 559) | def virtual_did(self) -> str:
    method internal_loop (line 563) | def internal_loop(self) -> asyncio.AbstractEventLoop:
    method init_done (line 567) | def init_done(self) -> bool:
    method init_async (line 570) | async def init_async(self) -> None:
    method __internal_loop_thread (line 612) | def __internal_loop_thread(self) -> None:
    method deinit_async (line 620) | async def deinit_async(self) -> None:
    method update_net_ifs_async (line 644) | async def update_net_ifs_async(self, net_ifs: list[str]) -> None:
    method vote_for_lan_ctrl_async (line 671) | async def vote_for_lan_ctrl_async(self, key: str, vote: bool) -> None:
    method update_subscribe_option (line 679) | async def update_subscribe_option(self, enable_subscribe: bool) -> None:
    method update_devices (line 688) | def update_devices(self, devices: dict[str, dict]) -> bool:
    method delete_devices (line 696) | def delete_devices(self, devices: list[str]) -> bool:
    method sub_lan_state (line 704) | def sub_lan_state(
    method unsub_lan_state (line 709) | def unsub_lan_state(self, key: str) -> None:
    method sub_device_state (line 713) | def sub_device_state(
    method unsub_device_state (line 726) | def unsub_device_state(self, key: str) -> bool:
    method sub_prop (line 734) | def sub_prop(
    method unsub_prop (line 756) | def unsub_prop(
    method sub_event (line 775) | def sub_event(
    method unsub_event (line 797) | def unsub_event(
    method get_prop_async (line 816) | async def get_prop_async(
    method set_prop_async (line 836) | async def set_prop_async(
    method action_async (line 861) | async def action_async(
    method get_dev_list_async (line 880) | async def get_dev_list_async(
    method __call_api_async (line 899) | async def __call_api_async(
    method __on_network_info_change_external_async (line 911) | async def __on_network_info_change_external_async(
    method __on_mips_service_change (line 938) | async def __on_mips_service_change(
    method ping (line 952) | def ping(self, if_name: Optional[str], target_ip: str) -> None:
    method send2device (line 959) | def send2device(
    method __make_request (line 993) | def __make_request(
    method broadcast_device_state (line 1024) | def broadcast_device_state(self, did: str, state: dict) -> None:
    method __gen_msg_id (line 1030) | def __gen_msg_id(self) -> int:
    method __call_api (line 1038) | def __call_api(
    method __sub_device_state (line 1060) | def __sub_device_state(self, data: _MIoTLanSubDeviceData) -> None:
    method __unsub_device_state (line 1063) | def __unsub_device_state(self, data: _MIoTLanUnsubDeviceData) -> None:
    method __sub_broadcast (line 1066) | def __sub_broadcast(self, data: _MIoTLanRegisterBroadcastData) -> None:
    method __unsub_broadcast (line 1070) | def __unsub_broadcast(self, data: _MIoTLanUnregisterBroadcastData) -> ...
    method __get_dev_list (line 1075) | def __get_dev_list(self, data: _MIoTLanGetDevListData) -> None:
    method __update_devices (line 1086) | def __update_devices(self, devices: dict[str, dict]) -> None:
    method __delete_devices (line 1116) | def __delete_devices(self, devices: list[str]) -> None:
    method __on_network_info_change (line 1123) | def __on_network_info_change(self, data: _MIoTLanNetworkUpdateData) ->...
    method __update_net_ifs (line 1132) | def __update_net_ifs(self, net_ifs: list[str]) -> None:
    method __update_subscribe_option (line 1141) | def __update_subscribe_option(self, options: dict) -> None:
    method __deinit (line 1150) | def __deinit(self) -> None:
    method __init_socket (line 1170) | def __init_socket(self) -> None:
    method __create_socket (line 1177) | def __create_socket(self, if_name: str) -> None:
    method __deinit_socket (line 1200) | def __deinit_socket(self) -> None:
    method __destroy_socket (line 1205) | def __destroy_socket(self, if_name: str) -> None:
    method __socket_read_handler (line 1213) | def __socket_read_handler(self, ctx: tuple[str, socket.socket]) -> None:
    method __raw_message_handler (line 1229) | def __raw_message_handler(
    method __message_handler (line 1271) | def __message_handler(self, did: str, msg: dict) -> None:
    method __filter_dup_message (line 1327) | def __filter_dup_message(self, did: str, msg_id: int) -> bool:
    method __sendto (line 1337) | def __sendto(
    method __scan_devices (line 1353) | def __scan_devices(self) -> None:
    method __get_next_scan_time (line 1369) | def __get_next_scan_time(self) -> float:

FILE: custom_components/xiaomi_home/miot/miot_mdns.py
  class MipsServiceState (line 76) | class MipsServiceState(Enum):
  class MipsServiceData (line 82) | class MipsServiceData:
    method __init__ (line 98) | def __init__(self, service_info: AsyncServiceInfo) -> None:
    method valid_service (line 125) | def valid_service(self) -> bool:
    method to_dict (line 130) | def to_dict(self) -> dict:
    method __str__ (line 143) | def __str__(self) -> str:
  class MipsService (line 147) | class MipsService:
    method __init__ (line 157) | def __init__(
    method init_async (line 167) | async def init_async(self) -> None:
    method deinit_async (line 176) | async def deinit_async(self) -> None:
    method get_services (line 181) | def get_services(self, group_id: Optional[str] = None) -> dict[str, di...
    method sub_service_change (line 208) | def sub_service_change(
    method unsub_service_change (line 216) | def unsub_service_change(self, key: str) -> None:
    method __on_service_state_change (line 223) | def __on_service_state_change(
    method __request_service_info_async (line 243) | async def __request_service_info_async(
    method __call_service_change (line 278) | def __call_service_change(

FILE: custom_components/xiaomi_home/miot/miot_mips.py
  class _MipsMsgTypeOptions (line 81) | class _MipsMsgTypeOptions(Enum):
  class _MipsMessage (line 90) | class _MipsMessage:
    method unpack (line 98) | def unpack(data: bytes) -> '_MipsMessage':
    method pack (line 127) | def pack(
    method __str__ (line 157) | def __str__(self) -> str:
  class _MipsRequest (line 162) | class _MipsRequest:
  class _MipsBroadcast (line 171) | class _MipsBroadcast:
    method __str__ (line 182) | def __str__(self) -> str:
  class _MipsState (line 187) | class _MipsState:
  class MIoTDeviceState (line 197) | class MIoTDeviceState(Enum):
  class MipsDeviceState (line 205) | class MipsDeviceState:
  class _MipsClient (line 217) | class _MipsClient(ABC):
    method __init__ (line 256) | def __init__(
    method client_id (line 300) | def client_id(self) -> str:
    method host (line 304) | def host(self) -> str:
    method port (line 308) | def port(self) -> int:
    method mips_state (line 313) | def mips_state(self) -> bool:
    method connect (line 323) | def connect(self, thread_name: Optional[str] = None) -> None:
    method connect_async (line 335) | async def connect_async(self) -> None:
    method disconnect (line 340) | def disconnect(self) -> None:
    method disconnect_async (line 349) | async def disconnect_async(self) -> None:
    method deinit (line 355) | def deinit(self) -> None:
    method deinit_async (line 371) | async def deinit_async(self) -> None:
    method update_mqtt_password (line 386) | def update_mqtt_password(self, password: str) -> None:
    method log_debug (line 392) | def log_debug(self, msg, *args, **kwargs) -> None:
    method log_info (line 396) | def log_info(self, msg, *args, **kwargs) -> None:
    method log_error (line 400) | def log_error(self, msg, *args, **kwargs) -> None:
    method enable_logger (line 404) | def enable_logger(self, logger: Optional[logging.Logger] = None) -> None:
    method enable_mqtt_logger (line 407) | def enable_mqtt_logger(
    method sub_mips_state (line 418) | def sub_mips_state(
    method unsub_mips_state (line 435) | def unsub_mips_state(self, key: str) -> bool:
    method sub_prop (line 445) | def sub_prop(
    method unsub_prop (line 455) | def unsub_prop(
    method sub_event (line 463) | def sub_event(
    method unsub_event (line 473) | def unsub_event(
    method get_dev_list_async (line 481) | async def get_dev_list_async(
    method get_prop_async (line 488) | async def get_prop_async(
    method set_prop_async (line 493) | async def set_prop_async(
    method action_async (line 499) | async def action_async(
    method _on_mips_message (line 505) | def _on_mips_message(self, topic: str, payload: bytes) -> None: ...
    method _on_mips_connect (line 508) | def _on_mips_connect(self, rc: int, props: dict) -> None: ...
    method _on_mips_disconnect (line 511) | def _on_mips_disconnect(self, rc: int, props: dict) -> None: ...
    method _mips_sub_internal (line 514) | def _mips_sub_internal(self, topic: str) -> None:
    method _mips_unsub_internal (line 530) | def _mips_unsub_internal(self, topic: str) -> None:
    method _mips_publish_internal (line 551) | def _mips_publish_internal(
    method __thread_check (line 574) | def __thread_check(self) -> None:
    method __mqtt_read_handler (line 578) | def __mqtt_read_handler(self) -> None:
    method __mqtt_write_handler (line 581) | def __mqtt_write_handler(self) -> None:
    method __mqtt_timer_handler (line 585) | def __mqtt_timer_handler(self) -> None:
    method __mqtt_loop_handler (line 591) | def __mqtt_loop_handler(self) -> None:
    method __mips_loop_thread (line 614) | def __mips_loop_thread(self) -> None:
    method __on_connect (line 647) | def __on_connect(self, client, user_data, flags, rc, props) -> None:
    method __on_connect_failed (line 672) | def __on_connect_failed(self, client: Client, user_data: Any) -> None:
    method __on_disconnect (line 677) | def __on_disconnect(self,  client, user_data, rc, props) -> None:
    method __on_message (line 713) | def __on_message(
    method __mips_sub_internal_pending_handler (line 721) | def __mips_sub_internal_pending_handler(self, ctx: Any) -> None:
    method __mips_connect (line 757) | def __mips_connect(self) -> None:
    method __mips_try_reconnect (line 801) | def __mips_try_reconnect(self, immediately: bool = False) -> None:
    method __mips_start_connect_tries (line 815) | def __mips_start_connect_tries(self) -> None:
    method __mips_disconnect (line 819) | def __mips_disconnect(self) -> None:
    method __get_next_reconnect_time (line 841) | def __get_next_reconnect_time(self) -> float:
    method __reset_reconnect_time (line 850) | def __reset_reconnect_time(self) -> None:
  class MipsCloudClient (line 854) | class MipsCloudClient(_MipsClient):
    method __init__ (line 860) | def __init__(
    method disconnect (line 872) | def disconnect(self) -> None:
    method update_access_token (line 876) | def update_access_token(self, access_token: str) -> bool:
    method sub_prop (line 883) | def sub_prop(
    method unsub_prop (line 921) | def unsub_prop(
    method sub_event (line 935) | def sub_event(
    method unsub_event (line 974) | def unsub_event(
    method sub_device_state (line 989) | def sub_device_state(
    method unsub_device_state (line 1025) | def unsub_device_state(self, did: str) -> bool:
    method get_dev_list_async (line 1031) | async def get_dev_list_async(
    method get_prop_async (line 1036) | async def get_prop_async(
    method set_prop_async (line 1041) | async def set_prop_async(
    method action_async (line 1047) | async def action_async(
    method __reg_broadcast_external (line 1053) | def __reg_broadcast_external(
    method __unreg_broadcast_external (line 1061) | def __unreg_broadcast_external(self, topic: str) -> bool:
    method __reg_broadcast (line 1066) | def __reg_broadcast(
    method __unreg_broadcast (line 1079) | def __unreg_broadcast(self, topic: str) -> None:
    method _on_mips_connect (line 1084) | def _on_mips_connect(self, rc: int, props: dict) -> None:
    method _on_mips_disconnect (line 1090) | def _on_mips_disconnect(self, rc: int, props: dict) -> None:
    method _on_mips_message (line 1094) | def _on_mips_message(self, topic: str, payload: bytes) -> None:
  class MipsLocalClient (line 1114) | class MipsLocalClient(_MipsClient):
    method __init__ (line 1134) | def __init__(
    method group_id (line 1157) | def group_id(self) -> str:
    method log_debug (line 1160) | def log_debug(self, msg, *args, **kwargs) -> None:
    method log_info (line 1164) | def log_info(self, msg, *args, **kwargs) -> None:
    method log_error (line 1168) | def log_error(self, msg, *args, **kwargs) -> None:
    method connect (line 1173) | def connect(self, thread_name: Optional[str] = None) -> None:
    method disconnect (line 1178) | def disconnect(self) -> None:
    method sub_prop (line 1184) | def sub_prop(
    method unsub_prop (line 1214) | def unsub_prop(
    method sub_event (line 1226) | def sub_event(
    method unsub_event (line 1259) | def unsub_event(
    method get_prop_safe_async (line 1271) | async def get_prop_safe_async(
    method get_prop_async (line 1293) | async def get_prop_async(
    method set_prop_async (line 1309) | async def set_prop_async(
    method action_async (line 1346) | async def action_async(
    method get_dev_list_async (line 1376) | async def get_dev_list_async(
    method get_action_group_list_async (line 1404) | async def get_action_group_list_async(
    method exec_action_group_list_async (line 1416) | async def exec_action_group_list_async(
    method on_dev_list_changed (line 1434) | def on_dev_list_changed(
    method on_dev_list_changed (line 1441) | def on_dev_list_changed(
    method __request (line 1447) | def __request(
    method __reg_broadcast (line 1477) | def __reg_broadcast(
    method __unreg_broadcast (line 1491) | def __unreg_broadcast(self, topic) -> None:
    method _on_mips_connect (line 1500) | def _on_mips_connect(self, rc: int, props: dict) -> None:
    method _on_mips_disconnect (line 1514) | def _on_mips_disconnect(self, rc: int, props: dict) -> None:
    method _on_mips_message (line 1518) | def _on_mips_message(self, topic: str, payload: bytes) -> None:
    method __gen_mips_id (line 1569) | def __gen_mips_id(self) -> int:
    method __mips_publish (line 1574) | def __mips_publish(
    method __request_external (line 1590) | def __request_external(
    method __reg_broadcast_external (line 1601) | def __reg_broadcast_external(
    method __unreg_broadcast_external (line 1610) | def __unreg_broadcast_external(self, topic) -> bool:
    method __request_async (line 1616) | async def __request_async(
    method __get_prop_timer_handle (line 1642) | async def __get_prop_timer_handle(self) -> None:

FILE: custom_components/xiaomi_home/miot/miot_network.py
  class InterfaceStatus (line 63) | class InterfaceStatus(Enum):
  class NetworkInfo (line 71) | class NetworkInfo:
  class MIoTNetwork (line 79) | class MIoTNetwork:
    method __init__ (line 112) | def __init__(
    method init_async (line 140) | async def init_async(self) -> bool:
    method deinit_async (line 145) | async def deinit_async(self) -> None:
    method network_status (line 161) | def network_status(self) -> bool:
    method network_info (line 165) | def network_info(self) -> dict[str, NetworkInfo]:
    method update_addr_list_async (line 168) | async def update_addr_list_async(
    method sub_network_status (line 188) | def sub_network_status(
    method unsub_network_status (line 193) | def unsub_network_status(self, key: str) -> None:
    method sub_network_info (line 196) | def sub_network_info(
    method unsub_network_info (line 202) | def unsub_network_info(self, key: str) -> None:
    method refresh_async (line 205) | async def refresh_async(self) -> None:
    method get_network_status_async (line 208) | async def get_network_status_async(self) -> bool:
    method get_network_info_async (line 240) | async def get_network_info_async(self) -> dict[str, NetworkInfo]:
    method ping_multi_async (line 244) | async def ping_multi_async(
    method http_multi_async (line 257) | async def http_multi_async(
    method __calc_network_address (line 270) | def __calc_network_address(self, ip: str, netmask: str) -> str:
    method __ping_async (line 274) | async def __ping_async(self, address: Optional[str] = None) -> float:
    method __http_async (line 297) | async def __http_async(self, url: str) -> float:
    method __get_network_info (line 307) | def __get_network_info(self) -> dict[str, NetworkInfo]:
    method __call_network_info_change (line 332) | def __call_network_info_change(
    method __update_status_and_info_async (line 338) | async def __update_status_and_info_async(self) -> None:
    method __refresh_timer_handler (line 374) | def __refresh_timer_handler(self) -> None:

FILE: custom_components/xiaomi_home/miot/miot_spec.py
  class MIoTSpecValueRange (line 65) | class MIoTSpecValueRange:
    method __init__ (line 71) | def __init__(self, value_range: Union[dict, list]) -> None:
    method load (line 79) | def load(self, value_range: dict) -> None:
    method from_spec (line 87) | def from_spec(self, value_range: list) -> None:
    method dump (line 94) | def dump(self) -> dict:
    method __str__ (line 97) | def __str__(self) -> str:
  class MIoTSpecValueListItem (line 101) | class MIoTSpecValueListItem:
    method __init__ (line 110) | def __init__(self, item: dict) -> None:
    method load (line 113) | def load(self, item: dict) -> None:
    method from_spec (line 122) | def from_spec(item: dict) -> 'MIoTSpecValueListItem':
    method dump (line 134) | def dump(self) -> dict:
    method __str__ (line 141) | def __str__(self) -> str:
  class MIoTSpecValueList (line 145) | class MIoTSpecValueList:
    method __init__ (line 150) | def __init__(self, value_list: list[dict]) -> None:
    method names (line 157) | def names(self) -> list[str]:
    method values (line 161) | def values(self) -> list[Any]:
    method descriptions (line 165) | def descriptions(self) -> list[str]:
    method from_spec (line 169) | def from_spec(value_list: list[dict]) -> 'MIoTSpecValueList':
    method load (line 186) | def load(self, value_list: list[dict]) -> None:
    method to_map (line 190) | def to_map(self) -> dict:
    method get_value_by_description (line 193) | def get_value_by_description(self, description: str) -> Any:
    method get_description_by_value (line 199) | def get_description_by_value(self, value: Any) -> Optional[str]:
    method dump (line 205) | def dump(self) -> list:
  class _SpecStdLib (line 209) | class _SpecStdLib:
    method __init__ (line 220) | def __init__(self, lang: str) -> None:
    method load (line 231) | def load(self, std_lib: dict[str, dict[str, dict[str, str]]]) -> None:
    method device_translate (line 244) | def device_translate(self, key: str) -> Optional[str]:
    method service_translate (line 251) | def service_translate(self, key: str) -> Optional[str]:
    method property_translate (line 258) | def property_translate(self, key: str) -> Optional[str]:
    method event_translate (line 265) | def event_translate(self, key: str) -> Optional[str]:
    method action_translate (line 272) | def action_translate(self, key: str) -> Optional[str]:
    method value_translate (line 279) | def value_translate(self, key: str) -> Optional[str]:
    method dump (line 286) | def dump(self) -> dict[str, dict[str, dict[str, str]]]:
    method refresh_async (line 296) | async def refresh_async(self) -> bool:
    method __request_from_cloud_async (line 303) | async def __request_from_cloud_async(self) -> Optional[dict]:
    method __get_property_value (line 394) | async def __get_property_value(self) -> dict:
    method __get_template_list (line 413) | async def __get_template_list(self, url: str) -> dict:
  class _MIoTSpecBase (line 436) | class _MIoTSpecBase:
    method __init__ (line 456) | def __init__(self, spec: dict) -> None:
    method __hash__ (line 475) | def __hash__(self) -> int:
    method __eq__ (line 478) | def __eq__(self, value) -> bool:
  class MIoTSpecProperty (line 482) | class MIoTSpecProperty(_MIoTSpecBase):
    method __init__ (line 499) | def __init__(self,
    method format_ (line 522) | def format_(self) -> Type:
    method format_ (line 526) | def format_(self, value: str) -> None:
    method access (line 535) | def access(self) -> list:
    method access (line 539) | def access(self, value: list) -> None:
    method writable (line 547) | def writable(self) -> bool:
    method readable (line 551) | def readable(self) -> bool:
    method notifiable (line 555) | def notifiable(self):
    method value_range (line 559) | def value_range(self) -> Optional[MIoTSpecValueRange]:
    method value_range (line 563) | def value_range(self, value: Union[dict, list, None]) -> None:
    method value_list (line 574) | def value_list(self) -> Optional[MIoTSpecValueList]:
    method value_list (line 578) | def value_list(self, value: Union[list[dict], MIoTSpecValueList,
    method eval_expr (line 588) | def eval_expr(self, src_value: Any) -> Any:
    method value_format (line 599) | def value_format(self, value: Any) -> Any:
    method value_precision (line 611) | def value_precision(self, value: Any) -> Any:
    method dump (line 623) | def dump(self) -> dict:
  class MIoTSpecEvent (line 644) | class MIoTSpecEvent(_MIoTSpecBase):
    method __init__ (line 649) | def __init__(self,
    method dump (line 659) | def dump(self) -> dict:
  class MIoTSpecAction (line 672) | class MIoTSpecAction(_MIoTSpecBase):
    method __init__ (line 678) | def __init__(self,
    method dump (line 690) | def dump(self) -> dict:
  class MIoTSpecService (line 704) | class MIoTSpecService(_MIoTSpecBase):
    method __init__ (line 710) | def __init__(self, spec: dict) -> None:
    method dump (line 716) | def dump(self) -> dict:
  class MIoTSpecInstance (line 731) | class MIoTSpecInstance:
    method __init__ (line 745) | def __init__(self, urn: str, name: str, description: str,
    method load (line 754) | def load(specs: dict) -> 'MIoTSpecInstance':
    method dump (line 806) | def dump(self) -> dict:
  class _MIoTSpecMultiLang (line 816) | class _MIoTSpecMultiLang:
    method __init__ (line 828) | def __init__(self,
    method set_spec_async (line 839) | async def set_spec_async(self, urn: str) -> None:
    method translate (line 918) | def translate(self, key: str) -> Optional[str]:
    method __get_multi_lang_async (line 923) | async def __get_multi_lang_async(self, urn: str) -> dict:
  class _SpecBoolTranslation (line 933) | class _SpecBoolTranslation:
    method __init__ (line 943) | def __init__(self,
    method init_async (line 951) | async def init_async(self) -> None:
    method deinit_async (line 1000) | async def deinit_async(self) -> None:
    method translate_async (line 1004) | async def translate_async(self, urn: str) -> Optional[list[dict]]:
  class _SpecFilter (line 1017) | class _SpecFilter:
    method __init__ (line 1026) | def __init__(self, loop: Optional[asyncio.AbstractEventLoop]) -> None:
    method init_async (line 1031) | async def init_async(self) -> None:
    method deinit_async (line 1058) | async def deinit_async(self) -> None:
    method set_spec_spec (line 1062) | async def set_spec_spec(self, urn_key: str) -> None:
    method filter_service (line 1068) | def filter_service(self, siid: int) -> bool:
    method filter_property (line 1078) | def filter_property(self, siid: int, piid: int) -> bool:
    method filter_event (line 1087) | def filter_event(self, siid: int, eiid: int) -> bool:
    method filter_action (line 1096) | def filter_action(self, siid: int, aiid: int) -> bool:
  class _SpecAdd (line 1106) | class _SpecAdd:
    method __init__ (line 1113) | def __init__(self,
    method init_async (line 1118) | async def init_async(self) -> None:
    method deinit_async (line 1142) | async def deinit_async(self) -> None:
    method set_spec_async (line 1146) | async def set_spec_async(self, urn: str) -> None:
    method get_service_add (line 1153) | def get_service_add(self) -> Optional[list[dict]]:
  class _SpecModify (line 1157) | class _SpecModify:
    method __init__ (line 1164) | def __init__(self,
    method init_async (line 1169) | async def init_async(self) -> None:
    method deinit_async (line 1193) | async def deinit_async(self) -> None:
    method set_spec_async (line 1197) | async def set_spec_async(self, urn: str) -> None:
    method get_prop_name (line 1204) | def get_prop_name(self, siid: int, piid: int) -> Optional[str]:
    method get_prop_unit (line 1207) | def get_prop_unit(self, siid: int, piid: int) -> Optional[str]:
    method get_prop_format (line 1210) | def get_prop_format(self, siid: int, piid: int) -> Optional[str]:
    method get_prop_expr (line 1213) | def get_prop_expr(self, siid: int, piid: int) -> Optional[str]:
    method get_prop_icon (line 1216) | def get_prop_icon(self, siid: int, piid: int) -> Optional[str]:
    method get_prop_access (line 1219) | def get_prop_access(self, siid: int, piid: int) -> Optional[list]:
    method get_prop_value_range (line 1225) | def get_prop_value_range(self, siid: int, piid: int) -> Optional[list]:
    method get_prop_value_list (line 1233) | def get_prop_value_list(self, siid: int, piid: int) -> Optional[list]:
    method __get_prop_item (line 1241) | def __get_prop_item(self, siid: int, piid: int, key: str) -> Optional[...
  class MIoTSpecParser (line 1250) | class MIoTSpecParser:
    method __init__ (line 1268) | def __init__(self,
    method init_async (line 1287) | async def init_async(self) -> None:
    method deinit_async (line 1326) | async def deinit_async(self) -> None:
    method parse (line 1334) | async def parse(
    method refresh_async (line 1353) | async def refresh_async(self, urn_list: list[str]) -> int:
    method __cache_get (line 1380) | async def __cache_get(self, urn: str) -> Optional[dict]:
    method __cache_set (line 1387) | async def __cache_set(self, urn: str, data: dict) -> bool:
    method __get_instance (line 1394) | async def __get_instance(self, urn: str) -> Optional[dict]:
    method __parse (line 1399) | async def __parse(self, urn: str) -> MIoTSpecInstance:

FILE: custom_components/xiaomi_home/miot/miot_storage.py
  class MIoTStorageType (line 80) | class MIoTStorageType(Enum):
  class MIoTStorage (line 90) | class MIoTStorage:
    method __init__ (line 100) | def __init__(
    method __get_full_path (line 113) | def __get_full_path(self, domain: str, name: str, suffix: str) -> str:
    method __add_file_future (line 117) | def __add_file_future(
    method __load (line 127) | def __load(
    method load (line 168) | def load(
    method load_async (line 175) | async def load_async(
    method __save (line 194) | def __save(
    method save (line 232) | def save(
    method save_async (line 239) | async def save_async(
    method __remove (line 254) | def __remove(self, full_path: str) -> bool:
    method remove (line 260) | def remove(self, domain: str, name: str, type_: type) -> bool:
    method remove_async (line 265) | async def remove_async(self, domain: str, name: str, type_: type) -> b...
    method __remove_domain (line 281) | def __remove_domain(self, full_path: str) -> bool:
    method remove_domain (line 288) | def remove_domain(self, domain: str) -> bool:
    method remove_domain_async (line 292) | async def remove_domain_async(self, domain: str) -> bool:
    method get_names (line 312) | def get_names(self, domain: str, type_: type) -> list[str]:
    method file_exists (line 322) | def file_exists(self, domain: str, name_with_suffix: str) -> bool:
    method save_file (line 326) | def save_file(
    method save_file_async (line 335) | async def save_file_async(
    method load_file (line 352) | def load_file(self, domain: str, name_with_suffix: str) -> Optional[by...
    method load_file_async (line 358) | async def load_file_async(
    method remove_file (line 376) | def remove_file(self, domain: str, name_with_suffix: str) -> bool:
    method remove_file_async (line 380) | async def remove_file_async(
    method clear (line 397) | def clear(self) -> bool:
    method clear_async (line 406) | async def clear_async(self) -> bool:
    method update_user_config (line 421) | def update_user_config(
    method update_user_config_async (line 445) | async def update_user_config_async(
    method load_user_config (line 481) | def load_user_config(
    method load_user_config_async (line 499) | async def load_user_config_async(
    method gen_storage_path (line 527) | def gen_storage_path(
  class MIoTCert (line 540) | class MIoTCert:
    method __init__ (line 552) | def __init__(
    method ca_file (line 566) | def ca_file(self) -> str:
    method key_file (line 572) | def key_file(self) -> str:
    method cert_file (line 578) | def cert_file(self) -> str:
    method verify_ca_cert_async (line 583) | async def verify_ca_cert_async(self) -> bool:
    method user_cert_remaining_time_async (line 605) | async def user_cert_remaining_time_async(
    method gen_user_key (line 655) | def gen_user_key(self) -> str:
    method gen_user_csr (line 664) | def gen_user_csr(self, user_key: str, did: str) -> str:
    method load_user_key_async (line 682) | async def load_user_key_async(self) -> Optional[str]:
    method update_user_key_async (line 688) | async def update_user_key_async(self, key: str) -> bool:
    method load_user_cert_async (line 695) | async def load_user_cert_async(self) -> Optional[str]:
    method update_user_cert_async (line 701) | async def update_user_cert_async(self, cert: str) -> bool:
    method remove_ca_cert_async (line 708) | async def remove_ca_cert_async(self) -> bool:
    method remove_user_key_async (line 713) | async def remove_user_key_async(self) -> bool:
    method remove_user_cert_async (line 718) | async def remove_user_cert_async(self) -> bool:
    method __did_hash (line 723) | def __did_hash(self, did: str) -> str:
  class DeviceManufacturer (line 729) | class DeviceManufacturer:
    method __init__ (line 736) | def __init__(
    method init_async (line 744) | async def init_async(self) -> None:
    method deinit_async (line 782) | async def deinit_async(self) -> None:
    method get_name (line 785) | def get_name(self, short_name: str) -> str:

FILE: custom_components/xiaomi_home/miot/web_pages.py
  function _load_page_template (line 55) | def _load_page_template():
  function oauth_redirect_page (line 64) | async def oauth_redirect_page(

FILE: custom_components/xiaomi_home/notify.py
  function async_setup_entry (line 66) | async def async_setup_entry(
  class Notify (line 84) | class Notify(MIoTActionEntity, NotifyEntity):
    method __init__ (line 88) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecAction) -> N...
    method async_send_message (line 97) | async def async_send_message(

FILE: custom_components/xiaomi_home/number.py
  function async_setup_entry (line 61) | async def async_setup_entry(
  class Number (line 79) | class Number(MIoTPropertyEntity, NumberEntity):
    method __init__ (line 82) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) ->...
    method native_value (line 100) | def native_value(self) -> Optional[float]:
    method async_set_native_value (line 104) | async def async_set_native_value(self, value: float) -> None:

FILE: custom_components/xiaomi_home/select.py
  function async_setup_entry (line 61) | async def async_setup_entry(
  class Select (line 79) | class Select(MIoTPropertyEntity, SelectEntity):
    method __init__ (line 82) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) ->...
    method async_select_option (line 88) | async def async_select_option(self, option: str) -> None:
    method current_option (line 94) | def current_option(self) -> Optional[str]:

FILE: custom_components/xiaomi_home/sensor.py
  function async_setup_entry (line 65) | async def async_setup_entry(
  class Sensor (line 89) | class Sensor(MIoTPropertyEntity, SensorEntity):
    method __init__ (line 92) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) ->...
    method native_value (line 123) | def native_value(self) -> Any:

FILE: custom_components/xiaomi_home/switch.py
  function async_setup_entry (line 62) | async def async_setup_entry(
  class Switch (line 80) | class Switch(MIoTPropertyEntity, SwitchEntity):
    method __init__ (line 84) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) ->...
    method is_on (line 91) | def is_on(self) -> bool:
    method async_turn_on (line 95) | async def async_turn_on(self, **kwargs: Any) -> None:
    method async_turn_off (line 99) | async def async_turn_off(self, **kwargs: Any) -> None:
    method async_toggle (line 103) | async def async_toggle(self, **kwargs: Any) -> None:

FILE: custom_components/xiaomi_home/text.py
  function async_setup_entry (line 66) | async def async_setup_entry(
  class Text (line 88) | class Text(MIoTPropertyEntity, TextEntity):
    method __init__ (line 91) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) ->...
    method native_value (line 96) | def native_value(self) -> Optional[str]:
    method async_set_value (line 102) | async def async_set_value(self, value: str) -> None:
  class ActionText (line 107) | class ActionText(MIoTActionEntity, TextEntity):
    method __init__ (line 110) | def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecAction) -> N...
    method async_set_value (line 119) | async def async_set_value(self, value: str) -> None:

FILE: custom_components/xiaomi_home/vacuum.py
  function async_setup_entry (line 72) | async def async_setup_entry(
  class Vacuum (line 88) | class Vacuum(MIoTServiceEntity, StateVacuumEntity):
    method __init__ (line 111) | def __init__(self, miot_device: MIoTDevice,
    method async_start (line 211) | async def async_start(self) -> None:
    method async_stop (line 221) | async def async_stop(self, **kwargs: Any) -> None:
    method async_pause (line 225) | async def async_pause(self) -> None:
    method async_return_to_base (line 229) | async def async_return_to_base(self, **kwargs: Any) -> None:
    method async_locate (line 233) | async def async_locate(self, **kwargs: Any) -> None:
    method async_set_fan_speed (line 237) | async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> ...
    method name (line 245) | def name(self) -> Optional[str]:
    method fan_speed (line 250) | def fan_speed(self) -> Optional[str]:
    method activity (line 259) | def activity(self) -> Optional[str]:
    method state (line 294) | def state(self) -> Optional[str]:

FILE: custom_components/xiaomi_home/water_heater.py
  function async_setup_entry (line 67) | async def async_setup_entry(
  class WaterHeater (line 86) | class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
    method __init__ (line 95) | def __init__(self, miot_device: MIoTDevice,
    method async_turn_on (line 150) | async def async_turn_on(self) -> None:
    method async_turn_off (line 154) | async def async_turn_off(self) -> None:
    method async_set_temperature (line 158) | async def async_set_temperature(self, **kwargs: Any) -> None:
    method async_set_operation_mode (line 163) | async def async_set_operation_mode(self, operation_mode: str) -> None:
    method current_temperature (line 181) | def current_temperature(self) -> Optional[float]:
    method target_temperature (line 187) | def target_temperature(self) -> Optional[float]:
    method current_operation (line 193) | def current_operation(self) -> Optional[str]:

FILE: test/check_rule_format.py
  function load_json_file (line 29) | def load_json_file(file_path: str) -> Optional[dict]:
  function save_json_file (line 41) | def save_json_file(file_path: str, data: dict) -> None:
  function load_yaml_file (line 46) | def load_yaml_file(file_path: str) -> Optional[dict]:
  function save_yaml_file (line 58) | def save_yaml_file(file_path: str, data: dict) -> None:
  function dict_str_str (line 68) | def dict_str_str(d: dict) -> bool:
  function dict_str_dict (line 78) | def dict_str_dict(d: dict) -> bool:
  function nested_2_dict_str_str (line 88) | def nested_2_dict_str_str(d: dict) -> bool:
  function nested_3_dict_str_str (line 98) | def nested_3_dict_str_str(d: dict) -> bool:
  function spec_filter (line 108) | def spec_filter(d: dict) -> bool:
  function bool_trans (line 121) | def bool_trans(d: dict) -> bool:
  function multi_lang (line 145) | def multi_lang(data: dict) -> bool:
  function spec_add (line 153) | def spec_add(data: dict) -> bool:
  function spec_modify (line 253) | def spec_modify(data: dict) -> bool:
  function compare_dict_structure (line 268) | def compare_dict_structure(dict1: dict, dict2: dict) -> bool:
  function sort_bool_trans (line 293) | def sort_bool_trans(file_path: str):
  function sort_spec_filter (line 302) | def sort_spec_filter(file_path: str):
  function sort_spec_add (line 311) | def sort_spec_add(file_path: str):
  function sort_multi_lang (line 317) | def sort_multi_lang(file_path: str):
  function sort_spec_modify (line 321) | def sort_spec_modify(file_path: str):
  function test_bool_trans (line 328) | def test_bool_trans():
  function test_spec_filter (line 336) | def test_spec_filter():
  function test_multi_lang (line 344) | def test_multi_lang():
  function test_spec_add (line 352) | def test_spec_add():
  function test_spec_modify (line 360) | def test_spec_modify():
  function test_miot_i18n (line 368) | def test_miot_i18n():
  function test_translations (line 378) | def test_translations():
  function test_miot_lang_integrity (line 388) | def test_miot_lang_integrity():
  function test_miot_data_sort (line 425) | def test_miot_data_sort():
  function test_sort_spec_data (line 462) | def test_sort_spec_data():

FILE: test/conftest.py
  function set_logger (line 24) | def set_logger():
  function load_py_file (line 37) | def load_py_file():
  function test_root_path (line 98) | def test_root_path() -> str:
  function test_cache_path (line 103) | def test_cache_path() -> str:
  function test_oauth2_redirect_url (line 109) | def test_oauth2_redirect_url() -> str:
  function test_lang (line 114) | def test_lang() -> str:
  function test_uid (line 119) | def test_uid() -> str:
  function test_random_did (line 124) | def test_random_did() -> str:
  function test_uuid (line 130) | def test_uuid() -> str:
  function test_cloud_server (line 136) | def test_cloud_server() -> str:
  function test_domain_cloud_cache (line 141) | def test_domain_cloud_cache() -> str:
  function test_name_oauth2_info (line 146) | def test_name_oauth2_info() -> str:
  function test_name_uid (line 151) | def test_name_uid() -> str:
  function test_name_uuid (line 156) | def test_name_uuid() -> str:
  function test_name_rd_did (line 161) | def test_name_rd_did() -> str:
  function test_name_homes (line 166) | def test_name_homes() -> str:
  function test_name_devices (line 171) | def test_name_devices() -> str:

FILE: test/test_cloud.py
  function test_miot_oauth_async (line 15) | async def test_miot_oauth_async(
  function test_miot_oauth_refresh_token (line 86) | async def test_miot_oauth_refresh_token(
  function test_miot_cloud_get_nickname_async (line 138) | async def test_miot_cloud_get_nickname_async(
  function test_miot_cloud_get_uid_async (line 167) | async def test_miot_cloud_get_uid_async(
  function test_miot_cloud_get_homeinfos_async (line 199) | async def test_miot_cloud_get_homeinfos_async(
  function test_miot_cloud_get_devices_async (line 245) | async def test_miot_cloud_get_devices_async(
  function test_miot_cloud_get_devices_with_dids_async (line 297) | async def test_miot_cloud_get_devices_with_dids_async(
  function test_miot_cloud_get_cert (line 335) | async def test_miot_cloud_get_cert(
  function test_miot_cloud_get_prop_async (line 418) | async def test_miot_cloud_get_prop_async(
  function test_miot_cloud_get_props_async (line 455) | async def test_miot_cloud_get_props_async(
  function test_miot_cloud_set_prop_async (line 494) | async def test_miot_cloud_set_prop_async(
  function test_miot_cloud_action_async (line 545) | async def test_miot_cloud_action_async(

FILE: test/test_common.py
  function test_miot_matcher (line 9) | def test_miot_matcher():

FILE: test/test_lan.py
  function test_lan_async (line 45) | async def test_lan_async(test_devices: dict):

FILE: test/test_mdns.py
  function test_service_loop_async (line 15) | async def test_service_loop_async():

FILE: test/test_mips.py
  function test_mips_local_async (line 22) | async def test_mips_local_async(
  function test_mips_cloud_async (line 152) | async def test_mips_cloud_async(

FILE: test/test_network.py
  function test_network_monitor_loop_async (line 13) | async def test_network_monitor_loop_async():

FILE: test/test_spec.py
  function test_spec_parse_async (line 26) | async def test_spec_parse_async(test_cache_path, test_lang, urn):
  function test_spec_refresh_async (line 47) | async def test_spec_refresh_async(test_cache_path, test_lang, urn_list):
  function test_spec_random_parse_async (line 59) | async def test_spec_random_parse_async(test_cache_path, test_lang):

FILE: test/test_storage.py
  function test_variable_async (line 16) | async def test_variable_async(test_cache_path):
  function test_load_domain_async (line 79) | async def test_load_domain_async(test_cache_path):
  function test_multi_task_load_async (line 94) | async def test_multi_task_load_async(test_cache_path):
  function test_file_save_load_async (line 115) | async def test_file_save_load_async(test_cache_path):
  function test_user_config_async (line 145) | async def test_user_config_async(
  function test_clear_async (line 213) | async def test_clear_async(test_cache_path):

FILE: tools/common.py
  function load_yaml_file (line 9) | def load_yaml_file(yaml_file: str) -> dict:
  function save_yaml_file (line 14) | def save_yaml_file(yaml_file: str, data: dict) -> None:
  function load_json_file (line 20) | def load_json_file(json_file: str) -> dict:
  function save_json_file (line 25) | def save_json_file(json_file: str, data: dict) -> None:
  function http_get (line 30) | def http_get(

FILE: tools/update_lan_rule.py
  function update_profile_model (line 56) | def update_profile_model(file_path: str):
Condensed preview — 100 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,566K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "chars": 3877,
    "preview": "name: Bug Report / 报告问题\ndescription: Create a report to help us improve. / 报告问题以帮助我们改进\nbody:\n  - type: textarea\n    attr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 404,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Feature Suggestion / 功能建议\n    url: https://github.com/XiaoMi/ha_xia"
  },
  {
    "path": ".github/workflows/release.yaml",
    "chars": 631,
    "preview": "name: Release\n\non:\n  release:\n    types: [published]\n\njobs:\n  release-zip:\n    runs-on: ubuntu-latest\n    steps:\n      -"
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 674,
    "preview": "name: Tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n  workflow_dispatch:\n\njob"
  },
  {
    "path": ".github/workflows/validate.yaml",
    "chars": 1740,
    "preview": "name: Validate\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n  workflow_dispatch:\n\n"
  },
  {
    "path": ".gitignore",
    "chars": 57,
    "preview": "__pycache__\n.pytest_cache\n.vscode\n.idea\nrequirements.txt\n"
  },
  {
    "path": ".pylintrc",
    "chars": 12975,
    "preview": "# This Pylint rcfile contains a best-effort configuration to uphold the\n# best-practices and style described in the Goog"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 24647,
    "preview": "# CHANGELOG\n## v0.4.7\n### Added\n- Add turkish language in multi_lang.json. [#1593](https://github.com/XiaoMi/ha_xiaomi_h"
  },
  {
    "path": "CLAUDE.md",
    "chars": 7083,
    "preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 4455,
    "preview": "# Contribution Guidelines\n\n[English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)\n\nThank you for considering co"
  },
  {
    "path": "LICENSE.md",
    "chars": 3412,
    "preview": "# 许可证\n\n版权声明 (C) 2024 小米公司。\n\n在本许可证下提供的 Home Assistant 米家集成(Xiaomi Home Integration)和相关米家云服务 API 接口,包括源代码和目标代码(统称为“授权作品”)的"
  },
  {
    "path": "LegalNotice.md",
    "chars": 1368,
    "preview": "# 法律声明\n\n版权声明 (C) 2024 小米。\nHome Assistant 米家集成(Xiaomi Home Integration)所使用的米家云服务 API 接口(以下简称小米云接口)的所有权及其知识产权为小米所有。您仅限于在[米"
  },
  {
    "path": "README.md",
    "chars": 24087,
    "preview": "# Xiaomi Home Integration for Home Assistant\n\n[English](./README.md) | [简体中文](./doc/README_zh.md)\n\nXiaomi Home Integrati"
  },
  {
    "path": "custom_components/xiaomi_home/__init__.py",
    "chars": 16488,
    "preview": "# -*- coding: utf-8 -*-\r\n\"\"\"\r\nCopyright (C) 2024 Xiaomi Corporation.\r\n\r\nThe ownership and intellectual property rights o"
  },
  {
    "path": "custom_components/xiaomi_home/binary_sensor.py",
    "chars": 4407,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/button.py",
    "chars": 3996,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/climate.py",
    "chars": 33347,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/config_flow.py",
    "chars": 97348,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/cover.py",
    "chars": 14937,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/device_tracker.py",
    "chars": 5620,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/event.py",
    "chars": 4269,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/fan.py",
    "chars": 13817,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/humidifier.py",
    "chars": 8408,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/light.py",
    "chars": 12207,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/manifest.json",
    "chars": 738,
    "preview": "{\n    \"domain\": \"xiaomi_home\",\n    \"name\": \"Xiaomi Home\",\n    \"codeowners\": [\n        \"@XiaoMi\"\n    ],\n    \"config_flow\""
  },
  {
    "path": "custom_components/xiaomi_home/media_player.py",
    "chars": 20026,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/common.py",
    "chars": 7217,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/const.py",
    "chars": 6052,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/de.json",
    "chars": 8081,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"Geräte\",\n            \"found_central_gateway\": \", lokales ze"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/en.json",
    "chars": 7463,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"Devices\",\n            \"found_central_gateway\": \", Found Loc"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/es.json",
    "chars": 8239,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"dispositivos\",\n            \"found_central_gateway\": \", se e"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/fr.json",
    "chars": 8093,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"appareils\",\n            \"found_central_gateway\": \", passere"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/it.json",
    "chars": 8196,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"Dispositivi\",\n            \"found_central_gateway\": \", Trova"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/ja.json",
    "chars": 5983,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"デバイス\",\n            \"found_central_gateway\": \"、ローカル中央ゲートウェイが"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/nl.json",
    "chars": 7805,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"Apparaten\",\n            \"found_central_gateway\": \", Lokale "
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/pt-BR.json",
    "chars": 7951,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"dispositivos\",\n            \"found_central_gateway\": \"encont"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/pt.json",
    "chars": 7949,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"dispositivos\",\n            \"found_central_gateway\": \", enco"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/ru.json",
    "chars": 7973,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"устройства\",\n            \"found_central_gateway\": \", найден"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/tr.json",
    "chars": 7610,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"Cihazlar\",\n            \"found_central_gateway\": \", Yerel Me"
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/zh-Hans.json",
    "chars": 5403,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"个设备\",\n            \"found_central_gateway\": \",发现本地中枢网关\",\n   "
  },
  {
    "path": "custom_components/xiaomi_home/miot/i18n/zh-Hant.json",
    "chars": 5405,
    "preview": "{\n    \"config\": {\n        \"other\": {\n            \"devices\": \"個設備\",\n            \"found_central_gateway\": \",發現本地中樞網關\",\n   "
  },
  {
    "path": "custom_components/xiaomi_home/miot/lan/profile_models.yaml",
    "chars": 30901,
    "preview": "090615.curtain.mt800w:\n  ts: 1604589513\n090615.curtain.p01:\n  ts: 1603355069\n090615.curtain.sidt82:\n  ts: 1604589553\n090"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_client.py",
    "chars": 86906,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_cloud.py",
    "chars": 32893,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_device.py",
    "chars": 60094,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_error.py",
    "chars": 4665,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_i18n.py",
    "chars": 4661,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_lan.py",
    "chars": 51807,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_mdns.py",
    "chars": 10666,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_mips.py",
    "chars": 59724,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_network.py",
    "chars": 14559,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_spec.py",
    "chars": 65397,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/miot_storage.py",
    "chars": 31203,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/resource/oauth_redirect_page.html",
    "chars": 6773,
    "preview": "<html>\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n   "
  },
  {
    "path": "custom_components/xiaomi_home/miot/specs/bool_trans.yaml",
    "chars": 7172,
    "preview": "data:\n  urn:miot-spec-v2:property:air-cooler:000000EB: open_close\n  urn:miot-spec-v2:property:alarm:00000012: open_close"
  },
  {
    "path": "custom_components/xiaomi_home/miot/specs/multi_lang.json",
    "chars": 37043,
    "preview": "{\n  \"urn:miot-spec-v2:device:bath-heater:0000A028:yeelink-v10\": {\n    \"en\": {\n      \"service:003:property:001:valuelist:"
  },
  {
    "path": "custom_components/xiaomi_home/miot/specs/spec_add.json",
    "chars": 44380,
    "preview": "{\n  \"urn:miot-spec-v2:device:air-conditioner:0000A004:090615-ktf:1\": [\n    {\n      \"iid\": 2,\n      \"type\": \"urn:miot-spe"
  },
  {
    "path": "custom_components/xiaomi_home/miot/specs/spec_filter.yaml",
    "chars": 1474,
    "preview": "urn:miot-spec-v2:device:air-conditioner:0000A004:090615-ktf:\n  services:\n  - '4'\nurn:miot-spec-v2:device:air-conditioner"
  },
  {
    "path": "custom_components/xiaomi_home/miot/specs/spec_modify.yaml",
    "chars": 17139,
    "preview": "urn:miot-spec-v2:device:air-condition-outlet:0000A045:lumi-mcn04:1:\n  prop.3.4:\n    format: uint8\nurn:miot-spec-v2:devic"
  },
  {
    "path": "custom_components/xiaomi_home/miot/specs/specv2entity.py",
    "chars": 22858,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/miot/web_pages.py",
    "chars": 3508,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/notify.py",
    "chars": 7092,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/number.py",
    "chars": 4750,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/select.py",
    "chars": 4345,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/sensor.py",
    "chars": 6183,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/switch.py",
    "chars": 4564,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/text.py",
    "chars": 8216,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/translations/de.json",
    "chars": 28322,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Xiaomi Home Integration\",\n        \"step\": {\n            \"eula\": {\n             "
  },
  {
    "path": "custom_components/xiaomi_home/translations/en.json",
    "chars": 25231,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Xiaomi Home Integration\",\n        \"step\": {\n            \"eula\": {\n             "
  },
  {
    "path": "custom_components/xiaomi_home/translations/es.json",
    "chars": 28881,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Integración de Xiaomi Home\",\n        \"step\": {\n            \"eula\": {\n          "
  },
  {
    "path": "custom_components/xiaomi_home/translations/fr.json",
    "chars": 29103,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Intégration Xiaomi Home\",\n        \"step\": {\n            \"eula\": {\n             "
  },
  {
    "path": "custom_components/xiaomi_home/translations/it.json",
    "chars": 28457,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Integrazione Xiaomi Home\",\n        \"step\": {\n            \"eula\": {\n            "
  },
  {
    "path": "custom_components/xiaomi_home/translations/ja.json",
    "chars": 17243,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Xiaomi Home インテグレーション\",\n        \"step\": {\n            \"eula\": {\n               "
  },
  {
    "path": "custom_components/xiaomi_home/translations/nl.json",
    "chars": 26767,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Xiaomi Home Integratie\",\n        \"step\": {\n            \"eula\": {\n              "
  },
  {
    "path": "custom_components/xiaomi_home/translations/pt-BR.json",
    "chars": 26708,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Integração Xiaomi Home\",\n        \"step\": {\n            \"eula\": {\n              "
  },
  {
    "path": "custom_components/xiaomi_home/translations/pt.json",
    "chars": 26763,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Integração Xiaomi Home\",\n        \"step\": {\n            \"eula\": {\n              "
  },
  {
    "path": "custom_components/xiaomi_home/translations/ru.json",
    "chars": 27135,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Ми Дома Интеграция\",\n        \"step\": {\n            \"eula\": {\n                \"t"
  },
  {
    "path": "custom_components/xiaomi_home/translations/tr.json",
    "chars": 25621,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"Xiaomi Home Entegrasyonu\",\n        \"step\": {\n            \"eula\": {\n            "
  },
  {
    "path": "custom_components/xiaomi_home/translations/zh-Hans.json",
    "chars": 13797,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"米家集成\",\n        \"step\": {\n            \"eula\": {\n                \"title\": \"风险告知\","
  },
  {
    "path": "custom_components/xiaomi_home/translations/zh-Hant.json",
    "chars": 13751,
    "preview": "{\n    \"config\": {\n        \"flow_title\": \"米家集成\",\n        \"step\": {\n            \"eula\": {\n                \"title\": \"風險告知\","
  },
  {
    "path": "custom_components/xiaomi_home/vacuum.py",
    "chars": 13668,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "custom_components/xiaomi_home/water_heater.py",
    "chars": 9169,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nCopyright (C) 2024 Xiaomi Corporation.\n\nThe ownership and intellectual property rights of Xi"
  },
  {
    "path": "doc/CONTRIBUTING_zh.md",
    "chars": 2126,
    "preview": "# 贡献指南\n\n[English](../CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)\n\n感谢您考虑为我们的项目做出贡献!您的努力将使我们的项目变得更好。\n\n在您开始贡献之前,请花一点时间阅"
  },
  {
    "path": "doc/README_zh.md",
    "chars": 13675,
    "preview": "# Home Assistant 米家集成\n\n[English](../README.md) | [简体中文](./README_zh.md)\n\n米家集成是一个由小米官方提供支持的 Home Assistant 的集成组件,它可以让您在 H"
  },
  {
    "path": "hacs.json",
    "chars": 85,
    "preview": "{\n    \"name\": \"Xiaomi Home\",\n    \"homeassistant\": \"2024.4.4\",\n    \"hacs\": \"1.34.0\"\n}\n"
  },
  {
    "path": "install.sh",
    "chars": 766,
    "preview": "#!/bin/bash\nset -e\n\n# Check the number of input parameters.\nif [ $# -ne 1 ]; then\n    echo \"usage: $0 [config_path]\"\n   "
  },
  {
    "path": "test/.gitignore",
    "chars": 15,
    "preview": "miot\ntest_cache"
  },
  {
    "path": "test/check_rule_format.py",
    "chars": 19525,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Test rule format.\"\"\"\nimport json\nimport logging\nfrom os import listdir, path\nfrom typing impo"
  },
  {
    "path": "test/conftest.py",
    "chars": 4678,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Pytest fixtures.\"\"\"\nimport logging\nimport random\nimport shutil\nimport pytest\nfrom os import p"
  },
  {
    "path": "test/pytest.ini",
    "chars": 96,
    "preview": "[pytest]\nmarkers:\n    github: tests for github actions\n    update: update or re-sort config file"
  },
  {
    "path": "test/test_cloud.py",
    "chars": 21482,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Unit test for miot_cloud.py.\"\"\"\nimport asyncio\nimport logging\nimport time\nimport webbrowser\ni"
  },
  {
    "path": "test/test_common.py",
    "chars": 1148,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Unit test for miot_common.py.\"\"\"\nimport pytest\n\n# pylint: disable=import-outside-toplevel, un"
  },
  {
    "path": "test/test_lan.py",
    "chars": 5184,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Unit test for miot_lan.py.\"\"\"\nimport logging\nfrom typing import Any\nimport pytest\nimport asyn"
  },
  {
    "path": "test/test_mdns.py",
    "chars": 1253,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Unit test for miot_mdns.py.\"\"\"\nimport asyncio\nimport logging\nimport pytest\nfrom zeroconf impo"
  },
  {
    "path": "test/test_mips.py",
    "chars": 10102,
    "preview": "# -*- coding: utf-8 -*-\r\n\"\"\"Unit test for miot_mips.py.\r\nNOTICE: When running this test case, you need to run test_cloud"
  },
  {
    "path": "test/test_network.py",
    "chars": 1026,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Unit test for miot_network.py.\"\"\"\nimport logging\nimport pytest\nimport asyncio\n\n_LOGGER = logg"
  },
  {
    "path": "test/test_spec.py",
    "chars": 3606,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Unit test for miot_spec.py.\"\"\"\nimport json\nimport logging\nimport random\nimport time\nfrom urll"
  },
  {
    "path": "test/test_storage.py",
    "chars": 8150,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Unit test for miot_storage.py.\"\"\"\nimport asyncio\nimport logging\nfrom os import path\nimport py"
  },
  {
    "path": "tools/common.py",
    "chars": 1278,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Common functions.\"\"\"\nimport json\nimport yaml\nfrom urllib.parse import urlencode\nfrom urllib.r"
  },
  {
    "path": "tools/update_lan_rule.py",
    "chars": 3083,
    "preview": "\"\"\" Update LAN rule.\"\"\"\n# -*- coding: utf-8 -*-\n# pylint: disable=relative-beyond-top-level\nfrom os import path\nfrom com"
  }
]

About this extraction

This page contains the full source code of the XiaoMi/ha_xiaomi_home GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 100 files (1.4 MB), approximately 371.2k tokens, and a symbol index with 1008 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!